Przeglądaj źródła

Merge branch 'master' into vulkan

rdb 8 lat temu
rodzic
commit
9feb68f0aa
100 zmienionych plików z 4883 dodań i 1777 usunięć
  1. 10 1
      .gitignore
  2. 18 3
      .travis.yml
  3. 34 1
      README.md
  4. 1 1
      contrib/src/ai/aiCharacter.h
  5. 1 1
      contrib/src/ai/aiNode.h
  6. 1 1
      contrib/src/ai/flock.h
  7. 0 10
      direct/metalibs/direct/direct.cxx
  8. 95 1
      direct/src/actor/Actor.py
  9. 13 0
      direct/src/controls/InputState.py
  10. 1 1
      direct/src/dcparser/dcPacker.cxx
  11. 2 2
      direct/src/distributed/cConnectionRepository.h
  12. 21 0
      direct/src/filter/CommonFilters.py
  13. 12 0
      direct/src/filter/FilterManager.py
  14. 3 2
      direct/src/filter/filter-bloomx.sha
  15. 3 2
      direct/src/filter/filter-bloomy.sha
  16. 6 4
      direct/src/filter/filter-blurx.sha
  17. 6 4
      direct/src/filter/filter-blury.sha
  18. 1 1
      direct/src/interval/ActorInterval.py
  19. 3 1
      direct/src/interval/Interval.py
  20. 3 3
      direct/src/interval/cConstrainHprInterval.h
  21. 4 4
      direct/src/interval/cConstrainPosHprInterval.h
  22. 3 3
      direct/src/interval/cConstrainPosInterval.h
  23. 3 3
      direct/src/interval/cConstrainTransformInterval.h
  24. 2 2
      direct/src/interval/cLerpAnimEffectInterval.h
  25. 4 4
      direct/src/interval/cLerpNodePathInterval.h
  26. 1 1
      direct/src/interval/cMetaInterval.h
  27. 1 1
      direct/src/interval/hideInterval.h
  28. 1 1
      direct/src/interval/showInterval.h
  29. 1 1
      direct/src/interval/waitInterval.h
  30. 24 0
      direct/src/showbase/Audio3DManager.py
  31. 12 0
      direct/src/showbase/DirectObject.py
  32. 128 53
      direct/src/showbase/Loader.py
  33. 24 1
      direct/src/showbase/Messenger.py
  34. 42 4
      direct/src/showbase/MirrorDemo.py
  35. 53 45
      direct/src/showbase/ShowBase.py
  36. 2 2
      direct/src/showbase/Transitions.py
  37. 35 11
      direct/src/stdpy/thread.py
  38. 33 11
      direct/src/stdpy/threading.py
  39. 43 26
      direct/src/stdpy/threading2.py
  40. 13 1
      direct/src/task/Task.py
  41. 536 524
      dtool/src/cppparser/cppBison.cxx.prebuilt
  42. 92 90
      dtool/src/cppparser/cppBison.h.prebuilt
  43. 252 95
      dtool/src/cppparser/cppBison.yxx
  44. 8 0
      dtool/src/cppparser/cppExtensionType.cxx
  45. 1 0
      dtool/src/cppparser/cppExtensionType.h
  46. 2 0
      dtool/src/cppparser/cppFunctionType.h
  47. 19 8
      dtool/src/cppparser/cppInstance.cxx
  48. 14 28
      dtool/src/cppparser/cppMakeProperty.cxx
  49. 65 8
      dtool/src/cppparser/cppMakeProperty.h
  50. 8 0
      dtool/src/cppparser/cppPointerType.cxx
  51. 1 0
      dtool/src/cppparser/cppPointerType.h
  52. 1 0
      dtool/src/cppparser/cppPreprocessor.cxx
  53. 8 0
      dtool/src/cppparser/cppSimpleType.cxx
  54. 1 0
      dtool/src/cppparser/cppSimpleType.h
  55. 176 0
      dtool/src/cppparser/cppStructType.cxx
  56. 7 1
      dtool/src/cppparser/cppStructType.h
  57. 8 0
      dtool/src/cppparser/cppType.cxx
  58. 1 0
      dtool/src/cppparser/cppType.h
  59. 8 0
      dtool/src/cppparser/cppTypedefType.cxx
  60. 1 0
      dtool/src/cppparser/cppTypedefType.h
  61. 13 1
      dtool/src/dtoolbase/dtoolbase.h
  62. 13 16
      dtool/src/dtoolbase/dtoolbase_cc.h
  63. 14 17
      dtool/src/dtoolbase/memoryHook.cxx
  64. 1 3
      dtool/src/dtoolbase/memoryHook.h
  65. 0 2
      dtool/src/dtoolbase/typeRegistryNode.cxx
  66. 0 2
      dtool/src/dtoolbase/typeRegistryNode.h
  67. 16 0
      dtool/src/dtoolutil/executionEnvironment.cxx
  68. 9 0
      dtool/src/dtoolutil/executionEnvironment.h
  69. 11 4
      dtool/src/dtoolutil/filename.cxx
  70. 1 1
      dtool/src/dtoolutil/filename_ext.cxx
  71. 5 5
      dtool/src/dtoolutil/load_dso.cxx
  72. 3 3
      dtool/src/dtoolutil/pandaFileStream.h
  73. 15 0
      dtool/src/dtoolutil/pandaSystem.h
  74. 1 0
      dtool/src/dtoolutil/textEncoder.h
  75. 28 0
      dtool/src/interrogate/README.md
  76. 17 11
      dtool/src/interrogate/functionRemap.cxx
  77. 10 9
      dtool/src/interrogate/interfaceMaker.cxx
  78. 5 2
      dtool/src/interrogate/interfaceMaker.h
  79. 1 1
      dtool/src/interrogate/interfaceMakerPython.cxx
  80. 458 275
      dtool/src/interrogate/interfaceMakerPythonNative.cxx
  81. 4 0
      dtool/src/interrogate/interfaceMakerPythonNative.h
  82. 169 37
      dtool/src/interrogate/interrogateBuilder.cxx
  83. 0 52
      dtool/src/interrogate/typeManager.cxx
  84. 0 1
      dtool/src/interrogate/typeManager.h
  85. 1 1
      dtool/src/interrogatedb/interrogateDatabase.cxx
  86. 46 0
      dtool/src/interrogatedb/interrogateElement.I
  87. 8 1
      dtool/src/interrogatedb/interrogateElement.cxx
  88. 12 0
      dtool/src/interrogatedb/interrogateElement.h
  89. 8 0
      dtool/src/interrogatedb/interrogateType.I
  90. 2 0
      dtool/src/interrogatedb/interrogateType.h
  91. 2 4
      dtool/src/interrogatedb/p3interrogatedb_composite2.cxx
  92. 54 0
      dtool/src/interrogatedb/py_compat.cxx
  93. 175 0
      dtool/src/interrogatedb/py_compat.h
  94. 42 8
      dtool/src/interrogatedb/py_panda.I
  95. 73 207
      dtool/src/interrogatedb/py_panda.cxx
  96. 31 146
      dtool/src/interrogatedb/py_panda.h
  97. 1675 0
      dtool/src/interrogatedb/py_wrappers.cxx
  98. 78 0
      dtool/src/interrogatedb/py_wrappers.h
  99. 9 0
      dtool/src/parser-inc/iostream
  100. 1 1
      dtool/src/prc/configVariable.h

+ 10 - 1
.gitignore

@@ -8,6 +8,7 @@
 core
 core.*
 vgcore.*
+*.core
 
 # Editor files/directories
 *.save
@@ -22,6 +23,9 @@ vgcore.*
 *.o
 *.gch
 *.pch
+/+DESC
+/+MANIFEST
+/pkg-plist
 
 # Produced installer/executables
 /*.exe
@@ -31,6 +35,7 @@ vgcore.*
 /*.pkg
 /*.dmg
 /*.whl
+/*.txz
 
 # CMake
 /build/
@@ -47,6 +52,10 @@ Thumbs.db
 ehthumbs.db
 
 # Python
-__pycache__
+__pycache__/
 *.pyc
 *.pyo
+
+# Test tool cache directories
+.tox/
+.cache/

+ 18 - 3
.travis.yml

@@ -2,15 +2,22 @@ language: cpp
 sudo: false
 matrix:
   include:
-    - compiler: gcc
-      env: PYTHONV=python2.7 FLAGS=--optimize=4
     - compiler: clang
       env: PYTHONV=python3 FLAGS=--installer
     - compiler: clang
       env: PYTHONV=python2.7 FLAGS=--override=STDFLOAT_DOUBLE=1
+    - compiler: gcc
+      env: PYTHONV=python2.7 FLAGS=--optimize=4
+      before_install:
+        - export CC=gcc-4.7
+        - export CXX=g++-4.7
 addons:
   apt:
+    sources:
+    - ubuntu-toolchain-r-test
     packages:
+    - gcc-4.7
+    - g++-4.7
     - bison
     - flex
     - libfreetype6-dev
@@ -27,8 +34,16 @@ addons:
     - nvidia-cg-toolkit
     - python-dev
     - python3-dev
+    - python-virtualenv
     - zlib1g-dev
-script: $PYTHONV makepanda/makepanda.py --everything --git-commit $TRAVIS_COMMIT $FLAGS --threads 4 && LD_LIBRARY_PATH=built/lib PYTHONPATH=built $PYTHONV makepanda/test_imports.py
+    - fakeroot
+install:
+    - virtualenv --python=$PYTHONV venv && source venv/bin/activate
+    - $PYTHONV -m pip install pytest
+script:
+    - $PYTHONV makepanda/makepanda.py --everything --git-commit $TRAVIS_COMMIT $FLAGS --threads 4
+    - LD_LIBRARY_PATH=built/lib PYTHONPATH=built $PYTHONV makepanda/test_imports.py
+    - LD_LIBRARY_PATH=built/lib PYTHONPATH=built $PYTHONV -m pytest tests
 notifications:
   irc:
     channels:

+ 34 - 1
README.md

@@ -134,13 +134,46 @@ If the build was successful, makepanda will have generated a .dmg file in
 the source directory containing the installer.  Simply open it and run the
 package file in order to install the SDK onto your system.
 
+FreeBSD
+-------
+
+Building on FreeBSD is very similar to building on Linux.  You will need to
+install the requisite packages using the system package manager.  To install
+the recommended set of dependencies, you can use this command:
+
+```bash
+pkg install pkgconf png jpeg-turbo tiff freetype2 eigen squish openal opusfile libvorbis libX11 libGL ode bullet assimp openexr
+```
+
+You will also need to choose which version of Python you want to use.
+Install the appropriate package for it (such as `python2` or `python36`) and
+run the makepanda script with your chosen Python version:
+
+```bash
+python3.6 makepanda/makepanda.py --everything --installer --no-egl --no-gles --no-gles2
+```
+
+If successful, this will produce a .pkg file in the root of the source
+directory which you can install using `pkg install`.
+
+Running Tests
+=============
+
+Install [PyTest](https://docs.pytest.org/en/latest/getting-started.html#installation)
+and run the `pytest` command.  If you have not installed Panda3D, you will
+need to configure your enviroment by pointing the `PYTHONPATH` variable at
+the `built` directory.  On Linux, you will also need to point the
+`LD_LIBRARY_PATH` variable at the `built/lib` directory.
+
+As a convenience, you can alternatively pass the `--tests` option to makepanda.
+
 Reporting Issues
 ================
 
 If you encounter any bugs when using Panda3D, please report them in the bug
 tracker.  This is hosted at:
 
-  https://bugs.launchpad.net/panda3d
+  https://github.com/panda3d/panda3d/issues
 
 Make sure to first use the search function to see if the bug has already been
 reported.  When filling out a bug report, make sure that you include as much

+ 1 - 1
contrib/src/ai/aiCharacter.h

@@ -62,7 +62,7 @@ PUBLISHED:
     // This function is used to enable or disable the guides for path finding.
     void set_pf_guide(bool pf_guide);
 
-    AICharacter(string model_name, NodePath model_np, double mass, double movt_force, double max_force);
+    explicit AICharacter(string model_name, NodePath model_np, double mass, double movt_force, double max_force);
     ~AICharacter();
 };
 

+ 1 - 1
contrib/src/ai/aiNode.h

@@ -66,7 +66,7 @@ public:
   AINode *_next;
 
 PUBLISHED:
-  AINode(int grid_x, int grid_y, LVecBase3 pos, float w, float l, float h);
+  explicit AINode(int grid_x, int grid_y, LVecBase3 pos, float w, float l, float h);
   ~AINode();
 
   bool contains(float x, float y);

+ 1 - 1
contrib/src/ai/flock.h

@@ -44,7 +44,7 @@ public:
   AICharList _ai_char_list;
 
 PUBLISHED:
-  Flock(unsigned int flock_id, double vcone_angle, double vcone_radius, unsigned int separation_wt = 2,
+  explicit Flock(unsigned int flock_id, double vcone_angle, double vcone_radius, unsigned int separation_wt = 2,
     unsigned int cohesion_wt = 4, unsigned int alignment_wt = 1);
   ~Flock();
 

+ 0 - 10
direct/metalibs/direct/direct.cxx

@@ -1,10 +0,0 @@
-/**
- * @file direct.cxx
- * @author drose
- * @date 2000-05-18
- */
-
-// This is a dummy file whose sole purpose is to give the compiler something
-// to compile when making libdirect.so in NO_DEFER mode, which generates an
-// empty library that itself links with all the other shared libraries that
-// make up libdirect.

+ 95 - 1
direct/src/actor/Actor.py

@@ -50,6 +50,10 @@ class Actor(DirectObject, NodePath):
         def __repr__(self):
             return 'Actor.PartDef(%s, %s)' % (repr(self.partBundleNP), repr(self.partModel))
 
+
+        #snake_case alias:
+        get_bundle = getBundle
+
     class AnimDef:
 
         """Instances of this class are stored within the
@@ -72,6 +76,10 @@ class Actor(DirectObject, NodePath):
         def __repr__(self):
             return 'Actor.AnimDef(%s)' % (repr(self.filename))
 
+
+        #snake_case alias:
+        make_copy = makeCopy
+
     class SubpartDef:
 
         """Instances of this class are stored within the SubpartDict
@@ -889,7 +897,7 @@ class Actor(DirectObject, NodePath):
         return ((toFrame+1)-fromFrame) / animControl.getFrameRate()
 
     def getNumFrames(self, animName=None, partName=None):
-        lodName = next(iter(self.__animControlDict))
+        #lodName = next(iter(self.__animControlDict))
         controls = self.getAnimControls(animName, partName)
         if len(controls) == 0:
             return None
@@ -2549,3 +2557,89 @@ class Actor(DirectObject, NodePath):
         for partBundleDict in self.__partBundleDict.values():
             partDef = partBundleDict.get(subpartDef.truePartName)
             partDef.getBundle().setName(newBundleName)
+
+    #snake_case alias:
+    control_joint = controlJoint
+    set_lod_animation = setLODAnimation
+    get_anim_control_dict = getAnimControlDict
+    get_actor_info = getActorInfo
+    clear_lod_animation = clearLODAnimation
+    reset_lod = resetLOD
+    fix_bounds = fixBounds
+    get_anim_filename = getAnimFilename
+    get_subparts_complete = getSubpartsComplete
+    verify_subparts_complete = verifySubpartsComplete
+    get_play_rate = getPlayRate
+    clear_python_data = clearPythonData
+    load_anims = loadAnims
+    set_subparts_complete = setSubpartsComplete
+    draw_in_front = drawInFront
+    get_lod_node = getLODNode
+    hide_part = hidePart
+    get_joint_transform_state = getJointTransformState
+    set_control_effect = setControlEffect
+    get_anim_controls = getAnimControls
+    release_joint = releaseJoint
+    print_anim_blends = printAnimBlends
+    get_lod = getLOD
+    disable_blend = disableBlend
+    show_part = showPart
+    get_joint_transform = getJointTransform
+    face_away_from_viewer = faceAwayFromViewer
+    set_lod = setLOD
+    osd_anim_blends = osdAnimBlends
+    get_current_frame = getCurrentFrame
+    set_play_rate = setPlayRate
+    bind_all_anims = bindAllAnims
+    unload_anims = unloadAnims
+    remove_part = removePart
+    use_lod = useLOD
+    get_anim_blends = getAnimBlends
+    get_lod_index = getLODIndex
+    get_num_frames = getNumFrames
+    post_flatten = postFlatten
+    get_lod_names = getLODNames
+    list_joints = listJoints
+    make_subpart = makeSubpart
+    get_anim_control = getAnimControl
+    get_part_bundle = getPartBundle
+    get_part_bundle_dict = getPartBundleDict
+    get_duration = getDuration
+    has_lod = hasLOD
+    print_lod = printLOD
+    fix_bounds_old = fixBounds_old
+    get_anim_names = getAnimNames
+    get_part_bundles = getPartBundles
+    anim_panel = animPanel
+    stop_joint = stopJoint
+    actor_interval = actorInterval
+    hide_all_bounds = hideAllBounds
+    show_all_bounds = showAllBounds
+    init_anims_on_all_lods = initAnimsOnAllLODs
+    get_part = getPart
+    add_lod = addLOD
+    show_all_parts = showAllParts
+    get_joints = getJoints
+    get_overlapping_joints = getOverlappingJoints
+    enable_blend = enableBlend
+    face_towards_viewer = faceTowardsViewer
+    bind_anim = bindAnim
+    set_blend = setBlend
+    get_frame_time = getFrameTime
+    remove_node = removeNode
+    wait_pending = waitPending
+    expose_joint = exposeJoint
+    set_lod_node = setLODNode
+    get_frame_rate = getFrameRate
+    get_current_anim = getCurrentAnim
+    get_part_names = getPartNames
+    freeze_joint = freezeJoint
+    set_center = setCenter
+    rename_part_bundles = renamePartBundles
+    get_geom_node = getGeomNode
+    set_geom_node = setGeomNode
+    load_model = loadModel
+    copy_actor = copyActor
+    get_base_frame_rate = getBaseFrameRate
+    remove_anim_control_dict = removeAnimControlDict
+    load_anims_on_all_lods = loadAnimsOnAllLODs

+ 13 - 0
direct/src/controls/InputState.py

@@ -21,6 +21,9 @@ class InputStateToken:
     def __hash__(self):
         return self._hash
 
+    #snake_case alias:
+    is_valid = isValid
+
 class InputStateWatchToken(InputStateToken, DirectObject.DirectObject):
     def release(self):
         self._inputState._ignore(self)
@@ -39,6 +42,9 @@ class InputStateTokenGroup:
             token.release()
         self._tokens = []
 
+    #snake_case alias:
+    add_token = addToken
+
 class InputState(DirectObject.DirectObject):
     """
     InputState is for tracking the on/off state of some events.
@@ -235,3 +241,10 @@ class InputState(DirectObject.DirectObject):
         """for debugging"""
         return self.notify.debug(
             "%s (%s) %s"%(id(self), len(self._state), message))
+
+    #snake_case alias:
+    watch_with_modifiers = watchWithModifiers
+    is_set = isSet
+    get_event_name = getEventName
+    debug_print = debugPrint
+    release_inputs = releaseInputs

+ 1 - 1
direct/src/dcparser/dcPacker.cxx

@@ -708,7 +708,7 @@ pack_object(PyObject *object) {
     pack_int64(PyLong_AsLongLong(object));
 #if PY_MAJOR_VERSION >= 3
   } else if (PyUnicode_Check(object)) {
-    char *buffer;
+    const char *buffer;
     Py_ssize_t length;
     buffer = PyUnicode_AsUTF8AndSize(object, &length);
     if (buffer) {

+ 2 - 2
direct/src/distributed/cConnectionRepository.h

@@ -55,8 +55,8 @@ class SocketStream;
  */
 class EXPCL_DIRECT CConnectionRepository {
 PUBLISHED:
-  CConnectionRepository(bool has_owner_view = false,
-                        bool threaded_net = false);
+  explicit CConnectionRepository(bool has_owner_view = false,
+                                 bool threaded_net = false);
   ~CConnectionRepository();
 
 /*

+ 21 - 0
direct/src/filter/CommonFilters.py

@@ -536,3 +536,24 @@ class CommonFilters:
             del self.configuration["GammaAdjust"]
             return self.reconfigure((old_gamma != 1.0), "GammaAdjust")
         return True
+
+    #snake_case alias:
+    del_cartoon_ink = delCartoonInk
+    set_half_pixel_shift = setHalfPixelShift
+    del_half_pixel_shift = delHalfPixelShift
+    set_inverted = setInverted
+    del_inverted = delInverted
+    del_view_glow = delViewGlow
+    set_volumetric_lighting = setVolumetricLighting
+    del_gamma_adjust = delGammaAdjust
+    set_bloom = setBloom
+    set_view_glow = setViewGlow
+    set_ambient_occlusion = setAmbientOcclusion
+    set_cartoon_ink = setCartoonInk
+    del_bloom = delBloom
+    del_ambient_occlusion = delAmbientOcclusion
+    load_shader = loadShader
+    set_blur_sharpen = setBlurSharpen
+    del_blur_sharpen = delBlurSharpen
+    del_volumetric_lighting = delVolumetricLighting
+    set_gamma_adjust = setGammaAdjust

+ 12 - 0
direct/src/filter/FilterManager.py

@@ -349,3 +349,15 @@ class FilterManager(DirectObject):
         self.nextsort = self.win.getSort() - 1000
         self.basex = 0
         self.basey = 0
+
+    #snake_case alias:
+    is_fullscreen = isFullscreen
+    resize_buffers = resizeBuffers
+    set_stacked_clears = setStackedClears
+    render_scene_into = renderSceneInto
+    get_scaled_size = getScaledSize
+    render_quad_into = renderQuadInto
+    get_clears = getClears
+    set_clears = setClears
+    create_buffer = createBuffer
+    window_event = windowEvent

+ 3 - 2
direct/src/filter/filter-bloomx.sha

@@ -12,8 +12,9 @@ void vshader(float4 vtx_position : POSITION,
   l_position=mul(mat_modelproj, vtx_position);
   float2 c=(vtx_position.xz * texpad_src.xy) + texpad_src.xy;
   float offset = texpix_src.x;
-  l_texcoord0 = float4(c.x-offset* -4, c.x-offset* -3, c.x-offset* -2, c.y);
-  l_texcoord1 = float4(c.x-offset* -1, c.x-offset*  0, c.x-offset*  1, c.y);
+  float pad = texpad_src.x * 2;
+  l_texcoord0 = float4(min(c.x-offset* -4, pad), min(c.x-offset* -3, pad), min(c.x-offset* -2, pad), c.y);
+  l_texcoord1 = float4(min(c.x-offset* -1, pad), c.x-offset*  0, c.x-offset*  1, c.y);
   l_texcoord2 = float4(c.x-offset*  2, c.x-offset*  3, c.x-offset*  4, c.y);
 }
 

+ 3 - 2
direct/src/filter/filter-bloomy.sha

@@ -12,8 +12,9 @@ void vshader(float4 vtx_position : POSITION,
   l_position=mul(mat_modelproj, vtx_position);
   float2 c=(vtx_position.xz * texpad_src.xy) + texpad_src.xy;
   float offset = texpix_src.y;
-  l_texcoord0 = float4(c.y-offset* -4, c.y-offset* -3, c.y-offset* -2, c.x);
-  l_texcoord1 = float4(c.y-offset* -1, c.y-offset*  0, c.y-offset*  1, c.x);
+  float pad = texpad_src.y * 2;
+  l_texcoord0 = float4(min(c.y-offset* -4, pad), min(c.y-offset* -3, pad), min(c.y-offset* -2, pad), c.x);
+  l_texcoord1 = float4(min(c.y-offset* -1, pad), c.y-offset*  0, c.y-offset*  1, c.x);
   l_texcoord2 = float4(c.y-offset*  2, c.y-offset*  3, c.y-offset*  4, c.x);
 }
 

+ 6 - 4
direct/src/filter/filter-blurx.sha

@@ -2,7 +2,7 @@
 //
 //Cg profile arbvp1 arbfp1
 
-void vshader(float4 vtx_position : POSITION, 
+void vshader(float4 vtx_position : POSITION,
              float2 vtx_texcoord0 : TEXCOORD0,
              out float4 l_position : POSITION,
       	     out float2 l_texcoord0 : TEXCOORD0,
@@ -17,16 +17,18 @@ void vshader(float4 vtx_position : POSITION,
 void fshader(float2 l_texcoord0 : TEXCOORD0,
              out float4 o_color : COLOR,
              uniform float2 texpix_src,
+             uniform float4 texpad_src,
              uniform sampler2D k_src : TEXUNIT0)
 {
+  float pad = texpad_src.x * 2;
   float3 offset = float3(1.0*texpix_src.x, 2.0*texpix_src.x, 3.0*texpix_src.x);
   o_color  = tex2D(k_src, l_texcoord0);
   o_color += tex2D(k_src, float2(l_texcoord0.x - offset.z, l_texcoord0.y));
   o_color += tex2D(k_src, float2(l_texcoord0.x - offset.y, l_texcoord0.y));
   o_color += tex2D(k_src, float2(l_texcoord0.x - offset.x, l_texcoord0.y));
-  o_color += tex2D(k_src, float2(l_texcoord0.x + offset.x, l_texcoord0.y));
-  o_color += tex2D(k_src, float2(l_texcoord0.x + offset.y, l_texcoord0.y));
-  o_color += tex2D(k_src, float2(l_texcoord0.x + offset.z, l_texcoord0.y));
+  o_color += tex2D(k_src, float2(min(l_texcoord0.x + offset.x, pad), l_texcoord0.y));
+  o_color += tex2D(k_src, float2(min(l_texcoord0.x + offset.y, pad), l_texcoord0.y));
+  o_color += tex2D(k_src, float2(min(l_texcoord0.x + offset.z, pad), l_texcoord0.y));
   o_color /= 7;
   o_color.w = 1;
 }

+ 6 - 4
direct/src/filter/filter-blury.sha

@@ -2,7 +2,7 @@
 //
 //Cg profile arbvp1 arbfp1
 
-void vshader(float4 vtx_position : POSITION, 
+void vshader(float4 vtx_position : POSITION,
              float2 vtx_texcoord0 : TEXCOORD0,
              out float4 l_position : POSITION,
       	     out float2 l_texcoord0 : TEXCOORD0,
@@ -17,16 +17,18 @@ void vshader(float4 vtx_position : POSITION,
 void fshader(float2 l_texcoord0 : TEXCOORD0,
              out float4 o_color : COLOR,
              uniform float2 texpix_src,
+             uniform float4 texpad_src,
              uniform sampler2D k_src : TEXUNIT0)
 {
+  float pad = texpad_src.y * 2;
   float3 offset = float3(1.0*texpix_src.y, 2.0*texpix_src.y, 3.0*texpix_src.y);
   o_color  = tex2D(k_src, l_texcoord0);
   o_color += tex2D(k_src, float2(l_texcoord0.x, l_texcoord0.y - offset.z));
   o_color += tex2D(k_src, float2(l_texcoord0.x, l_texcoord0.y - offset.y));
   o_color += tex2D(k_src, float2(l_texcoord0.x, l_texcoord0.y - offset.x));
-  o_color += tex2D(k_src, float2(l_texcoord0.x, l_texcoord0.y + offset.x));
-  o_color += tex2D(k_src, float2(l_texcoord0.x, l_texcoord0.y + offset.y));
-  o_color += tex2D(k_src, float2(l_texcoord0.x, l_texcoord0.y + offset.z));
+  o_color += tex2D(k_src, float2(l_texcoord0.x, min(l_texcoord0.y + offset.x, pad)));
+  o_color += tex2D(k_src, float2(l_texcoord0.x, min(l_texcoord0.y + offset.y, pad)));
+  o_color += tex2D(k_src, float2(l_texcoord0.x, min(l_texcoord0.y + offset.z, pad)));
   o_color /= 7;
   o_color.w = 1;
 }

+ 1 - 1
direct/src/interval/ActorInterval.py

@@ -83,7 +83,7 @@ class ActorInterval(Interval.Interval):
                 if startTime == None:
                     startTime = float(self.startFrame) / float(self.frameRate)
                 endTime = startTime + duration
-                self.endFrame = duration * self.frameRate
+                self.endFrame = endTime * self.frameRate
             else:
                 # No end frame specified.  Choose the maximum of all
                 # of the controls' numbers of frames.

+ 3 - 1
direct/src/interval/Interval.py

@@ -116,8 +116,9 @@ class Interval(DirectObject):
         return self.currT
 
     def start(self, startT = 0.0, endT = -1.0, playRate = 1.0):
+        """ Starts the interval.  Returns an awaitable. """
         self.setupPlay(startT, endT, playRate, 0)
-        self.__spawnTask()
+        return self.__spawnTask()
 
     def loop(self, startT = 0.0, endT = -1.0, playRate = 1.0):
         self.setupPlay(startT, endT, playRate, 1)
@@ -427,6 +428,7 @@ class Interval(DirectObject):
         task = Task(self.__playTask)
         task.interval = self
         taskMgr.add(task, taskName)
+        return task
 
     def __removeTask(self):
         # Kill old task(s), including those from a similarly-named but

+ 3 - 3
direct/src/interval/cConstrainHprInterval.h

@@ -26,9 +26,9 @@
  */
 class EXPCL_DIRECT CConstrainHprInterval : public CConstraintInterval {
 PUBLISHED:
-  CConstrainHprInterval(const string &name, double duration,
-                        const NodePath &node, const NodePath &target,
-                        bool wrt, const LVecBase3 hprOffset=LVector3::zero());
+  explicit CConstrainHprInterval(const string &name, double duration,
+                                 const NodePath &node, const NodePath &target,
+                                 bool wrt, const LVecBase3 hprOffset=LVector3::zero());
 
   INLINE const NodePath &get_node() const;
   INLINE const NodePath &get_target() const;

+ 4 - 4
direct/src/interval/cConstrainPosHprInterval.h

@@ -26,10 +26,10 @@
  */
 class EXPCL_DIRECT CConstrainPosHprInterval : public CConstraintInterval {
 PUBLISHED:
-  CConstrainPosHprInterval(const string &name, double duration,
-                           const NodePath &node, const NodePath &target,
-                           bool wrt, const LVecBase3 posOffset=LVector3::zero(),
-                           const LVecBase3 hprOffset=LVector3::zero());
+  explicit CConstrainPosHprInterval(const string &name, double duration,
+                                    const NodePath &node, const NodePath &target,
+                                    bool wrt, const LVecBase3 posOffset=LVector3::zero(),
+                                    const LVecBase3 hprOffset=LVector3::zero());
 
   INLINE const NodePath &get_node() const;
   INLINE const NodePath &get_target() const;

+ 3 - 3
direct/src/interval/cConstrainPosInterval.h

@@ -25,9 +25,9 @@
  */
 class EXPCL_DIRECT CConstrainPosInterval : public CConstraintInterval {
 PUBLISHED:
-  CConstrainPosInterval(const string &name, double duration,
-                        const NodePath &node, const NodePath &target,
-                        bool wrt, const LVecBase3 posOffset=LVector3::zero());
+  explicit CConstrainPosInterval(const string &name, double duration,
+                                 const NodePath &node, const NodePath &target,
+                                 bool wrt, const LVecBase3 posOffset=LVector3::zero());
 
   INLINE const NodePath &get_node() const;
   INLINE const NodePath &get_target() const;

+ 3 - 3
direct/src/interval/cConstrainTransformInterval.h

@@ -24,9 +24,9 @@
  */
 class EXPCL_DIRECT CConstrainTransformInterval : public CConstraintInterval {
 PUBLISHED:
-  CConstrainTransformInterval(const string &name, double duration,
-                              const NodePath &node, const NodePath &target,
-                              bool wrt);
+  explicit CConstrainTransformInterval(const string &name, double duration,
+                                       const NodePath &node,
+                                       const NodePath &target, bool wrt);
 
   INLINE const NodePath &get_node() const;
   INLINE const NodePath &get_target() const;

+ 2 - 2
direct/src/interval/cLerpAnimEffectInterval.h

@@ -31,8 +31,8 @@
  */
 class EXPCL_DIRECT CLerpAnimEffectInterval : public CLerpInterval {
 PUBLISHED:
-  INLINE CLerpAnimEffectInterval(const string &name, double duration,
-                                 BlendType blend_type);
+  INLINE explicit CLerpAnimEffectInterval(const string &name, double duration,
+                                          BlendType blend_type);
 
   INLINE void add_control(AnimControl *control, const string &name,
                           float begin_effect, float end_effect);

+ 4 - 4
direct/src/interval/cLerpNodePathInterval.h

@@ -25,10 +25,10 @@
  */
 class EXPCL_DIRECT CLerpNodePathInterval : public CLerpInterval {
 PUBLISHED:
-  CLerpNodePathInterval(const string &name, double duration,
-                        BlendType blend_type, bool bake_in_start,
-                        bool fluid,
-                        const NodePath &node, const NodePath &other);
+  explicit CLerpNodePathInterval(const string &name, double duration,
+                                 BlendType blend_type, bool bake_in_start,
+                                 bool fluid,
+                                 const NodePath &node, const NodePath &other);
 
   INLINE const NodePath &get_node() const;
   INLINE const NodePath &get_other() const;

+ 1 - 1
direct/src/interval/cMetaInterval.h

@@ -31,7 +31,7 @@
  */
 class EXPCL_DIRECT CMetaInterval : public CInterval {
 PUBLISHED:
-  CMetaInterval(const string &name);
+  explicit CMetaInterval(const string &name);
   virtual ~CMetaInterval();
 
   enum RelativeStart {

+ 1 - 1
direct/src/interval/hideInterval.h

@@ -23,7 +23,7 @@
  */
 class EXPCL_DIRECT HideInterval : public CInterval {
 PUBLISHED:
-  HideInterval(const NodePath &node, const string &name = string());
+  explicit HideInterval(const NodePath &node, const string &name = string());
 
   virtual void priv_instant();
   virtual void priv_reverse_instant();

+ 1 - 1
direct/src/interval/showInterval.h

@@ -23,7 +23,7 @@
  */
 class EXPCL_DIRECT ShowInterval : public CInterval {
 PUBLISHED:
-  ShowInterval(const NodePath &node, const string &name = string());
+  explicit ShowInterval(const NodePath &node, const string &name = string());
 
   virtual void priv_instant();
   virtual void priv_reverse_instant();

+ 1 - 1
direct/src/interval/waitInterval.h

@@ -23,7 +23,7 @@
  */
 class EXPCL_DIRECT WaitInterval : public CInterval {
 PUBLISHED:
-  INLINE WaitInterval(double duration);
+  INLINE explicit WaitInterval(double duration);
 
   virtual void priv_step(double t);
 

+ 24 - 0
direct/src/showbase/Audio3DManager.py

@@ -289,3 +289,27 @@ class Audio3DManager:
             for sound in self.sound_dict[object]:
                 self.detachSound(sound)
 
+    #snake_case alias:
+    get_doppler_factor = getDopplerFactor
+    set_listener_velocity_auto = setListenerVelocityAuto
+    attach_listener = attachListener
+    set_distance_factor = setDistanceFactor
+    attach_sound_to_object = attachSoundToObject
+    get_drop_off_factor = getDropOffFactor
+    set_doppler_factor = setDopplerFactor
+    get_sounds_on_object = getSoundsOnObject
+    set_sound_velocity_auto = setSoundVelocityAuto
+    get_sound_max_distance = getSoundMaxDistance
+    load_sfx = loadSfx
+    get_distance_factor = getDistanceFactor
+    set_listener_velocity = setListenerVelocity
+    set_sound_max_distance = setSoundMaxDistance
+    get_sound_velocity = getSoundVelocity
+    get_listener_velocity = getListenerVelocity
+    set_sound_velocity = setSoundVelocity
+    set_sound_min_distance = setSoundMinDistance
+    get_sound_min_distance = getSoundMinDistance
+    detach_listener = detachListener
+    set_drop_off_factor = setDropOffFactor
+    detach_sound = detachSound
+

+ 12 - 0
direct/src/showbase/DirectObject.py

@@ -101,3 +101,15 @@ class DirectObject:
             func = choice(getRepository()._crashOnProactiveLeakDetect,
                           self.notify.error, self.notify.warning)
             func('destroyed %s instance is still %s%s' % (self.__class__.__name__, estr, tstr))
+
+    #snake_case alias:
+    add_task = addTask
+    do_method_later = doMethodLater
+    detect_leaks = detectLeaks
+    accept_once = acceptOnce
+    ignore_all = ignoreAll
+    get_all_accepting = getAllAccepting
+    is_ignoring = isIgnoring
+    remove_all_tasks = removeAllTasks
+    remove_task = removeTask
+    is_accepting = isAccepting

+ 128 - 53
direct/src/showbase/Loader.py

@@ -21,31 +21,105 @@ class Loader(DirectObject):
     loaderIndex = 0
 
     class Callback:
-        def __init__(self, numObjects, gotList, callback, extraArgs):
+        """Returned by loadModel when used asynchronously.  This class is
+        modelled after Future, and can be awaited."""
+
+        # This indicates that this class behaves like a Future.
+        _asyncio_future_blocking = False
+
+        def __init__(self, loader, numObjects, gotList, callback, extraArgs):
+            self._loader = loader
             self.objects = [None] * numObjects
             self.gotList = gotList
             self.callback = callback
             self.extraArgs = extraArgs
-            self.numRemaining = numObjects
-            self.cancelled = False
             self.requests = set()
+            self.requestList = []
 
         def gotObject(self, index, object):
             self.objects[index] = object
-            self.numRemaining -= 1
 
-            if self.numRemaining == 0:
-                if self.gotList:
-                    self.callback(self.objects, *self.extraArgs)
-                else:
-                    self.callback(*(self.objects + self.extraArgs))
+            if not self.requests:
+                self._loader = None
+                if self.callback:
+                    if self.gotList:
+                        self.callback(self.objects, *self.extraArgs)
+                    else:
+                        self.callback(*(self.objects + self.extraArgs))
+
+        def cancel(self):
+            "Cancels the request.  Callback won't be called."
+            if self._loader:
+                for request in self.requests:
+                    self._loader.loader.remove(request)
+                    del self._loader._requests[request]
+                self._loader = None
+                self.requests = None
+                self.requestList = None
+
+        def cancelled(self):
+            "Returns true if the request was cancelled."
+            return self.requestList is None
+
+        def done(self):
+            "Returns true if all the requests were finished or cancelled."
+            return not self.requests
+
+        def result(self):
+            "Returns the results, suspending the thread to wait if necessary."
+            for r in list(self.requests):
+                r.wait()
+            if self.gotList:
+                return self.objects
+            else:
+                return self.objects[0]
+
+        def exception(self):
+            assert self.done() and not self.cancelled()
+            return None
+
+        def __await__(self):
+            """ Returns a generator that raises StopIteration when the loading
+            is complete.  This allows this class to be used with 'await'."""
+            if self.requests:
+                self._asyncio_future_blocking = True
+                yield self
+
+            # This should be a simple return, but older versions of Python
+            # don't allow return statements with arguments.
+            result = self.result()
+            exc = StopIteration(result)
+            exc.value = result
+            raise exc
+
+        def __aiter__(self):
+            """ This allows using `async for` to iterate asynchronously over
+            the results of this class.  It does guarantee to return the
+            results in order, though, even though they may not be loaded in
+            that order. """
+            requestList = self.requestList
+            assert requestList is not None, "Request was cancelled."
+
+            class AsyncIter:
+                index = 0
+                def __anext__(self):
+                    if self.index < len(requestList):
+                        i = self.index
+                        self.index = i + 1
+                        return requestList[i]
+                    else:
+                        raise StopAsyncIteration
+
+            iter = AsyncIter()
+            iter.objects = self.objects
+            return iter
 
     # special methods
     def __init__(self, base):
         self.base = base
         self.loader = PandaLoader.getGlobalPtr()
 
-        self.__requests = {}
+        self._requests = {}
 
         self.hook = "async_loader_%s" % (Loader.loaderIndex)
         Loader.loaderIndex += 1
@@ -60,7 +134,8 @@ class Loader(DirectObject):
     # model loading funcs
     def loadModel(self, modelPath, loaderOptions = None, noCache = None,
                   allowInstance = False, okMissing = None,
-                  callback = None, extraArgs = [], priority = None):
+                  callback = None, extraArgs = [], priority = None,
+                  blocking = None):
         """
         Attempts to load a model or models from one or more relative
         pathnames.  If the input modelPath is a string (a single model
@@ -97,10 +172,10 @@ class Loader(DirectObject):
         If callback is not None, then the model load will be performed
         asynchronously.  In this case, loadModel() will initiate a
         background load and return immediately.  The return value will
-        be an object that may later be passed to
-        loader.cancelRequest() to cancel the asynchronous request.  At
-        some later point, when the requested model(s) have finished
-        loading, the callback function will be invoked with the n
+        be an object that can be used to check the status, cancel the
+        request, or use it in an `await` expression.  Unless callback
+        is the special value True, when the requested model(s) have
+        finished loading, it will be invoked with the n
         loaded models passed as its parameter list.  It is possible
         that the callback will be invoked immediately, even before
         loadModel() returns.  If you use callback, you may also
@@ -152,7 +227,10 @@ class Loader(DirectObject):
             modelList = modelPath
             gotList = True
 
-        if callback is None:
+        if blocking is None:
+            blocking = callback is None
+
+        if blocking:
             # We got no callback, so it's a synchronous load.
 
             result = []
@@ -180,7 +258,7 @@ class Loader(DirectObject):
             # requested models have been loaded, we'll invoke the
             # callback (passing it the models on the parameter list).
 
-            cb = Loader.Callback(len(modelList), gotList, callback, extraArgs)
+            cb = Loader.Callback(self, len(modelList), gotList, callback, extraArgs)
             i = 0
             for modelPath in modelList:
                 request = self.loader.makeAsyncRequest(Filename(modelPath), loaderOptions)
@@ -189,26 +267,26 @@ class Loader(DirectObject):
                 request.setDoneEvent(self.hook)
                 self.loader.loadAsync(request)
                 cb.requests.add(request)
-                self.__requests[request] = (cb, i)
+                cb.requestList.append(request)
+                self._requests[request] = (cb, i)
                 i += 1
             return cb
 
     def cancelRequest(self, cb):
         """Cancels an aysynchronous loading or flatten request issued
         earlier.  The callback associated with the request will not be
-        called after cancelRequest() has been performed. """
+        called after cancelRequest() has been performed.
+
+        This is now deprecated: call cb.cancel() instead. """
 
-        if not cb.cancelled:
-            cb.cancelled = True
-            for request in cb.requests:
-                self.loader.remove(request)
-                del self.__requests[request]
-            cb.requests = None
+        cb.cancel()
 
     def isRequestPending(self, cb):
         """ Returns true if an asynchronous loading or flatten request
         issued earlier is still pending, or false if it has completed or
-        been cancelled. """
+        been cancelled.
+
+        This is now deprecated: call cb.done() instead. """
 
         return bool(cb.requests)
 
@@ -290,7 +368,8 @@ class Loader(DirectObject):
         ModelPool.releaseModel(modelNode)
 
     def saveModel(self, modelPath, node, loaderOptions = None,
-                  callback = None, extraArgs = [], priority = None):
+                  callback = None, extraArgs = [], priority = None,
+                  blocking = None):
         """ Saves the model (a NodePath or PandaNode) to the indicated
         filename path.  Returns true on success, false on failure.  If
         a callback is used, the model is saved asynchronously, and the
@@ -325,7 +404,10 @@ class Loader(DirectObject):
         # From here on, we deal with a list of (filename, node) pairs.
         modelList = list(zip(modelList, nodeList))
 
-        if callback is None:
+        if blocking is None:
+            blocking = callback is None
+
+        if blocking:
             # We got no callback, so it's a synchronous save.
 
             result = []
@@ -344,7 +426,7 @@ class Loader(DirectObject):
             # requested models have been saved, we'll invoke the
             # callback (passing it the models on the parameter list).
 
-            cb = Loader.Callback(len(modelList), gotList, callback, extraArgs)
+            cb = Loader.Callback(self, len(modelList), gotList, callback, extraArgs)
             i = 0
             for modelPath, node in modelList:
                 request = self.loader.makeAsyncSaveRequest(Filename(modelPath), loaderOptions, node)
@@ -353,7 +435,8 @@ class Loader(DirectObject):
                 request.setDoneEvent(self.hook)
                 self.loader.saveAsync(request)
                 cb.requests.add(request)
-                self.__requests[request] = (cb, i)
+                cb.requestList.append(request)
+                self._requests[request] = (cb, i)
                 i += 1
             return cb
 
@@ -880,13 +963,14 @@ class Loader(DirectObject):
             # requested sounds have been loaded, we'll invoke the
             # callback (passing it the sounds on the parameter list).
 
-            cb = Loader.Callback(len(soundList), gotList, callback, extraArgs)
+            cb = Loader.Callback(self, len(soundList), gotList, callback, extraArgs)
             for i, soundPath in enumerate(soundList):
                 request = AudioLoadRequest(manager, soundPath, positional)
                 request.setDoneEvent(self.hook)
                 self.loader.loadAsync(request)
                 cb.requests.add(request)
-                self.__requests[request] = (cb, i)
+                cb.requestList.append(request)
+                self._requests[request] = (cb, i)
             return cb
 
     def unloadSfx(self, sfx):
@@ -944,14 +1028,15 @@ class Loader(DirectObject):
             callback = self.__asyncFlattenDone
             gotList = True
 
-        cb = Loader.Callback(len(modelList), gotList, callback, extraArgs)
+        cb = Loader.Callback(self, len(modelList), gotList, callback, extraArgs)
         i = 0
         for model in modelList:
             request = ModelFlattenRequest(model.node())
             request.setDoneEvent(self.hook)
             self.loader.loadAsync(request)
             cb.requests.add(request)
-            self.__requests[request] = (cb, i)
+            cb.requestList.append(request)
+            self._requests[request] = (cb, i)
             i += 1
         return cb
 
@@ -980,36 +1065,26 @@ class Loader(DirectObject):
         of loaded objects, and call the appropriate callback when it's
         time."""
 
-        if request not in self.__requests:
+        if request not in self._requests:
             return
 
-        cb, i = self.__requests[request]
-        if cb.cancelled:
+        cb, i = self._requests[request]
+        if cb.cancelled() or request.cancelled():
             # Shouldn't be here.
-            del self.__requests[request]
+            del self._requests[request]
             return
 
         cb.requests.discard(request)
         if not cb.requests:
-            del self.__requests[request]
-
-        object = None
-        if hasattr(request, "getModel"):
-            node = request.getModel()
-            if node is not None:
-                object = NodePath(node)
-
-        elif hasattr(request, "getSound"):
-            object = request.getSound()
+            del self._requests[request]
 
-        elif hasattr(request, "getSuccess"):
-            object = request.getSuccess()
+        result = request.result()
+        if isinstance(result, PandaNode):
+            result = NodePath(result)
 
-        cb.gotObject(i, object)
+        cb.gotObject(i, result)
 
     load_model = loadModel
-    cancel_request = cancelRequest
-    is_request_pending = isRequestPending
     unload_model = unloadModel
     save_model = saveModel
     load_font = loadFont

+ 24 - 1
direct/src/showbase/Messenger.py

@@ -109,6 +109,12 @@ class Messenger:
             if record[0] <= 0:
                 del self._id2object[id]
 
+    def future(self, event):
+        """ Returns a future that is triggered by the given event name.  This
+        will function only once. """
+
+        return eventMgr.eventHandler.get_future(event)
+
     def accept(self, event, object, method, extraArgs=[], persistent=1):
         """ accept(self, string, DirectObject, Function, List, Boolean)
 
@@ -409,10 +415,14 @@ class Messenger:
                 # Release the lock temporarily while we call the method.
                 self.lock.release()
                 try:
-                    method (*(extraArgs + sentArgs))
+                    result = method (*(extraArgs + sentArgs))
                 finally:
                     self.lock.acquire()
 
+                if hasattr(result, 'cr_await'):
+                    # It's a coroutine, so schedule it with the task manager.
+                    taskMgr.add(result)
+
     def clear(self):
         """
         Start fresh with a clear dict
@@ -635,3 +645,16 @@ class Messenger:
         str = str + '='*50 + '\n'
         return str
 
+    #snake_case alias:
+    get_events = getEvents
+    is_ignoring = isIgnoring
+    who_accepts = whoAccepts
+    find_all = findAll
+    replace_method = replaceMethod
+    ignore_all = ignoreAll
+    is_accepting = isAccepting
+    is_empty = isEmpty
+    detailed_repr = detailedRepr
+    get_all_accepting = getAllAccepting
+    toggle_verbose = toggleVerbose
+

+ 42 - 4
direct/src/showbase/MirrorDemo.py

@@ -24,7 +24,8 @@ __all__ = ['setupMirror', 'showFrustum']
 from panda3d.core import *
 from direct.task import Task
 
-def setupMirror(name, width, height, rootCamera = None):
+def setupMirror(name, width, height, rootCamera = None,
+                bufferSize = 256, clearColor = None):
     # The return value is a NodePath that contains a rectangle that
     # reflects render.  You can reparent, reposition, and rotate it
     # anywhere you like.
@@ -49,9 +50,12 @@ def setupMirror(name, width, height, rootCamera = None):
     # Now create an offscreen buffer for rendering the mirror's point
     # of view.  The parameters here control the resolution of the
     # texture.
-    buffer = base.win.makeTextureBuffer(name, 256, 256)
-    #buffer.setClearColor(base.win.getClearColor())
-    buffer.setClearColor(VBase4(0, 0, 1, 1))
+    buffer = base.win.makeTextureBuffer(name, bufferSize, bufferSize)
+    if clearColor is None:
+        buffer.setClearColor(base.win.getClearColor())
+        #buffer.setClearColor(VBase4(0, 0, 1, 1))
+    else:
+        buffer.setClearColor(clearColor)
 
     # Set up a display region on this buffer, and create a camera.
     dr = buffer.makeDisplayRegion()
@@ -85,6 +89,10 @@ def setupMirror(name, width, height, rootCamera = None):
         # Set the camera to the mirror-image position of the main camera.
         cameraNP.setMat(rootCamera.getMat(planeNP) * plane.getReflectionMat())
 
+        # Set the cameras roll to the roll of the mirror. Otherwise
+        # mirrored objects will be moved unexpectedly
+        cameraNP.setR(planeNP.getR()-180)
+
         # And reset the frustum to exactly frame the mirror's corners.
         # This is a minor detail, but it helps to provide a realistic
         # reflection and keep the subject centered.
@@ -92,6 +100,18 @@ def setupMirror(name, width, height, rootCamera = None):
         ur = cameraNP.getRelativePoint(card, Point3(width / 2.0, 0, height / 2.0))
         ll = cameraNP.getRelativePoint(card, Point3(-width / 2.0, 0, -height / 2.0))
         lr = cameraNP.getRelativePoint(card, Point3(width / 2.0, 0, -height / 2.0))
+
+        # get the distance from the mirrors camera to the mirror plane
+        camvec = planeNP.getPos() - cameraNP.getPos()
+        camdist = camvec.length()
+
+        # set the discance on the mirrors corners so it will keep correct
+        # sizes of the mirrored objects
+        ul.setY(camdist)
+        ur.setY(camdist)
+        ll.setY(camdist)
+        lr.setY(camdist)
+
         lens.setFrustumFromCorners(ul, ur, ll, lr, Lens.FCCameraPlane | Lens.FCOffAxis | Lens.FCAspectRatio)
 
         return Task.cont
@@ -116,3 +136,21 @@ def showFrustum(np):
     geomNode.addGeom(lens.makeGeometry())
     cameraNP.attachNewNode(geomNode)
 
+if __name__ == "__main__":
+    from direct.showbase.ShowBase import ShowBase
+    base = ShowBase()
+
+    panda = loader.loadModel("panda")
+    panda.setH(180)
+    panda.setPos(0, 10, -2.5)
+    panda.setScale(0.5)
+    panda.reparentTo(render)
+
+    myMirror = setupMirror("mirror", 10, 10, bufferSize=1024, clearColor=(0, 0, 1, 1))
+    myMirror.setPos(0, 15, 2.5)
+    myMirror.setH(180)
+
+    # Uncomment this to show the frustum of the camera in the mirror
+    #showFrustum(render)
+
+    base.run()

+ 53 - 45
direct/src/showbase/ShowBase.py

@@ -968,7 +968,9 @@ class ShowBase(DirectObject.DirectObject):
             if isinstance(self.win, GraphicsWindow):
                 self.setupMouse(self.win)
             self.makeCamera2d(self.win)
-            self.makeCamera2dp(self.win)
+
+            if self.wantRender2dp:
+                self.makeCamera2dp(self.win)
 
             if oldLens != None:
                 # Restore the previous lens properties.
@@ -1258,9 +1260,9 @@ class ShowBase(DirectObject.DirectObject):
         if win == None:
             win = self.win
 
-        if win != None and win.hasSize() and win.getSbsLeftYSize() != 0:
+        if win != None and win.getSideBySideStereo() and \
+                win.hasSize() and win.getSbsLeftYSize() != 0:
             aspectRatio = float(win.getSbsLeftXSize()) / float(win.getSbsLeftYSize())
-
         else:
             if win == None or not hasattr(win, "getRequestedProperties"):
                 props = WindowProperties.getDefault()
@@ -1295,8 +1297,7 @@ class ShowBase(DirectObject.DirectObject):
                 if not props.hasSize():
                     props = WindowProperties.getDefault()
 
-            if props.hasSize():
-                return props.getXSize(), props.getYSize()
+            return props.getXSize(), props.getYSize()
 
     def makeCamera(self, win, sort = 0, scene = None,
                    displayRegion = (0, 1, 0, 1), stereo = None,
@@ -1560,9 +1561,11 @@ class ShowBase(DirectObject.DirectObject):
 
         # Tell the gui system about our new mouse watcher.
         self.aspect2d.node().setMouseWatcher(mw.node())
-        self.aspect2dp.node().setMouseWatcher(mw.node())
         self.pixel2d.node().setMouseWatcher(mw.node())
-        self.pixel2dp.node().setMouseWatcher(mw.node())
+        if self.wantRender2dp:
+            self.aspect2dp.node().setMouseWatcher(mw.node())
+            self.pixel2dp.node().setMouseWatcher(mw.node())
+
         mw.node().addRegion(PGMouseWatcherBackground())
 
         return self.buttonThrowers[0]
@@ -2723,13 +2726,15 @@ class ShowBase(DirectObject.DirectObject):
             # changed and update the camera lenses and aspect2d parameters
             self.adjustWindowAspectRatio(self.getAspectRatio())
 
-            # Temporary hasattr for old Pandas
-            if not hasattr(win, 'getSbsLeftXSize'):
-                self.pixel2d.setScale(2.0 / win.getXSize(), 1.0, 2.0 / win.getYSize())
-                self.pixel2dp.setScale(2.0 / win.getXSize(), 1.0, 2.0 / win.getYSize())
-            else:
+            if win.getSideBySideStereo() and win.hasSize() and win.getSbsLeftYSize() != 0:
                 self.pixel2d.setScale(2.0 / win.getSbsLeftXSize(), 1.0, 2.0 / win.getSbsLeftYSize())
                 self.pixel2dp.setScale(2.0 / win.getSbsLeftXSize(), 1.0, 2.0 / win.getSbsLeftYSize())
+            else:
+                xsize, ysize = self.getSize()
+                if xsize > 0 and ysize > 0:
+                    self.pixel2d.setScale(2.0 / xsize, 1.0, 2.0 / ysize)
+                    if self.wantRender2dp:
+                        self.pixel2dp.setScale(2.0 / xsize, 1.0, 2.0 / ysize)
 
     def adjustWindowAspectRatio(self, aspectRatio):
         """ This function is normally called internally by
@@ -2753,11 +2758,12 @@ class ShowBase(DirectObject.DirectObject):
                 self.a2dLeft = -1
                 self.a2dRight = 1.0
                 # Don't forget 2dp
-                self.aspect2dp.setScale(1.0, aspectRatio, aspectRatio)
-                self.a2dpTop = 1.0 / aspectRatio
-                self.a2dpBottom = - 1.0 / aspectRatio
-                self.a2dpLeft = -1
-                self.a2dpRight = 1.0
+                if self.wantRender2dp:
+                    self.aspect2dp.setScale(1.0, aspectRatio, aspectRatio)
+                    self.a2dpTop = 1.0 / aspectRatio
+                    self.a2dpBottom = - 1.0 / aspectRatio
+                    self.a2dpLeft = -1
+                    self.a2dpRight = 1.0
 
             else:
                 # If the window is WIDE, lets expand the left and right
@@ -2767,41 +2773,43 @@ class ShowBase(DirectObject.DirectObject):
                 self.a2dLeft = -aspectRatio
                 self.a2dRight = aspectRatio
                 # Don't forget 2dp
-                self.aspect2dp.setScale(1.0 / aspectRatio, 1.0, 1.0)
-                self.a2dpTop = 1.0
-                self.a2dpBottom = -1.0
-                self.a2dpLeft = -aspectRatio
-                self.a2dpRight = aspectRatio
+                if self.wantRender2dp:
+                    self.aspect2dp.setScale(1.0 / aspectRatio, 1.0, 1.0)
+                    self.a2dpTop = 1.0
+                    self.a2dpBottom = -1.0
+                    self.a2dpLeft = -aspectRatio
+                    self.a2dpRight = aspectRatio
 
             # Reposition the aspect2d marker nodes
-            self.a2dTopCenter.setPos(0, self.a2dTop, self.a2dTop)
-            self.a2dBottomCenter.setPos(0, self.a2dBottom, self.a2dBottom)
+            self.a2dTopCenter.setPos(0, 0, self.a2dTop)
+            self.a2dTopCenterNs.setPos(0, 0, self.a2dTop)
+            self.a2dBottomCenter.setPos(0, 0, self.a2dBottom)
+            self.a2dBottomCenterNs.setPos(0, 0, self.a2dBottom)
             self.a2dLeftCenter.setPos(self.a2dLeft, 0, 0)
-            self.a2dRightCenter.setPos(self.a2dRight, 0, 0)
-            self.a2dTopLeft.setPos(self.a2dLeft, self.a2dTop, self.a2dTop)
-            self.a2dTopRight.setPos(self.a2dRight, self.a2dTop, self.a2dTop)
-            self.a2dBottomLeft.setPos(self.a2dLeft, self.a2dBottom, self.a2dBottom)
-            self.a2dBottomRight.setPos(self.a2dRight, self.a2dBottom, self.a2dBottom)
-
-            # Reposition the aspect2d marker nodes
-            self.a2dTopCenterNs.setPos(0, self.a2dTop, self.a2dTop)
-            self.a2dBottomCenterNs.setPos(0, self.a2dBottom, self.a2dBottom)
             self.a2dLeftCenterNs.setPos(self.a2dLeft, 0, 0)
+            self.a2dRightCenter.setPos(self.a2dRight, 0, 0)
             self.a2dRightCenterNs.setPos(self.a2dRight, 0, 0)
-            self.a2dTopLeftNs.setPos(self.a2dLeft, self.a2dTop, self.a2dTop)
-            self.a2dTopRightNs.setPos(self.a2dRight, self.a2dTop, self.a2dTop)
-            self.a2dBottomLeftNs.setPos(self.a2dLeft, self.a2dBottom, self.a2dBottom)
-            self.a2dBottomRightNs.setPos(self.a2dRight, self.a2dBottom, self.a2dBottom)
+
+            self.a2dTopLeft.setPos(self.a2dLeft, 0, self.a2dTop)
+            self.a2dTopLeftNs.setPos(self.a2dLeft, 0, self.a2dTop)
+            self.a2dTopRight.setPos(self.a2dRight, 0, self.a2dTop)
+            self.a2dTopRightNs.setPos(self.a2dRight, 0, self.a2dTop)
+            self.a2dBottomLeft.setPos(self.a2dLeft, 0, self.a2dBottom)
+            self.a2dBottomLeftNs.setPos(self.a2dLeft, 0, self.a2dBottom)
+            self.a2dBottomRight.setPos(self.a2dRight, 0, self.a2dBottom)
+            self.a2dBottomRightNs.setPos(self.a2dRight, 0, self.a2dBottom)
 
             # Reposition the aspect2dp marker nodes
-            self.a2dpTopCenter.setPos(0, self.a2dpTop, self.a2dpTop)
-            self.a2dpBottomCenter.setPos(0, self.a2dpBottom, self.a2dpBottom)
-            self.a2dpLeftCenter.setPos(self.a2dpLeft, 0, 0)
-            self.a2dpRightCenter.setPos(self.a2dpRight, 0, 0)
-            self.a2dpTopLeft.setPos(self.a2dpLeft, self.a2dpTop, self.a2dpTop)
-            self.a2dpTopRight.setPos(self.a2dpRight, self.a2dpTop, self.a2dpTop)
-            self.a2dpBottomLeft.setPos(self.a2dpLeft, self.a2dpBottom, self.a2dpBottom)
-            self.a2dpBottomRight.setPos(self.a2dpRight, self.a2dpBottom, self.a2dpBottom)
+            if self.wantRender2dp:
+                self.a2dpTopCenter.setPos(0, 0, self.a2dpTop)
+                self.a2dpBottomCenter.setPos(0, 0, self.a2dpBottom)
+                self.a2dpLeftCenter.setPos(self.a2dpLeft, 0, 0)
+                self.a2dpRightCenter.setPos(self.a2dpRight, 0, 0)
+
+                self.a2dpTopLeft.setPos(self.a2dpLeft, 0, self.a2dpTop)
+                self.a2dpTopRight.setPos(self.a2dpRight, 0, self.a2dpTop)
+                self.a2dpBottomLeft.setPos(self.a2dpLeft, 0, self.a2dpBottom)
+                self.a2dpBottomRight.setPos(self.a2dpRight, 0, self.a2dpBottom)
 
             # If anybody needs to update their GUI, put a callback on this event
             messenger.send("aspectRatioChanged")

+ 2 - 2
direct/src/showbase/Transitions.py

@@ -347,7 +347,7 @@ class Transitions:
                 frameColor = (0, 0, 0, 1),
                 borderWidth = (0, 0),
                 frameSize = (-1, 1, 0, 0.2),
-                pos = (0, 0, 0.8),
+                pos = (0, 0, 1.0),
                 image = barImage,
                 image_scale = (2.25,1,.5),
                 image_pos = (0,0,.1),
@@ -362,7 +362,7 @@ class Transitions:
                 frameColor = (0, 0, 0, 1),
                 borderWidth = (0, 0),
                 frameSize = (-1, 1, 0, 0.2),
-                pos = (0, 0, -1),
+                pos = (0, 0, -1.2),
                 image = barImage,
                 image_scale = (2.25,1,.5),
                 image_pos = (0,0,.1),

+ 35 - 11
direct/src/stdpy/thread.py

@@ -11,18 +11,32 @@ __all__ = [
     'interrupt_main',
     'exit', 'allocate_lock', 'get_ident',
     'stack_size',
+    'force_yield', 'consider_yield',
     'forceYield', 'considerYield',
+    'TIMEOUT_MAX'
     ]
 
 from panda3d import core
+import sys
+
+if sys.platform == "win32":
+    TIMEOUT_MAX = float(0xffffffff // 1000)
+else:
+    TIMEOUT_MAX = float(0x7fffffffffffffff // 1000000000)
 
 # These methods are defined in Panda, and are particularly useful if
 # you may be running in Panda's SIMPLE_THREADS compilation mode.
-forceYield = core.Thread.forceYield
-considerYield = core.Thread.considerYield
+force_yield = core.Thread.force_yield
+consider_yield = core.Thread.consider_yield
 
-class error(Exception):
-    pass
+forceYield = force_yield
+considerYield = consider_yield
+
+if sys.version_info >= (3, 3):
+    error = RuntimeError
+else:
+    class error(Exception):
+        pass
 
 class LockType:
     """ Implements a mutex lock.  Instead of directly subclassing
@@ -36,13 +50,18 @@ class LockType:
         self.__cvar = core.ConditionVar(self.__lock)
         self.__locked = False
 
-    def acquire(self, waitflag = 1):
+    def acquire(self, waitflag = 1, timeout = -1):
         self.__lock.acquire()
         try:
             if self.__locked and not waitflag:
                 return False
-            while self.__locked:
-                self.__cvar.wait()
+
+            if timeout >= 0:
+                while self.__locked:
+                    self.__cvar.wait(timeout)
+            else:
+                while self.__locked:
+                    self.__cvar.wait()
 
             self.__locked = True
             return True
@@ -202,12 +221,17 @@ def _get_thread_locals(thread, i):
 def _remove_thread_id(threadId):
     """ Removes the thread with the indicated ID from the thread list. """
 
+    # On interpreter shutdown, Python may set module globals to None.
+    if _threadsLock is None or _threads is None:
+        return
+
     _threadsLock.acquire()
     try:
-        thread, locals, wrapper = _threads[threadId]
-        assert thread.getPythonIndex() == threadId
-        del _threads[threadId]
-        thread.setPythonIndex(-1)
+        if threadId in _threads:
+            thread, locals, wrapper = _threads[threadId]
+            assert thread.getPythonIndex() == threadId
+            del _threads[threadId]
+            thread.setPythonIndex(-1)
 
     finally:
         _threadsLock.release()

+ 33 - 11
direct/src/stdpy/threading.py

@@ -35,11 +35,15 @@ __all__ = [
     'Event',
     'Timer',
     'local',
-    'current_thread', 'currentThread',
-    'enumerate', 'active_count', 'activeCount',
+    'current_thread',
+    'main_thread',
+    'enumerate', 'active_count',
     'settrace', 'setprofile', 'stack_size',
+    'TIMEOUT_MAX',
     ]
 
+TIMEOUT_MAX = _thread.TIMEOUT_MAX
+
 local = _thread._local
 _newname = _thread._newname
 
@@ -98,7 +102,15 @@ class Thread(ThreadBase):
         self.__dict__['daemon'] = current.daemon
         self.__dict__['name'] = name
 
-        self.__thread = core.PythonThread(self.run, None, name, name)
+        def call_run():
+            # As soon as the thread is done, break the circular reference.
+            try:
+                self.run()
+            finally:
+                self.__thread = None
+                _thread._remove_thread_id(self.ident)
+
+        self.__thread = core.PythonThread(call_run, None, name, name)
         threadId = _thread._add_thread(self.__thread, weakref.proxy(self))
         self.__dict__['ident'] = threadId
 
@@ -109,16 +121,17 @@ class Thread(ThreadBase):
             _thread._remove_thread_id(self.ident)
 
     def is_alive(self):
-        return self.__thread.isStarted()
+        thread = self.__thread
+        return thread is not None and thread.is_started()
 
-    def isAlive(self):
-        return self.__thread.isStarted()
+    isAlive = is_alive
 
     def start(self):
-        if self.__thread.isStarted():
+        thread = self.__thread
+        if thread is None or thread.is_started():
             raise RuntimeError
 
-        if not self.__thread.start(core.TPNormal, True):
+        if not thread.start(core.TPNormal, True):
             raise RuntimeError
 
     def run(self):
@@ -132,8 +145,12 @@ class Thread(ThreadBase):
     def join(self, timeout = None):
         # We don't support a timed join here, sorry.
         assert timeout is None
-        self.__thread.join()
-        self.__thread = None
+        thread = self.__thread
+        if thread is not None:
+            thread.join()
+            # Clear the circular reference.
+            self.__thread = None
+            _thread._remove_thread_id(self.ident)
 
     def setName(self, name):
         self.__dict__['name'] = name
@@ -379,6 +396,10 @@ def current_thread():
     t = core.Thread.getCurrentThread()
     return _thread._get_thread_wrapper(t, _create_thread_wrapper)
 
+def main_thread():
+    t = core.Thread.getMainThread()
+    return _thread._get_thread_wrapper(t, _create_thread_wrapper)
+
 currentThread = current_thread
 
 def enumerate():
@@ -386,7 +407,7 @@ def enumerate():
     _thread._threadsLock.acquire()
     try:
         for thread, locals, wrapper in list(_thread._threads.values()):
-            if wrapper and thread.isStarted():
+            if wrapper and wrapper.is_alive():
                 tlist.append(wrapper)
         return tlist
     finally:
@@ -394,6 +415,7 @@ def enumerate():
 
 def active_count():
     return len(enumerate())
+
 activeCount = active_count
 
 _settrace_func = None

+ 43 - 26
direct/src/stdpy/threading2.py

@@ -15,7 +15,7 @@ implementation. """
 
 import sys as _sys
 
-from direct.stdpy import thread
+from direct.stdpy import thread as _thread
 from direct.stdpy.thread import stack_size, _newname, _local as local
 from panda3d import core
 _sleep = core.Thread.sleep
@@ -23,16 +23,19 @@ _sleep = core.Thread.sleep
 from time import time as _time
 from traceback import format_exc as _format_exc
 
-# Rename some stuff so "from threading import *" is safe
-__all__ = ['activeCount', 'Condition', 'currentThread', 'enumerate', 'Event',
-           'Lock', 'RLock', 'Semaphore', 'BoundedSemaphore', 'Thread',
-           'Timer', 'setprofile', 'settrace', 'local', 'stack_size']
+__all__ = ['get_ident', 'active_count', 'Condition', 'current_thread',
+           'enumerate', 'main_thread', 'TIMEOUT_MAX',
+           'Event', 'Lock', 'RLock', 'Semaphore', 'BoundedSemaphore', 'Thread',
+           'Timer', 'ThreadError',
+           'setprofile', 'settrace', 'local', 'stack_size']
 
-_start_new_thread = thread.start_new_thread
-_allocate_lock = thread.allocate_lock
-_get_ident = thread.get_ident
-ThreadError = thread.error
-del thread
+# Rename some stuff so "from threading import *" is safe
+_start_new_thread = _thread.start_new_thread
+_allocate_lock = _thread.allocate_lock
+get_ident = _thread.get_ident
+ThreadError = _thread.error
+TIMEOUT_MAX = _thread.TIMEOUT_MAX
+del _thread
 
 
 # Debug support (adapted from ihooks.py).
@@ -446,7 +449,7 @@ class Thread(_Verbose):
         try:
             self.__started = True
             _active_limbo_lock.acquire()
-            _active[_get_ident()] = self
+            _active[get_ident()] = self
             del _limbo[self]
             _active_limbo_lock.release()
             if __debug__:
@@ -537,7 +540,7 @@ class Thread(_Verbose):
         _active_limbo_lock.acquire()
         try:
             try:
-                del _active[_get_ident()]
+                del _active[get_ident()]
             except KeyError:
                 if 'dummy_threading' not in _sys.modules:
                     raise
@@ -581,10 +584,12 @@ class Thread(_Verbose):
         assert self.__initialized, "Thread.__init__() not called"
         self.__name = str(name)
 
-    def isAlive(self):
+    def is_alive(self):
         assert self.__initialized, "Thread.__init__() not called"
         return self.__started and not self.__stopped
 
+    isAlive = is_alive
+
     def isDaemon(self):
         assert self.__initialized, "Thread.__init__() not called"
         return self.__daemonic
@@ -634,7 +639,7 @@ class _MainThread(Thread):
         Thread.__init__(self, name="MainThread")
         self._Thread__started = True
         _active_limbo_lock.acquire()
-        _active[_get_ident()] = self
+        _active[get_ident()] = self
         _active_limbo_lock.release()
 
     def _set_daemon(self):
@@ -653,12 +658,6 @@ class _MainThread(Thread):
             self._note("%s: exiting", self)
         self._Thread__delete()
 
-def _pickSomeNonDaemonThread():
-    for t in enumerate():
-        if not t.isDaemon() and t.isAlive():
-            return t
-    return None
-
 
 # Dummy thread class to represent threads not started here.
 # These aren't garbage collected when they die, nor can they be waited for.
@@ -680,7 +679,7 @@ class _DummyThread(Thread):
 
         self._Thread__started = True
         _active_limbo_lock.acquire()
-        _active[_get_ident()] = self
+        _active[get_ident()] = self
         _active_limbo_lock.release()
 
     def _set_daemon(self):
@@ -692,19 +691,23 @@ class _DummyThread(Thread):
 
 # Global API functions
 
-def currentThread():
+def current_thread():
     try:
-        return _active[_get_ident()]
+        return _active[get_ident()]
     except KeyError:
-        ##print "currentThread(): no current thread for", _get_ident()
+        ##print "current_thread(): no current thread for", get_ident()
         return _DummyThread()
 
-def activeCount():
+currentThread = current_thread
+
+def active_count():
     _active_limbo_lock.acquire()
     count = len(_active) + len(_limbo)
     _active_limbo_lock.release()
     return count
 
+activeCount = active_count
+
 def enumerate():
     _active_limbo_lock.acquire()
     active = list(_active.values()) + list(_limbo.values())
@@ -717,7 +720,21 @@ def enumerate():
 # and make it available for the interpreter
 # (Py_Main) as threading._shutdown.
 
-_shutdown = _MainThread()._exitfunc
+_main_thread = _MainThread()
+_shutdown = _main_thread._exitfunc
+
+def _pickSomeNonDaemonThread():
+    for t in enumerate():
+        if not t.isDaemon() and t.isAlive():
+            return t
+    return None
+
+def main_thread():
+    """Return the main thread object.
+    In normal conditions, the main thread is the thread from which the
+    Python interpreter was started.
+    """
+    return _main_thread
 
 # get thread-local implementation, either from the thread
 # module, or from the python fallback

+ 13 - 1
direct/src/task/Task.py

@@ -74,7 +74,9 @@ Task = PythonTask
 # Copy the module-level enums above into the class level.  This funny
 # syntax is necessary because it's a C++-wrapped extension type, not a
 # true Python class.
-Task.DtoolClassDict['done'] = done
+# We can't override 'done', which is already a known method.  We have a
+# special check in PythonTask for when the method is being returned.
+#Task.DtoolClassDict['done'] = done
 Task.DtoolClassDict['cont'] = cont
 Task.DtoolClassDict['again'] = again
 Task.DtoolClassDict['pickup'] = pickup
@@ -333,6 +335,7 @@ class TaskManager:
         funcOrTask - either an existing Task object (not already added
         to the task manager), or a callable function object.  If this
         is a function, a new Task object will be created and returned.
+        You may also pass in a coroutine object.
 
         name - the name to assign to the Task.  Required, unless you
         are passing in a Task object that already has a name.
@@ -385,6 +388,15 @@ class TaskManager:
             task = funcOrTask
         elif hasattr(funcOrTask, '__call__'):
             task = PythonTask(funcOrTask)
+            if name is None:
+                name = getattr(funcOrTask, '__qualname__', None) or \
+                       getattr(funcOrTask, '__name__', None)
+        elif hasattr(funcOrTask, 'cr_await') or type(funcOrTask) == types.GeneratorType:
+            # It's a coroutine, or something emulating one.
+            task = PythonTask(funcOrTask)
+            if name is None:
+                name = getattr(funcOrTask, '__qualname__', None) or \
+                       getattr(funcOrTask, '__name__', None)
         else:
             self.notify.error(
                 'add: Tried to add a task that was not a Task or a func')

Plik diff jest za duży
+ 536 - 524
dtool/src/cppparser/cppBison.cxx.prebuilt


+ 92 - 90
dtool/src/cppparser/cppBison.h.prebuilt

@@ -143,51 +143,52 @@ extern int cppyydebug;
     KW_IS_TRIVIAL = 353,
     KW_IS_UNION = 354,
     KW_LONG = 355,
-    KW_MAKE_MAP_PROPERTY = 356,
-    KW_MAKE_PROPERTY = 357,
-    KW_MAKE_PROPERTY2 = 358,
-    KW_MAKE_SEQ = 359,
-    KW_MAKE_SEQ_PROPERTY = 360,
-    KW_MUTABLE = 361,
-    KW_NAMESPACE = 362,
-    KW_NEW = 363,
-    KW_NOEXCEPT = 364,
-    KW_NULLPTR = 365,
-    KW_OPERATOR = 366,
-    KW_OVERRIDE = 367,
-    KW_PRIVATE = 368,
-    KW_PROTECTED = 369,
-    KW_PUBLIC = 370,
-    KW_REGISTER = 371,
-    KW_REINTERPRET_CAST = 372,
-    KW_RETURN = 373,
-    KW_SHORT = 374,
-    KW_SIGNED = 375,
-    KW_SIZEOF = 376,
-    KW_STATIC = 377,
-    KW_STATIC_ASSERT = 378,
-    KW_STATIC_CAST = 379,
-    KW_STRUCT = 380,
-    KW_TEMPLATE = 381,
-    KW_THREAD_LOCAL = 382,
-    KW_THROW = 383,
-    KW_TRUE = 384,
-    KW_TRY = 385,
-    KW_TYPEDEF = 386,
-    KW_TYPEID = 387,
-    KW_TYPENAME = 388,
-    KW_UNDERLYING_TYPE = 389,
-    KW_UNION = 390,
-    KW_UNSIGNED = 391,
-    KW_USING = 392,
-    KW_VIRTUAL = 393,
-    KW_VOID = 394,
-    KW_VOLATILE = 395,
-    KW_WCHAR_T = 396,
-    KW_WHILE = 397,
-    START_CPP = 398,
-    START_CONST_EXPR = 399,
-    START_TYPE = 400
+    KW_MAKE_MAP_KEYS_SEQ = 356,
+    KW_MAKE_MAP_PROPERTY = 357,
+    KW_MAKE_PROPERTY = 358,
+    KW_MAKE_PROPERTY2 = 359,
+    KW_MAKE_SEQ = 360,
+    KW_MAKE_SEQ_PROPERTY = 361,
+    KW_MUTABLE = 362,
+    KW_NAMESPACE = 363,
+    KW_NEW = 364,
+    KW_NOEXCEPT = 365,
+    KW_NULLPTR = 366,
+    KW_OPERATOR = 367,
+    KW_OVERRIDE = 368,
+    KW_PRIVATE = 369,
+    KW_PROTECTED = 370,
+    KW_PUBLIC = 371,
+    KW_REGISTER = 372,
+    KW_REINTERPRET_CAST = 373,
+    KW_RETURN = 374,
+    KW_SHORT = 375,
+    KW_SIGNED = 376,
+    KW_SIZEOF = 377,
+    KW_STATIC = 378,
+    KW_STATIC_ASSERT = 379,
+    KW_STATIC_CAST = 380,
+    KW_STRUCT = 381,
+    KW_TEMPLATE = 382,
+    KW_THREAD_LOCAL = 383,
+    KW_THROW = 384,
+    KW_TRUE = 385,
+    KW_TRY = 386,
+    KW_TYPEDEF = 387,
+    KW_TYPEID = 388,
+    KW_TYPENAME = 389,
+    KW_UNDERLYING_TYPE = 390,
+    KW_UNION = 391,
+    KW_UNSIGNED = 392,
+    KW_USING = 393,
+    KW_VIRTUAL = 394,
+    KW_VOID = 395,
+    KW_VOLATILE = 396,
+    KW_WCHAR_T = 397,
+    KW_WHILE = 398,
+    START_CPP = 399,
+    START_CONST_EXPR = 400,
+    START_TYPE = 401
   };
 #endif
 /* Tokens.  */
@@ -289,51 +290,52 @@ extern int cppyydebug;
 #define KW_IS_TRIVIAL 353
 #define KW_IS_UNION 354
 #define KW_LONG 355
-#define KW_MAKE_MAP_PROPERTY 356
-#define KW_MAKE_PROPERTY 357
-#define KW_MAKE_PROPERTY2 358
-#define KW_MAKE_SEQ 359
-#define KW_MAKE_SEQ_PROPERTY 360
-#define KW_MUTABLE 361
-#define KW_NAMESPACE 362
-#define KW_NEW 363
-#define KW_NOEXCEPT 364
-#define KW_NULLPTR 365
-#define KW_OPERATOR 366
-#define KW_OVERRIDE 367
-#define KW_PRIVATE 368
-#define KW_PROTECTED 369
-#define KW_PUBLIC 370
-#define KW_REGISTER 371
-#define KW_REINTERPRET_CAST 372
-#define KW_RETURN 373
-#define KW_SHORT 374
-#define KW_SIGNED 375
-#define KW_SIZEOF 376
-#define KW_STATIC 377
-#define KW_STATIC_ASSERT 378
-#define KW_STATIC_CAST 379
-#define KW_STRUCT 380
-#define KW_TEMPLATE 381
-#define KW_THREAD_LOCAL 382
-#define KW_THROW 383
-#define KW_TRUE 384
-#define KW_TRY 385
-#define KW_TYPEDEF 386
-#define KW_TYPEID 387
-#define KW_TYPENAME 388
-#define KW_UNDERLYING_TYPE 389
-#define KW_UNION 390
-#define KW_UNSIGNED 391
-#define KW_USING 392
-#define KW_VIRTUAL 393
-#define KW_VOID 394
-#define KW_VOLATILE 395
-#define KW_WCHAR_T 396
-#define KW_WHILE 397
-#define START_CPP 398
-#define START_CONST_EXPR 399
-#define START_TYPE 400
+#define KW_MAKE_MAP_KEYS_SEQ 356
+#define KW_MAKE_MAP_PROPERTY 357
+#define KW_MAKE_PROPERTY 358
+#define KW_MAKE_PROPERTY2 359
+#define KW_MAKE_SEQ 360
+#define KW_MAKE_SEQ_PROPERTY 361
+#define KW_MUTABLE 362
+#define KW_NAMESPACE 363
+#define KW_NEW 364
+#define KW_NOEXCEPT 365
+#define KW_NULLPTR 366
+#define KW_OPERATOR 367
+#define KW_OVERRIDE 368
+#define KW_PRIVATE 369
+#define KW_PROTECTED 370
+#define KW_PUBLIC 371
+#define KW_REGISTER 372
+#define KW_REINTERPRET_CAST 373
+#define KW_RETURN 374
+#define KW_SHORT 375
+#define KW_SIGNED 376
+#define KW_SIZEOF 377
+#define KW_STATIC 378
+#define KW_STATIC_ASSERT 379
+#define KW_STATIC_CAST 380
+#define KW_STRUCT 381
+#define KW_TEMPLATE 382
+#define KW_THREAD_LOCAL 383
+#define KW_THROW 384
+#define KW_TRUE 385
+#define KW_TRY 386
+#define KW_TYPEDEF 387
+#define KW_TYPEID 388
+#define KW_TYPENAME 389
+#define KW_UNDERLYING_TYPE 390
+#define KW_UNION 391
+#define KW_UNSIGNED 392
+#define KW_USING 393
+#define KW_VIRTUAL 394
+#define KW_VOID 395
+#define KW_VOLATILE 396
+#define KW_WCHAR_T 397
+#define KW_WHILE 398
+#define START_CPP 399
+#define START_CONST_EXPR 400
+#define START_TYPE 401
 
 /* Value type.  */
 

+ 252 - 95
dtool/src/cppparser/cppBison.yxx

@@ -304,6 +304,7 @@ pop_struct() {
 %token KW_IS_TRIVIAL
 %token KW_IS_UNION
 %token KW_LONG
+%token KW_MAKE_MAP_KEYS_SEQ
 %token KW_MAKE_MAP_PROPERTY
 %token KW_MAKE_PROPERTY
 %token KW_MAKE_PROPERTY2
@@ -397,6 +398,7 @@ pop_struct() {
 %type <u.type> class_derivation_name
 %type <u.type> enum_element_type
 %type <u.type> maybe_trailing_return_type
+%type <u.identifier> maybe_comma_identifier
 /*%type <u.type> typedefname*/
 %type <u.identifier> name
 %type <u.identifier> name_no_final
@@ -548,202 +550,341 @@ declaration:
 {
   current_scope->set_current_vis(V_private);
 }
-        | KW_MAKE_PROPERTY '(' name ',' IDENTIFIER ')' ';'
-{
-
-  CPPDeclaration *getter = $5->find_symbol(current_scope, global_scope, current_lexer);
-  if (getter == (CPPDeclaration *)NULL || getter->get_subtype() != CPPDeclaration::ST_function_group) {
-    yyerror("Reference to non-existent or invalid getter: " + $5->get_fully_scoped_name(), @5);
-  }
-
-  CPPMakeProperty *make_property = new CPPMakeProperty($3, getter->as_function_group(), NULL, current_scope, @1.file);
-  current_scope->add_declaration(make_property, global_scope, current_lexer, @1);
-}
-        | KW_MAKE_PROPERTY '(' name ',' IDENTIFIER ',' IDENTIFIER ')' ';'
+        | KW_MAKE_PROPERTY '(' name ',' IDENTIFIER maybe_comma_identifier ')' ';'
 {
   CPPDeclaration *getter = $5->find_symbol(current_scope, global_scope, current_lexer);
-  if (getter == (CPPDeclaration *)NULL || getter->get_subtype() != CPPDeclaration::ST_function_group) {
+  if (getter == nullptr || getter->get_subtype() != CPPDeclaration::ST_function_group) {
     yyerror("Reference to non-existent or invalid getter: " + $5->get_fully_scoped_name(), @5);
-
   } else {
-    CPPDeclaration *setter = $7->find_symbol(current_scope, global_scope, current_lexer);
-    CPPFunctionGroup *setter_func = NULL;
-
-    if (setter == (CPPDeclaration *)NULL || setter->get_subtype() != CPPDeclaration::ST_function_group) {
-      yyerror("Reference to non-existent or invalid setter: " + $7->get_fully_scoped_name(), @7);
-    } else {
-      setter_func = setter->as_function_group();
+    CPPMakeProperty *make_property = new CPPMakeProperty($3, CPPMakeProperty::T_normal, current_scope, @1.file);
+    make_property->_get_function = getter->as_function_group();
+
+    if ($6 != nullptr) {
+      CPPDeclaration *setter = $6->find_symbol(current_scope, global_scope, current_lexer);
+      if (setter == nullptr || setter->get_subtype() != CPPDeclaration::ST_function_group) {
+        yyerror("Reference to non-existent or invalid setter: " + $6->get_fully_scoped_name(), @6);
+      } else {
+        make_property->_set_function = setter->as_function_group();
+      }
     }
 
-    CPPMakeProperty *make_property = new CPPMakeProperty($3, getter->as_function_group(),
-                                                         setter_func, current_scope, @1.file);
     current_scope->add_declaration(make_property, global_scope, current_lexer, @1);
   }
 }
         | KW_MAKE_PROPERTY '(' name ',' IDENTIFIER ',' IDENTIFIER ',' IDENTIFIER ')' ';'
 {
   CPPDeclaration *getter = $5->find_symbol(current_scope, global_scope, current_lexer);
-  if (getter == (CPPDeclaration *)NULL || getter->get_subtype() != CPPDeclaration::ST_function_group) {
+  if (getter == nullptr || getter->get_subtype() != CPPDeclaration::ST_function_group) {
     yyerror("Reference to non-existent or invalid getter: " + $5->get_fully_scoped_name(), @5);
 
   } else {
-    CPPDeclaration *setter = $7->find_symbol(current_scope, global_scope, current_lexer);
-    CPPFunctionGroup *setter_func = NULL;
+    CPPMakeProperty *make_property = new CPPMakeProperty($3, CPPMakeProperty::T_normal, current_scope, @1.file);
+    make_property->_get_function = getter->as_function_group();
 
-    if (setter == (CPPDeclaration *)NULL || setter->get_subtype() != CPPDeclaration::ST_function_group) {
+    CPPDeclaration *setter = $7->find_symbol(current_scope, global_scope, current_lexer);
+    if (setter == nullptr || setter->get_subtype() != CPPDeclaration::ST_function_group) {
       yyerror("Reference to non-existent or invalid setter: " + $7->get_fully_scoped_name(), @7);
     } else {
-      setter_func = setter->as_function_group();
+      make_property->_set_function = setter->as_function_group();
     }
 
     CPPDeclaration *deleter = $9->find_symbol(current_scope, global_scope, current_lexer);
-    if (deleter == (CPPDeclaration *)NULL || deleter->get_subtype() != CPPDeclaration::ST_function_group) {
+    if (deleter == nullptr || deleter->get_subtype() != CPPDeclaration::ST_function_group) {
       yyerror("reference to non-existent or invalid delete method: " + $9->get_fully_scoped_name(), @9);
-      deleter = NULL;
-    }
-
-    CPPMakeProperty *make_property = new CPPMakeProperty($3, getter->as_function_group(),
-                                                         setter_func, current_scope, @1.file);
-    if (deleter) {
+    } else {
       make_property->_del_function = deleter->as_function_group();
     }
+
     current_scope->add_declaration(make_property, global_scope, current_lexer, @1);
   }
 }
         | KW_MAKE_SEQ_PROPERTY '(' name ',' IDENTIFIER ',' IDENTIFIER ')' ';'
 {
   CPPDeclaration *length_getter = $5->find_symbol(current_scope, global_scope, current_lexer);
-  if (length_getter == (CPPDeclaration *)NULL || length_getter->get_subtype() != CPPDeclaration::ST_function_group) {
+  if (length_getter == nullptr || length_getter->get_subtype() != CPPDeclaration::ST_function_group) {
     yyerror("reference to non-existent or invalid length method: " + $5->get_fully_scoped_name(), @5);
-    length_getter = NULL;
+    length_getter = nullptr;
   }
 
   CPPDeclaration *getter = $7->find_symbol(current_scope, global_scope, current_lexer);
-  if (getter == (CPPDeclaration *)NULL || getter->get_subtype() != CPPDeclaration::ST_function_group) {
+  if (getter == nullptr || getter->get_subtype() != CPPDeclaration::ST_function_group) {
     yyerror("Reference to non-existent or invalid getter: " + $7->get_fully_scoped_name(), @7);
+    getter = nullptr;
   }
 
-  CPPMakeProperty *make_property = new CPPMakeProperty($3, getter->as_function_group(), NULL, current_scope, @1.file);
-  make_property->_length_function = length_getter->as_function_group();
-  current_scope->add_declaration(make_property, global_scope, current_lexer, @1);
+  if (getter != nullptr && length_getter != nullptr) {
+    CPPMakeProperty *make_property = new CPPMakeProperty($3, CPPMakeProperty::T_sequence, current_scope, @1.file);
+    make_property->_get_function = getter->as_function_group();
+    make_property->_length_function = length_getter->as_function_group();
+    current_scope->add_declaration(make_property, global_scope, current_lexer, @1);
+  }
 }
         | KW_MAKE_SEQ_PROPERTY '(' name ',' IDENTIFIER ',' IDENTIFIER ',' IDENTIFIER ')' ';'
 {
   CPPDeclaration *length_getter = $5->find_symbol(current_scope, global_scope, current_lexer);
-  if (length_getter == (CPPDeclaration *)NULL || length_getter->get_subtype() != CPPDeclaration::ST_function_group) {
+  if (length_getter == nullptr || length_getter->get_subtype() != CPPDeclaration::ST_function_group) {
     yyerror("reference to non-existent or invalid length method: " + $5->get_fully_scoped_name(), @5);
-    length_getter = NULL;
+    length_getter = nullptr;
   }
 
   CPPDeclaration *getter = $7->find_symbol(current_scope, global_scope, current_lexer);
-  if (getter == (CPPDeclaration *)NULL || getter->get_subtype() != CPPDeclaration::ST_function_group) {
+  if (getter == nullptr || getter->get_subtype() != CPPDeclaration::ST_function_group) {
     yyerror("Reference to non-existent or invalid getter: " + $7->get_fully_scoped_name(), @7);
+    getter = nullptr;
+  }
 
-  } else {
-    CPPDeclaration *setter = $9->find_symbol(current_scope, global_scope, current_lexer);
-    CPPFunctionGroup *setter_func = NULL;
+  if (getter != nullptr && length_getter != nullptr) {
+    CPPMakeProperty *make_property = new CPPMakeProperty($3, CPPMakeProperty::T_sequence, current_scope, @1.file);
+    make_property->_get_function = getter->as_function_group();
+    make_property->_length_function = length_getter->as_function_group();
 
-    if (setter == (CPPDeclaration *)NULL || setter->get_subtype() != CPPDeclaration::ST_function_group) {
+    CPPDeclaration *setter = $9->find_symbol(current_scope, global_scope, current_lexer);
+    if (setter == nullptr || setter->get_subtype() != CPPDeclaration::ST_function_group) {
       yyerror("Reference to non-existent or invalid setter: " + $9->get_fully_scoped_name(), @9);
     } else {
-      setter_func = setter->as_function_group();
+      make_property->_set_function = setter->as_function_group();
     }
 
-    CPPMakeProperty *make_property = new CPPMakeProperty($3, getter->as_function_group(),
-                                                         setter_func, current_scope, @1.file);
-    make_property->_length_function = length_getter->as_function_group();
     current_scope->add_declaration(make_property, global_scope, current_lexer, @1);
   }
 }
         | KW_MAKE_SEQ_PROPERTY '(' name ',' IDENTIFIER ',' IDENTIFIER ',' IDENTIFIER ',' IDENTIFIER ')' ';'
 {
   CPPDeclaration *length_getter = $5->find_symbol(current_scope, global_scope, current_lexer);
-  if (length_getter == (CPPDeclaration *)NULL || length_getter->get_subtype() != CPPDeclaration::ST_function_group) {
+  if (length_getter == nullptr || length_getter->get_subtype() != CPPDeclaration::ST_function_group) {
     yyerror("reference to non-existent or invalid length method: " + $5->get_fully_scoped_name(), @5);
     length_getter = NULL;
   }
 
   CPPDeclaration *getter = $7->find_symbol(current_scope, global_scope, current_lexer);
-  if (getter == (CPPDeclaration *)NULL || getter->get_subtype() != CPPDeclaration::ST_function_group) {
+  if (getter == nullptr || getter->get_subtype() != CPPDeclaration::ST_function_group) {
     yyerror("Reference to non-existent or invalid getter: " + $7->get_fully_scoped_name(), @7);
+    getter = nullptr;
+  }
 
-  } else {
-    CPPDeclaration *setter = $9->find_symbol(current_scope, global_scope, current_lexer);
-    CPPFunctionGroup *setter_func = NULL;
+  if (getter != nullptr && length_getter != nullptr) {
+    CPPMakeProperty *make_property = new CPPMakeProperty($3, CPPMakeProperty::T_sequence, current_scope, @1.file);
+    make_property->_get_function = getter->as_function_group();
+    make_property->_length_function = length_getter->as_function_group();
 
-    if (setter == (CPPDeclaration *)NULL || setter->get_subtype() != CPPDeclaration::ST_function_group) {
+    CPPDeclaration *setter = $9->find_symbol(current_scope, global_scope, current_lexer);
+    if (setter == nullptr || setter->get_subtype() != CPPDeclaration::ST_function_group) {
       yyerror("Reference to non-existent or invalid setter: " + $9->get_fully_scoped_name(), @9);
     } else {
-      setter_func = setter->as_function_group();
+      make_property->_set_function = setter->as_function_group();
     }
 
     CPPDeclaration *deleter = $11->find_symbol(current_scope, global_scope, current_lexer);
-    if (deleter == (CPPDeclaration *)NULL || deleter->get_subtype() != CPPDeclaration::ST_function_group) {
+    if (deleter == nullptr || deleter->get_subtype() != CPPDeclaration::ST_function_group) {
       yyerror("reference to non-existent or invalid delete method: " + $11->get_fully_scoped_name(), @11);
-      deleter = NULL;
+    } else {
+      make_property->_del_function = deleter->as_function_group();
     }
 
-    CPPMakeProperty *make_property = new CPPMakeProperty($3, getter->as_function_group(),
-                                                         setter_func, current_scope, @1.file);
+    current_scope->add_declaration(make_property, global_scope, current_lexer, @1);
+  }
+}
+        | KW_MAKE_SEQ_PROPERTY '(' name ',' IDENTIFIER ',' IDENTIFIER ',' IDENTIFIER ',' IDENTIFIER ',' IDENTIFIER ')' ';'
+{
+  CPPDeclaration *length_getter = $5->find_symbol(current_scope, global_scope, current_lexer);
+  if (length_getter == nullptr || length_getter->get_subtype() != CPPDeclaration::ST_function_group) {
+    yyerror("reference to non-existent or invalid length method: " + $5->get_fully_scoped_name(), @5);
+    length_getter = NULL;
+  }
+
+  CPPDeclaration *getter = $7->find_symbol(current_scope, global_scope, current_lexer);
+  if (getter == nullptr || getter->get_subtype() != CPPDeclaration::ST_function_group) {
+    yyerror("Reference to non-existent or invalid getter: " + $7->get_fully_scoped_name(), @7);
+    getter = nullptr;
+  }
+
+  if (getter != nullptr && length_getter != nullptr) {
+    CPPMakeProperty *make_property = new CPPMakeProperty($3, CPPMakeProperty::T_sequence, current_scope, @1.file);
+    make_property->_get_function = getter->as_function_group();
     make_property->_length_function = length_getter->as_function_group();
-    if (deleter) {
+
+    CPPDeclaration *setter = $9->find_symbol(current_scope, global_scope, current_lexer);
+    if (setter == nullptr || setter->get_subtype() != CPPDeclaration::ST_function_group) {
+      yyerror("Reference to non-existent or invalid setter: " + $9->get_fully_scoped_name(), @9);
+    } else {
+      make_property->_set_function = setter->as_function_group();
+    }
+
+    CPPDeclaration *deleter = $11->find_symbol(current_scope, global_scope, current_lexer);
+    if (deleter == nullptr || deleter->get_subtype() != CPPDeclaration::ST_function_group) {
+      yyerror("reference to non-existent or invalid delete method: " + $11->get_fully_scoped_name(), @11);
+    } else {
       make_property->_del_function = deleter->as_function_group();
     }
+
+    CPPDeclaration *inserter = $13->find_symbol(current_scope, global_scope, current_lexer);
+    if (inserter == nullptr || inserter->get_subtype() != CPPDeclaration::ST_function_group) {
+      yyerror("reference to non-existent or invalid append method: " + $13->get_fully_scoped_name(), @13);
+    } else {
+      make_property->_insert_function = inserter->as_function_group();
+    }
+
     current_scope->add_declaration(make_property, global_scope, current_lexer, @1);
   }
 }
-        | KW_MAKE_PROPERTY2 '(' name ',' IDENTIFIER ',' IDENTIFIER ')' ';'
+        | KW_MAKE_MAP_PROPERTY '(' name ',' IDENTIFIER ')' ';'
 {
-  CPPDeclaration *hasser = $5->find_symbol(current_scope, global_scope, current_lexer);
-  if (hasser == (CPPDeclaration *)NULL || hasser->get_subtype() != CPPDeclaration::ST_function_group) {
-    yyerror("Reference to non-existent or invalid has-function: " + $5->get_fully_scoped_name(), @5);
-  }
+  CPPDeclaration *getter = $5->find_symbol(current_scope, global_scope, current_lexer);
+  if (getter == nullptr || getter->get_subtype() != CPPDeclaration::ST_function_group) {
+    yyerror("reference to non-existent or invalid item getter method: " + $5->get_fully_scoped_name(), @5);
 
+  } else {
+    CPPMakeProperty *make_property = new CPPMakeProperty($3, CPPMakeProperty::T_mapping, current_scope, @1.file);
+    make_property->_get_function = getter->as_function_group();
+    current_scope->add_declaration(make_property, global_scope, current_lexer, @1);
+  }
+}
+        | KW_MAKE_MAP_PROPERTY '(' name ',' IDENTIFIER ',' IDENTIFIER ')' ';'
+{
   CPPDeclaration *getter = $7->find_symbol(current_scope, global_scope, current_lexer);
-  if (getter == (CPPDeclaration *)NULL || getter->get_subtype() != CPPDeclaration::ST_function_group) {
+  if (getter == nullptr || getter->get_subtype() != CPPDeclaration::ST_function_group) {
     yyerror("Reference to non-existent or invalid getter: " + $7->get_fully_scoped_name(), @7);
-  }
 
-  if (hasser && getter) {
+  } else {
     CPPMakeProperty *make_property;
-    make_property = new CPPMakeProperty($3,
-                                        hasser->as_function_group(),
-                                        getter->as_function_group(),
-                                        NULL, NULL,
-                                        current_scope, @1.file);
+    make_property = new CPPMakeProperty($3, CPPMakeProperty::T_mapping, current_scope, @1.file);
+    make_property->_get_function = getter->as_function_group();
+
+    CPPDeclaration *hasser = $5->find_symbol(current_scope, global_scope, current_lexer);
+    if (hasser == nullptr || hasser->get_subtype() != CPPDeclaration::ST_function_group) {
+      yyerror("reference to non-existent or invalid has/find method: " + $5->get_fully_scoped_name(), @5);
+    } else {
+      make_property->_has_function = hasser->as_function_group();
+    }
+
     current_scope->add_declaration(make_property, global_scope, current_lexer, @1);
   }
 }
-        | KW_MAKE_PROPERTY2 '(' name ',' IDENTIFIER ',' IDENTIFIER ',' IDENTIFIER ',' IDENTIFIER ')' ';'
+        | KW_MAKE_MAP_PROPERTY '(' name ',' IDENTIFIER ',' IDENTIFIER ',' IDENTIFIER maybe_comma_identifier ')' ';'
 {
-  CPPDeclaration *hasser = $5->find_symbol(current_scope, global_scope, current_lexer);
-  if (hasser == (CPPDeclaration *)NULL || hasser->get_subtype() != CPPDeclaration::ST_function_group) {
-    yyerror("Reference to non-existent or invalid has-function: " + $5->get_fully_scoped_name(), @5);
+  CPPDeclaration *getter = $7->find_symbol(current_scope, global_scope, current_lexer);
+  if (getter == nullptr || getter->get_subtype() != CPPDeclaration::ST_function_group) {
+    yyerror("Reference to non-existent or invalid getter: " + $7->get_fully_scoped_name(), @7);
+
+  } else {
+    CPPMakeProperty *make_property = new CPPMakeProperty($3, CPPMakeProperty::T_mapping, current_scope, @1.file);
+    make_property->_get_function = getter->as_function_group();
+
+    CPPDeclaration *hasser = $5->find_symbol(current_scope, global_scope, current_lexer);
+    if (hasser == nullptr || hasser->get_subtype() != CPPDeclaration::ST_function_group) {
+      yyerror("reference to non-existent or invalid has/find method: " + $5->get_fully_scoped_name(), @5);
+    } else {
+      make_property->_has_function = hasser->as_function_group();
+    }
+
+    CPPDeclaration *setter = $9->find_symbol(current_scope, global_scope, current_lexer);
+    if (setter == nullptr || setter->get_subtype() != CPPDeclaration::ST_function_group) {
+      yyerror("Reference to non-existent or invalid setter: " + $9->get_fully_scoped_name(), @9);
+    } else {
+      make_property->_set_function = setter->as_function_group();
+    }
+
+    if ($10 != nullptr) {
+      CPPDeclaration *deleter = $10->find_symbol(current_scope, global_scope, current_lexer);
+      if (deleter == nullptr || deleter->get_subtype() != CPPDeclaration::ST_function_group) {
+        yyerror("reference to non-existent or invalid delete method: " + $10->get_fully_scoped_name(), @10);
+      } else {
+        make_property->_del_function = deleter->as_function_group();
+      }
+    }
+
+    current_scope->add_declaration(make_property, global_scope, current_lexer, @1);
+  }
+}
+        | KW_MAKE_MAP_KEYS_SEQ '(' name ',' IDENTIFIER ',' IDENTIFIER ')' ';'
+{
+  CPPDeclaration *length_getter = $5->find_symbol(current_scope, global_scope, current_lexer);
+  if (length_getter == nullptr || length_getter->get_subtype() != CPPDeclaration::ST_function_group) {
+    yyerror("reference to non-existent or invalid length method: " + $5->get_fully_scoped_name(), @5);
+    length_getter = nullptr;
   }
 
   CPPDeclaration *getter = $7->find_symbol(current_scope, global_scope, current_lexer);
-  if (getter == (CPPDeclaration *)NULL || getter->get_subtype() != CPPDeclaration::ST_function_group) {
-    yyerror("Reference to non-existent or invalid getter: " + $7->get_fully_scoped_name(), @7);
+  if (getter == nullptr || getter->get_subtype() != CPPDeclaration::ST_function_group) {
+    yyerror("reference to non-existent or invalid getter: " + $7->get_fully_scoped_name(), @7);
+    getter = nullptr;
   }
 
-  CPPDeclaration *setter = $9->find_symbol(current_scope, global_scope, current_lexer);
-  if (setter == (CPPDeclaration *)NULL || setter->get_subtype() != CPPDeclaration::ST_function_group) {
-    yyerror("Reference to non-existent or invalid setter: " + $9->get_fully_scoped_name(), @9);
+  if (getter != nullptr && length_getter != nullptr) {
+    CPPMakeProperty *make_property = nullptr;
+    for (size_t i = 0; i < current_scope->_declarations.size(); ++i) {
+      make_property = current_scope->_declarations[i]->as_make_property();
+      if (make_property != nullptr) {
+        if (make_property->get_fully_scoped_name() == $3->get_fully_scoped_name()) {
+          break;
+        } else {
+          make_property = nullptr;
+        }
+      }
+    }
+    if (make_property != nullptr) {
+      make_property->_get_key_function = getter->as_function_group();
+      make_property->_length_function = length_getter->as_function_group();
+    } else {
+      yyerror("reference to non-existent MAKE_MAP_PROPERTY: " + $3->get_fully_scoped_name(), @3);
+    }
   }
+}
+        | KW_MAKE_PROPERTY2 '(' name ',' IDENTIFIER ',' IDENTIFIER ')' ';'
+{
+  CPPDeclaration *getter = $7->find_symbol(current_scope, global_scope, current_lexer);
+  if (getter == nullptr || getter->get_subtype() != CPPDeclaration::ST_function_group) {
+    yyerror("Reference to non-existent or invalid getter: " + $7->get_fully_scoped_name(), @7);
 
-  CPPDeclaration *clearer = $11->find_symbol(current_scope, global_scope, current_lexer);
-  if (clearer == (CPPDeclaration *)NULL || clearer->get_subtype() != CPPDeclaration::ST_function_group) {
-    yyerror("Reference to non-existent or invalid clear-function: " + $11->get_fully_scoped_name(), @11);
+  } else {
+    CPPMakeProperty *make_property;
+    make_property = new CPPMakeProperty($3, CPPMakeProperty::T_normal,
+                                        current_scope, @1.file);
+    make_property->_get_function = getter->as_function_group();
+
+    CPPDeclaration *hasser = $5->find_symbol(current_scope, global_scope, current_lexer);
+    if (hasser == nullptr || hasser->get_subtype() != CPPDeclaration::ST_function_group) {
+      yyerror("reference to non-existent or invalid has/find method: " + $5->get_fully_scoped_name(), @5);
+    } else {
+      make_property->_has_function = hasser->as_function_group();
+    }
+
+    current_scope->add_declaration(make_property, global_scope, current_lexer, @1);
   }
+}
+        | KW_MAKE_PROPERTY2 '(' name ',' IDENTIFIER ',' IDENTIFIER ',' IDENTIFIER ',' IDENTIFIER ')' ';'
+{
+  CPPDeclaration *getter = $7->find_symbol(current_scope, global_scope, current_lexer);
+  if (getter == nullptr || getter->get_subtype() != CPPDeclaration::ST_function_group) {
+    yyerror("Reference to non-existent or invalid getter: " + $7->get_fully_scoped_name(), @7);
 
-  if (hasser && getter && setter && clearer) {
+  } else {
     CPPMakeProperty *make_property;
-    make_property = new CPPMakeProperty($3,
-                                        hasser->as_function_group(),
-                                        getter->as_function_group(),
-                                        setter->as_function_group(),
-                                        clearer->as_function_group(),
+    make_property = new CPPMakeProperty($3, CPPMakeProperty::T_normal,
                                         current_scope, @1.file);
+    make_property->_get_function = getter->as_function_group();
+
+    CPPDeclaration *hasser = $5->find_symbol(current_scope, global_scope, current_lexer);
+    if (hasser == nullptr || hasser->get_subtype() != CPPDeclaration::ST_function_group) {
+      yyerror("reference to non-existent or invalid has/find method: " + $5->get_fully_scoped_name(), @5);
+    } else {
+      make_property->_has_function = hasser->as_function_group();
+    }
+
+    CPPDeclaration *setter = $9->find_symbol(current_scope, global_scope, current_lexer);
+    if (setter == nullptr || setter->get_subtype() != CPPDeclaration::ST_function_group) {
+      yyerror("reference to non-existent or invalid setter: " + $9->get_fully_scoped_name(), @9);
+    } else {
+      make_property->_set_function = setter->as_function_group();
+    }
+
+    CPPDeclaration *clearer = $11->find_symbol(current_scope, global_scope, current_lexer);
+    if (clearer == nullptr || clearer->get_subtype() != CPPDeclaration::ST_function_group) {
+      yyerror("reference to non-existent or invalid clear method: " + $11->get_fully_scoped_name(), @11);
+    } else {
+      make_property->_clear_function = clearer->as_function_group();
+    }
+
     current_scope->add_declaration(make_property, global_scope, current_lexer, @1);
   }
 }
@@ -1723,6 +1864,18 @@ maybe_trailing_return_type:
         ;
 
 
+maybe_comma_identifier:
+        empty
+{
+  $$ = NULL;
+}
+        | ',' IDENTIFIER
+{
+  $$ = $2;
+}
+        ;
+
+
 function_parameter_list:
         empty
 {
@@ -4051,6 +4204,10 @@ name:
         | KW_STATIC
 {
   $$ = new CPPIdentifier("static", @1);
+}
+        | KW_DEFAULT
+{
+  $$ = new CPPIdentifier("default", @1);
 }
         ;
 

+ 8 - 0
dtool/src/cppparser/cppExtensionType.cxx

@@ -131,6 +131,14 @@ is_copy_constructible() const {
   return (_type == T_enum || _type == T_enum_class || _type == T_enum_struct);
 }
 
+/**
+ * Returns true if the type is copy-assignable.
+ */
+bool CPPExtensionType::
+is_copy_assignable() const {
+  return (_type == T_enum || _type == T_enum_class || _type == T_enum_struct);
+}
+
 /**
  *
  */

+ 1 - 0
dtool/src/cppparser/cppExtensionType.h

@@ -52,6 +52,7 @@ public:
   virtual bool is_constructible(const CPPType *type) const;
   virtual bool is_default_constructible() const;
   virtual bool is_copy_constructible() const;
+  virtual bool is_copy_assignable() const;
 
   virtual CPPDeclaration *substitute_decl(SubstDecl &subst,
                                           CPPScope *current_scope,

+ 2 - 0
dtool/src/cppparser/cppFunctionType.h

@@ -43,6 +43,8 @@ public:
     F_volatile_method   = 0x4000,
     F_lvalue_method     = 0x8000,
     F_rvalue_method     = 0x10000,
+    F_copy_assignment_operator = 0x20000,
+    F_move_assignment_operator = 0x40000,
   };
 
   CPPFunctionType(CPPType *return_type, CPPParameterList *parameters,

+ 19 - 8
dtool/src/cppparser/cppInstance.cxx

@@ -328,8 +328,8 @@ get_fully_scoped_name() const {
 
 /**
  * If this is a function type instance, checks whether the function name
- * matches the class name (or ~name), and if so, flags it as a constructor (or
- * destructor).
+ * matches the class name (or ~name), and if so, flags it as a constructor,
+ * destructor or assignment operator
  */
 void CPPInstance::
 check_for_constructor(CPPScope *current_scope, CPPScope *global_scope) {
@@ -344,13 +344,16 @@ check_for_constructor(CPPScope *current_scope, CPPScope *global_scope) {
     string class_name = scope->get_local_name();
 
     if (!method_name.empty() && !class_name.empty()) {
-      if (method_name == class_name) {
+      // Check either a constructor or assignment operator.
+      if (method_name == class_name || method_name == "operator =") {
         CPPType *void_type = CPPType::new_type
           (new CPPSimpleType(CPPSimpleType::T_void));
 
-        int flags = func->_flags | CPPFunctionType::F_constructor;
+        int flags = func->_flags;
+        if (method_name == class_name) {
+          flags |= CPPFunctionType::F_constructor;
+        }
 
-        // Check if it might be a copy or move constructor.
         CPPParameterList *params = func->_parameters;
         if (params->_parameters.size() == 1 && !params->_includes_ellipsis) {
           CPPType *param_type = params->_parameters[0]->_type;
@@ -360,10 +363,18 @@ check_for_constructor(CPPScope *current_scope, CPPScope *global_scope) {
             param_type = ref_type->_pointing_at->remove_cv();
 
             if (class_name == param_type->get_simple_name()) {
-              if (ref_type->_value_category == CPPReferenceType::VC_rvalue) {
-                flags |= CPPFunctionType::F_move_constructor;
+              if (flags & CPPFunctionType::F_constructor) {
+                if (ref_type->_value_category == CPPReferenceType::VC_rvalue) {
+                  flags |= CPPFunctionType::F_move_constructor;
+                } else {
+                  flags |= CPPFunctionType::F_copy_constructor;
+                }
               } else {
-                flags |= CPPFunctionType::F_copy_constructor;
+                if (ref_type->_value_category == CPPReferenceType::VC_rvalue) {
+                  flags |= CPPFunctionType::F_move_assignment_operator;
+                } else {
+                  flags |= CPPFunctionType::F_copy_assignment_operator;
+                }
               }
             }
           }

+ 14 - 28
dtool/src/cppparser/cppMakeProperty.cxx

@@ -18,37 +18,19 @@
  *
  */
 CPPMakeProperty::
-CPPMakeProperty(CPPIdentifier *ident,
-                CPPFunctionGroup *getter, CPPFunctionGroup *setter,
+CPPMakeProperty(CPPIdentifier *ident, Type type,
                 CPPScope *current_scope, const CPPFile &file) :
   CPPDeclaration(file),
   _ident(ident),
-  _length_function(NULL),
-  _has_function(NULL),
-  _get_function(getter),
-  _set_function(setter),
-  _clear_function(NULL),
-  _del_function(NULL)
-{
-  _ident->_native_scope = current_scope;
-}
-
-/**
- *
- */
-CPPMakeProperty::
-CPPMakeProperty(CPPIdentifier *ident,
-                CPPFunctionGroup *hasser, CPPFunctionGroup *getter,
-                CPPFunctionGroup *setter, CPPFunctionGroup *clearer,
-                CPPScope *current_scope, const CPPFile &file) :
-  CPPDeclaration(file),
-  _ident(ident),
-  _length_function(NULL),
-  _has_function(hasser),
-  _get_function(getter),
-  _set_function(setter),
-  _clear_function(clearer),
-  _del_function(NULL)
+  _type(type),
+  _length_function(nullptr),
+  _has_function(nullptr),
+  _get_function(nullptr),
+  _set_function(nullptr),
+  _clear_function(nullptr),
+  _del_function(nullptr),
+  _insert_function(nullptr),
+  _get_key_function(nullptr)
 {
   _ident->_native_scope = current_scope;
 }
@@ -116,6 +98,10 @@ output(ostream &out, int indent_level, CPPScope *scope, bool complete) const {
     out << ", " << _del_function->_name;
   }
 
+  if (_insert_function != NULL) {
+    out << ", " << _insert_function->_name;
+  }
+
   out << ");";
 }
 

+ 65 - 8
dtool/src/cppparser/cppMakeProperty.h

@@ -23,16 +23,72 @@
  * This is a MAKE_PROPERTY() declaration appearing within a class body.  It
  * means to generate a property within Python, replacing (for instance)
  * get_something()/set_something() with a synthetic 'something' attribute.
+ *
+ * This is an example of a simple property (MAKE_PROPERTY is defined as
+ * the built-in __make_property):
+ * @@code
+ *   Thing get_thing() const;
+ *   void set_thing(const Thing &);
+ *
+ *   MAKE_PROPERTY(thing, get_thing, set_thing);
+ * @@endcode
+ * The setter may be omitted to make the property read-only.
+ *
+ * There is also a secondary macro that allows the property to be set to a
+ * cleared state using separate clear functions.  In the scripting language,
+ * this would be represented by a "null" value, or an "optional" construct in
+ * languages that have no notion of a null value.
+ *
+ * @@code
+ *   bool has_thing() const;
+ *   Thing get_thing() const;
+ *   void set_thing(const Thing &);
+ *   void clear_thing();
+ *   MAKE_PROPERTY2(thing, has_thing, get_thing, set_thing, clear_thing);
+ * @@endcode
+ * As with MAKE_PROPERTY, both the setter and clearer can be omitted to create
+ * a read-only property.
+ *
+ * Thirdly, there is a variant called MAKE_SEQ_PROPERTY.  It takes a length
+ * function as argument and the getter and setter take an index as first
+ * argument:
+ * @@code
+ *   size_t get_num_things() const;
+ *   Thing &get_thing(size_t i) const;
+ *   void set_thing(size_t i, Thing value) const;
+ *   void remove_thing(size_t i) const;
+ *
+ *   MAKE_SEQ_PROPERTY(get_num_things, get_thing, set_thing, remove_thing);
+ * @@endcode
+ *
+ * Lastly, there is the possibility to have properties with key/value
+ * associations, often called a "map" or "dictionary" in scripting languages:
+ * @@code
+ *   bool has_thing(string key) const;
+ *   Thing &get_thing(string key) const;
+ *   void set_thing(string key, Thing value) const;
+ *   void clear_thing(string key) const;
+ *
+ *   MAKE_MAP_PROPERTY(things, has_thing, get_thing, set_thing, clear_thing);
+ * @@endcode
+ * You may also replace the "has" function with a "find" function that returns
+ * an index.  If the returned index is negative (or in the case of an unsigned
+ * integer, the maximum value), the item is assumed not to be present in the
+ * mapping.
+ *
+ * It is also possible to use both MAKE_SEQ_PROPERTY and MAKE_MAP_PROPERTY on
+ * the same property name.  This implies that this property has both a
+ * sequence and mapping interface.
  */
 class CPPMakeProperty : public CPPDeclaration {
 public:
-  CPPMakeProperty(CPPIdentifier *ident,
-                  CPPFunctionGroup *getter, CPPFunctionGroup *setter,
-                  CPPScope *current_scope, const CPPFile &file);
+  enum Type {
+    T_normal = 0x0,
+    T_sequence = 0x1,
+    T_mapping = 0x2,
+  };
 
-  CPPMakeProperty(CPPIdentifier *ident,
-                  CPPFunctionGroup *hasser, CPPFunctionGroup *getter,
-                  CPPFunctionGroup *setter, CPPFunctionGroup *clearer,
+  CPPMakeProperty(CPPIdentifier *ident, Type type,
                   CPPScope *current_scope, const CPPFile &file);
 
   virtual string get_simple_name() const;
@@ -46,14 +102,15 @@ public:
   virtual CPPMakeProperty *as_make_property();
 
   CPPIdentifier *_ident;
-  // If length_function is not NULL, this is actually a sequence property,
-  // and the other functions take an additional index argument.
+  Type _type;
   CPPFunctionGroup *_length_function;
   CPPFunctionGroup *_has_function;
   CPPFunctionGroup *_get_function;
   CPPFunctionGroup *_set_function;
   CPPFunctionGroup *_clear_function;
   CPPFunctionGroup *_del_function;
+  CPPFunctionGroup *_insert_function;
+  CPPFunctionGroup *_get_key_function;
 };
 
 #endif

+ 8 - 0
dtool/src/cppparser/cppPointerType.cxx

@@ -177,6 +177,14 @@ is_copy_constructible() const {
   return true;
 }
 
+/**
+ * Returns true if the type is copy-assignable.
+ */
+bool CPPPointerType::
+is_copy_assignable() const {
+  return true;
+}
+
 /**
  * This is a little more forgiving than is_equal(): it returns true if the
  * types appear to be referring to the same thing, even if they may have

+ 1 - 0
dtool/src/cppparser/cppPointerType.h

@@ -41,6 +41,7 @@ public:
   virtual bool is_constructible(const CPPType *other) const;
   virtual bool is_default_constructible() const;
   virtual bool is_copy_constructible() const;
+  virtual bool is_copy_assignable() const;
   virtual bool is_equivalent(const CPPType &other) const;
 
   virtual void output(ostream &out, int indent_level, CPPScope *scope,

+ 1 - 0
dtool/src/cppparser/cppPreprocessor.cxx

@@ -2641,6 +2641,7 @@ check_keyword(const string &name) {
   if (name == "__is_trivial") return KW_IS_TRIVIAL;
   if (name == "__is_union") return KW_IS_UNION;
   if (name == "long") return KW_LONG;
+  if (name == "__make_map_keys_seq") return KW_MAKE_MAP_KEYS_SEQ;
   if (name == "__make_map_property") return KW_MAKE_MAP_PROPERTY;
   if (name == "__make_property") return KW_MAKE_PROPERTY;
   if (name == "__make_property2") return KW_MAKE_PROPERTY2;

+ 8 - 0
dtool/src/cppparser/cppSimpleType.cxx

@@ -103,6 +103,14 @@ is_copy_constructible() const {
   return (_type != T_void);
 }
 
+/**
+ * Returns true if the type is copy-assignable.
+ */
+bool CPPSimpleType::
+is_copy_assignable() const {
+  return (_type != T_void);
+}
+
 /**
  * Returns true if the type is destructible.
  */

+ 1 - 0
dtool/src/cppparser/cppSimpleType.h

@@ -75,6 +75,7 @@ public:
   virtual bool is_constructible(const CPPType *type) const;
   virtual bool is_default_constructible() const;
   virtual bool is_copy_constructible() const;
+  virtual bool is_copy_assignable() const;
   virtual bool is_destructible() const;
   virtual bool is_parameter_expr() const;
 

+ 176 - 0
dtool/src/cppparser/cppStructType.cxx

@@ -435,6 +435,17 @@ is_copy_constructible() const {
   return is_copy_constructible(V_public);
 }
 
+/**
+ * Returns true if the type is copy-assignable.
+ */
+bool CPPStructType::
+is_copy_assignable() const {
+  if (is_abstract()) {
+    return false;
+  }
+  return is_copy_assignable(V_public);
+}
+
 /**
  * Returns true if the type is destructible.
  */
@@ -606,6 +617,97 @@ is_move_constructible(CPPVisibility min_vis) const {
   return is_copy_constructible(min_vis);
 }
 
+/**
+ * Returns true if the type is copy-assignable, without checking whether the
+ * class is abstract.
+ */
+bool CPPStructType::
+is_copy_assignable(CPPVisibility min_vis) const {
+  CPPInstance *assignment_operator = get_copy_assignment_operator();
+  if (assignment_operator != (CPPInstance *)NULL) {
+    // It has a copy assignment operator.
+    if (assignment_operator->_vis > min_vis) {
+      // Inaccessible copy assignment operator.
+      return false;
+    }
+
+    if (assignment_operator->_storage_class & CPPInstance::SC_deleted) {
+      // Deleted copy assignment operator.
+      return false;
+    }
+
+    // NB: if it's defaulted, it may still be deleted.
+    if ((assignment_operator->_storage_class & CPPInstance::SC_defaulted) == 0) {
+      return true;
+    }
+  }
+
+  // Implicit copy assignment operator.  Check if the implicit or defaulted
+  // copy assignment operator is deleted.
+  if (!assignment_operator && (get_move_constructor() || get_move_assignment_operator())) {
+    // It's not explicitly defaulted, and there is a move constructor or move
+    // assignment operator, so the implicitly-declared one is deleted.
+    return false;
+  }
+
+  Derivation::const_iterator di;
+  for (di = _derivation.begin(); di != _derivation.end(); ++di) {
+    CPPStructType *base = (*di)._base->as_struct_type();
+    if (base != NULL) {
+      if (!base->is_copy_assignable(V_protected)) {
+        return false;
+      }
+    }
+  }
+
+  // Make sure all members are assignable.
+  CPPScope::Variables::const_iterator vi;
+  for (vi = _scope->_variables.begin(); vi != _scope->_variables.end(); ++vi) {
+    CPPInstance *instance = (*vi).second;
+    assert(instance != NULL);
+
+    if (instance->_storage_class & CPPInstance::SC_static) {
+      // Static members don't count.
+      continue;
+    }
+
+    if (!instance->_type->is_copy_assignable()) {
+      // Const or reference member, can't do it.
+      return false;
+    }
+  }
+
+  return true;
+}
+
+/**
+ * Returns true if the type is move-assignable.
+ */
+bool CPPStructType::
+is_move_assignable(CPPVisibility min_vis) const {
+  CPPInstance *assignment_operator = get_move_assignment_operator();
+  if (assignment_operator != (CPPInstance *)NULL) {
+    // It has a user-declared move assignment_operator.
+    if (assignment_operator->_vis > min_vis) {
+      // Inaccessible move assignment_operator.
+      return false;
+    }
+
+    if (assignment_operator->_storage_class & CPPInstance::SC_deleted) {
+      // It is deleted.
+      return false;
+    }
+
+    if (is_abstract()) {
+      return false;
+    }
+
+    return true;
+  }
+
+  return is_copy_assignable(min_vis);
+}
+
 /**
  * Returns true if the type is destructible.
  */
@@ -886,6 +988,80 @@ get_move_constructor() const {
   return (CPPInstance *)NULL;
 }
 
+/**
+ * Returns the assignment operator defined for the struct type, if any, or
+ * NULL if no assignment operator is found.
+ */
+CPPFunctionGroup *CPPStructType::
+get_assignment_operator() const {
+  // Just look for the function with the name "operator ="
+  CPPScope::Functions::const_iterator fi;
+  fi = _scope->_functions.find("operator =");
+  if (fi != _scope->_functions.end()) {
+    return fi->second;
+  } else {
+    return (CPPFunctionGroup *)NULL;
+  }
+}
+
+/**
+ * Returns the copy assignment operator defined for the struct type, or NULL
+ * if no user-declared copy assignment operator exists.
+ */
+CPPInstance *CPPStructType::
+get_copy_assignment_operator() const {
+  CPPFunctionGroup *fgroup = get_assignment_operator();
+  if (fgroup == (CPPFunctionGroup *)NULL) {
+    return (CPPInstance *)NULL;
+  }
+
+  CPPFunctionGroup::Instances::const_iterator ii;
+  for (ii = fgroup->_instances.begin();
+       ii != fgroup->_instances.end();
+       ++ii) {
+    CPPInstance *inst = (*ii);
+    assert(inst->_type != (CPPType *)NULL);
+
+    CPPFunctionType *ftype = inst->_type->as_function_type();
+    assert(ftype != (CPPFunctionType *)NULL);
+
+    if ((ftype->_flags & CPPFunctionType::F_copy_assignment_operator) != 0) {
+      return inst;
+    }
+  }
+
+  return (CPPInstance *)NULL;
+}
+
+/**
+ * Returns the move assignment operator defined for the struct type, or NULL
+ * if no user-declared move assignment operator exists.
+ */
+CPPInstance *CPPStructType::
+get_move_assignment_operator() const {
+  CPPFunctionGroup *fgroup = get_assignment_operator();
+  if (fgroup == (CPPFunctionGroup *)NULL) {
+    return (CPPInstance *)NULL;
+  }
+
+  CPPFunctionGroup::Instances::const_iterator ii;
+  for (ii = fgroup->_instances.begin();
+       ii != fgroup->_instances.end();
+       ++ii) {
+    CPPInstance *inst = (*ii);
+    assert(inst->_type != (CPPType *)NULL);
+
+    CPPFunctionType *ftype = inst->_type->as_function_type();
+    assert(ftype != (CPPFunctionType *)NULL);
+
+    if ((ftype->_flags & CPPFunctionType::F_move_assignment_operator) != 0) {
+      return inst;
+    }
+  }
+
+  return (CPPInstance *)NULL;
+}
+
 /**
  * Returns the destructor defined for the struct type, if any, or NULL if no
  * user-declared destructor is found.

+ 7 - 1
dtool/src/cppparser/cppStructType.h

@@ -56,10 +56,13 @@ public:
   virtual bool is_constructible(const CPPType *arg_type) const;
   virtual bool is_default_constructible() const;
   virtual bool is_copy_constructible() const;
+  virtual bool is_copy_assignable() const;
   virtual bool is_destructible() const;
   bool is_default_constructible(CPPVisibility min_vis) const;
   bool is_copy_constructible(CPPVisibility min_vis) const;
-  bool is_move_constructible(CPPVisibility min_vis) const;
+  bool is_move_constructible(CPPVisibility min_vis  = V_public) const;
+  bool is_copy_assignable(CPPVisibility min_vis) const;
+  bool is_move_assignable(CPPVisibility min_vis = V_public) const;
   bool is_destructible(CPPVisibility min_vis) const;
   virtual bool is_convertible_to(const CPPType *other) const;
 
@@ -69,6 +72,9 @@ public:
   CPPInstance *get_default_constructor() const;
   CPPInstance *get_copy_constructor() const;
   CPPInstance *get_move_constructor() const;
+  CPPFunctionGroup *get_assignment_operator() const;
+  CPPInstance *get_copy_assignment_operator() const;
+  CPPInstance *get_move_assignment_operator() const;
   CPPInstance *get_destructor() const;
 
   virtual CPPDeclaration *

+ 8 - 0
dtool/src/cppparser/cppType.cxx

@@ -110,6 +110,14 @@ is_copy_constructible() const {
   return false;
 }
 
+/**
+ * Returns true if the type is copy-assignable.
+ */
+bool CPPType::
+is_copy_assignable() const {
+  return false;
+}
+
 /**
  * Returns true if the type is destructible.
  */

+ 1 - 0
dtool/src/cppparser/cppType.h

@@ -51,6 +51,7 @@ public:
   virtual bool is_constructible(const CPPType *type) const;
   virtual bool is_default_constructible() const;
   virtual bool is_copy_constructible() const;
+  virtual bool is_copy_assignable() const;
   virtual bool is_destructible() const;
   virtual bool is_parameter_expr() const;
 

+ 8 - 0
dtool/src/cppparser/cppTypedefType.cxx

@@ -205,6 +205,14 @@ is_copy_constructible() const {
   return _type->is_copy_constructible();
 }
 
+/**
+ * Returns true if the type is copy-assignable.
+ */
+bool CPPTypedefType::
+is_copy_assignable() const {
+  return _type->is_copy_assignable();
+}
+
 /**
  * Returns true if the type is destructible.
  */

+ 1 - 0
dtool/src/cppparser/cppTypedefType.h

@@ -48,6 +48,7 @@ public:
   virtual bool is_constructible(const CPPType *type) const;
   virtual bool is_default_constructible() const;
   virtual bool is_copy_constructible() const;
+  virtual bool is_copy_assignable() const;
   virtual bool is_destructible() const;
 
   virtual bool is_fully_specified() const;

+ 13 - 1
dtool/src/dtoolbase/dtoolbase.h

@@ -94,6 +94,14 @@
 #define RETURNS_ALIGNED(x)
 #endif
 
+#ifdef __GNUC__
+#define LIKELY(x) __builtin_expect(!!(x), 1)
+#define UNLIKELY(x) __builtin_expect(!!(x), 0)
+#else
+#define LIKELY(x) (x)
+#define UNLIKELY(x) (x)
+#endif
+
 /*
   include win32 defns for everything up to WinServer2003, and assume
   I'm smart enough to use GetProcAddress for backward compat on
@@ -332,7 +340,7 @@ typedef struct _object PyObject;
 
 #ifdef __WORDSIZE
 #define NATIVE_WORDSIZE __WORDSIZE
-#elif defined(_LP64)
+#elif defined(_LP64) || defined(_WIN64)
 #define NATIVE_WORDSIZE 64
 #else
 #define NATIVE_WORDSIZE 32
@@ -456,6 +464,8 @@ typedef struct _object PyObject;
 #define MAKE_PROPERTY2(property_name, ...) __make_property2(property_name, __VA_ARGS__)
 #define MAKE_SEQ(seq_name, num_name, element_name) __make_seq(seq_name, num_name, element_name)
 #define MAKE_SEQ_PROPERTY(property_name, ...) __make_seq_property(property_name, __VA_ARGS__)
+#define MAKE_MAP_PROPERTY(property_name, ...) __make_map_property(property_name, __VA_ARGS__)
+#define MAKE_MAP_KEYS_SEQ(property_name, ...) __make_map_keys_seq(property_name, __VA_ARGS__)
 #define EXTENSION(x) __extension x
 #define EXTEND __extension
 #else
@@ -466,6 +476,8 @@ typedef struct _object PyObject;
 #define MAKE_PROPERTY2(property_name, ...)
 #define MAKE_SEQ(seq_name, num_name, element_name)
 #define MAKE_SEQ_PROPERTY(property_name, ...)
+#define MAKE_MAP_PROPERTY(property_name, ...)
+#define MAKE_MAP_KEYS_SEQ(property_name, ...)
 #define EXTENSION(x)
 #define EXTEND
 #endif

+ 13 - 16
dtool/src/dtoolbase/dtoolbase_cc.h

@@ -34,9 +34,9 @@ using namespace std;
 #define ALWAYS_INLINE inline
 #define TYPENAME typename
 #define CONSTEXPR constexpr
+#define ALWAYS_INLINE_CONSTEXPR constexpr
 #define NOEXCEPT noexcept
 #define FINAL final
-#define OVERRIDE override
 #define MOVE(x) x
 #define DEFAULT_CTOR = default
 #define DEFAULT_DTOR = default
@@ -166,7 +166,6 @@ template<class T> typename remove_reference<T>::type &&move(T &&t) {
 #  endif
 #  if __has_extension(cxx_override_control) && (__cplusplus >= 201103L)
 #    define FINAL final
-#    define OVERRIDE override
 #  endif
 #  if __has_extension(cxx_defaulted_functions)
 #     define DEFAULT_CTOR = default
@@ -176,13 +175,7 @@ template<class T> typename remove_reference<T>::type &&move(T &&t) {
 #  if __has_extension(cxx_deleted_functions)
 #     define DELETED = delete
 #  endif
-#elif defined(__GNUC__) && (__cplusplus >= 201103L) // GCC
-
-// GCC defines several macros which we can query.  List of all supported
-// builtin macros: https://gcc.gnu.org/projects/cxx-status.html
-#  if __cpp_constexpr >= 200704
-#    define CONSTEXPR constexpr
-#  endif
+#elif defined(__GNUC__) // GCC
 
 // Starting at GCC 4.4
 #  if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4)
@@ -194,15 +187,21 @@ template<class T> typename remove_reference<T>::type &&move(T &&t) {
 
 // Starting at GCC 4.6
 #  if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
+#    define CONSTEXPR constexpr
 #    define NOEXCEPT noexcept
 #    define USE_MOVE_SEMANTICS
-#    define FINAL final
 #    define MOVE(x) move(x)
 #  endif
 
 // Starting at GCC 4.7
 #  if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7)
-#    define OVERRIDE override
+#    define FINAL final
+#  endif
+
+// GCC defines several macros which we can query.  List of all supported
+// builtin macros: https://gcc.gnu.org/projects/cxx-status.html
+#  if !defined(CONSTEXPR) && __cpp_constexpr >= 200704
+#    define CONSTEXPR constexpr
 #  endif
 
 #elif defined(_MSC_VER) && _MSC_VER >= 1900 // Visual Studio 2015
@@ -210,11 +209,9 @@ template<class T> typename remove_reference<T>::type &&move(T &&t) {
 #  define NOEXCEPT noexcept
 #  define USE_MOVE_SEMANTICS
 #  define FINAL final
-#  define OVERRIDE override
 #  define MOVE(x) move(x)
 #elif defined(_MSC_VER) && _MSC_VER >= 1600 // Visual Studio 2010
 #  define NOEXCEPT throw()
-#  define OVERRIDE override
 #  define USE_MOVE_SEMANTICS
 #  define FINAL sealed
 #  define MOVE(x) move(x)
@@ -230,6 +227,9 @@ template<class T> typename remove_reference<T>::type &&move(T &&t) {
 // Fallbacks if features are not supported
 #ifndef CONSTEXPR
 #  define CONSTEXPR INLINE
+#  define ALWAYS_INLINE_CONSTEXPR ALWAYS_INLINE
+#else
+#  define ALWAYS_INLINE_CONSTEXPR ALWAYS_INLINE CONSTEXPR
 #endif
 #ifndef NOEXCEPT
 #  define NOEXCEPT
@@ -240,9 +240,6 @@ template<class T> typename remove_reference<T>::type &&move(T &&t) {
 #ifndef FINAL
 #  define FINAL
 #endif
-#ifndef OVERRIDE
-#  define OVERRIDE
-#endif
 #ifndef DEFAULT_CTOR
 #  define DEFAULT_CTOR {}
 #endif

+ 14 - 17
dtool/src/dtoolbase/memoryHook.cxx

@@ -47,7 +47,9 @@ static_assert(MEMORY_HOOK_ALIGNMENT * 8 >= NATIVE_WORDSIZE,
 static_assert((MEMORY_HOOK_ALIGNMENT & (MEMORY_HOOK_ALIGNMENT - 1)) == 0,
               "MEMORY_HOOK_ALIGNMENT should be a power of two");
 
-#if defined(USE_MEMORY_DLMALLOC)
+#if defined(CPPPARSER)
+
+#elif defined(USE_MEMORY_DLMALLOC)
 
 // Memory manager: DLMALLOC This is Doug Lea's memory manager.  It is very
 // fast, but it is not thread-safe.  However, we provide thread locking within
@@ -202,13 +204,11 @@ MemoryHook() {
 
 #endif  // WIN32
 
-#ifdef DO_MEMORY_USAGE
   _total_heap_single_size = 0;
   _total_heap_array_size = 0;
   _requested_heap_size = 0;
   _total_mmap_size = 0;
   _max_heap_size = ~(size_t)0;
-#endif
 }
 
 /**
@@ -216,19 +216,16 @@ MemoryHook() {
  */
 MemoryHook::
 MemoryHook(const MemoryHook &copy) :
-  _page_size(copy._page_size)
-{
-#ifdef DO_MEMORY_USAGE
-  _total_heap_single_size = copy._total_heap_single_size;
-  _total_heap_array_size = copy._total_heap_array_size;
-  _requested_heap_size = copy._requested_heap_size;
-  _total_mmap_size = copy._total_mmap_size;
-  _max_heap_size = copy._max_heap_size;
-#endif
-
-  ((MutexImpl &)copy._lock).acquire();
+  _page_size(copy._page_size),
+  _total_heap_single_size(copy._total_heap_single_size),
+  _total_heap_array_size(copy._total_heap_array_size),
+  _requested_heap_size(copy._requested_heap_size),
+  _total_mmap_size(copy._total_mmap_size),
+  _max_heap_size(copy._max_heap_size) {
+
+  copy._lock.acquire();
   _deleted_chains = copy._deleted_chains;
-  ((MutexImpl &)copy._lock).release();
+  copy._lock.release();
 }
 
 /**
@@ -631,7 +628,6 @@ alloc_fail(size_t attempted_size) {
   abort();
 }
 
-#ifdef DO_MEMORY_USAGE
 /**
  * This callback method is called whenever the total allocated heap size
  * exceeds _max_heap_size.  It's mainly intended for reporting memory leaks,
@@ -642,6 +638,7 @@ alloc_fail(size_t attempted_size) {
  */
 void MemoryHook::
 overflow_heap_size() {
+#ifdef DO_MEMORY_USAGE
   _max_heap_size = ~(size_t)0;
-}
 #endif  // DO_MEMORY_USAGE
+}

+ 1 - 3
dtool/src/dtoolbase/memoryHook.h

@@ -67,7 +67,6 @@ public:
 
   INLINE static size_t get_ptr_size(void *ptr);
 
-#ifdef DO_MEMORY_USAGE
 protected:
   TVOLATILE AtomicAdjust::Integer _total_heap_single_size;
   TVOLATILE AtomicAdjust::Integer _total_heap_array_size;
@@ -79,7 +78,6 @@ protected:
   size_t _max_heap_size;
 
   virtual void overflow_heap_size();
-#endif  // DO_MEMORY_USAGE
 
 private:
   size_t _page_size;
@@ -87,7 +85,7 @@ private:
   typedef map<size_t, DeletedBufferChain *> DeletedChains;
   DeletedChains _deleted_chains;
 
-  MutexImpl _lock;
+  mutable MutexImpl _lock;
 };
 
 #include "memoryHook.I"

+ 0 - 2
dtool/src/dtoolbase/typeRegistryNode.cxx

@@ -26,9 +26,7 @@ TypeRegistryNode(TypeHandle handle, const string &name, TypeHandle &ref) :
   _handle(handle), _name(name), _ref(ref)
 {
   clear_subtree();
-#ifdef DO_MEMORY_USAGE
   memset(_memory_usage, 0, sizeof(_memory_usage));
-#endif
 }
 
 /**

+ 0 - 2
dtool/src/dtoolbase/typeRegistryNode.h

@@ -47,9 +47,7 @@ public:
   Classes _parent_classes;
   Classes _child_classes;
 
-#ifdef DO_MEMORY_USAGE
   AtomicAdjust::Integer _memory_usage[TypeHandle::MC_limit];
-#endif
 
   static bool _paranoid_inheritance;
 

+ 16 - 0
dtool/src/dtoolutil/executionEnvironment.cxx

@@ -341,6 +341,22 @@ ns_get_environment_variable(const string &var) const {
     }
   }
 
+#elif !defined(__APPLE__)
+  // Similarly, we define fallbacks on POSIX systems for the variables defined
+  // in the XDG Base Directory specification, so that they can be safely used
+  // in Config.prc files.
+  if (var == "XDG_CONFIG_HOME") {
+    Filename home_dir = Filename::get_home_directory();
+    return home_dir.get_fullpath() + "/.config";
+
+  } else if (var == "XDG_CACHE_HOME") {
+    Filename home_dir = Filename::get_home_directory();
+    return home_dir.get_fullpath() + "/.cache";
+
+  } else if (var == "XDG_DATA_HOME") {
+    Filename home_dir = Filename::get_home_directory();
+    return home_dir.get_fullpath() + "/.local/share";
+  }
 #endif // _WIN32
 
   return string();

+ 9 - 0
dtool/src/dtoolutil/executionEnvironment.h

@@ -51,6 +51,15 @@ PUBLISHED:
 
   static Filename get_cwd();
 
+PUBLISHED:
+  MAKE_MAP_PROPERTY(environment_variables, has_environment_variable,
+                    get_environment_variable, set_environment_variable);
+
+  MAKE_SEQ_PROPERTY(args, get_num_args, get_arg);
+  MAKE_PROPERTY(binary_name, get_binary_name, set_binary_name);
+  MAKE_PROPERTY(dtool_name, get_dtool_name, set_dtool_name);
+  MAKE_PROPERTY(cwd, get_cwd);
+
 private:
   bool ns_has_environment_variable(const string &var) const;
   string ns_get_environment_variable(const string &var) const;

+ 11 - 4
dtool/src/dtoolutil/filename.cxx

@@ -600,8 +600,14 @@ get_user_appdata_directory() {
     user_appdata_directory.set_basename("files");
 
 #else
-    // Posix case.
-    user_appdata_directory = get_home_directory();
+    // Posix case.  We follow the XDG base directory spec.
+    struct stat st;
+    const char *datadir = getenv("XDG_DATA_HOME");
+    if (datadir != nullptr && stat(datadir, &st) == 0 && S_ISDIR(st.st_mode)) {
+      user_appdata_directory = datadir;
+    } else {
+      user_appdata_directory = Filename(get_home_directory(), ".local/share");
+    }
 
 #endif  // WIN32
 
@@ -649,9 +655,10 @@ get_common_appdata_directory() {
     common_appdata_directory.set_dirname(_internal_data_dir);
     common_appdata_directory.set_basename("files");
 
+#elif defined(__FreeBSD__)
+    common_appdata_directory = "/usr/local/share";
 #else
-    // Posix case.
-    common_appdata_directory = "/var";
+    common_appdata_directory = "/usr/share";
 #endif  // WIN32
 
     if (common_appdata_directory.empty()) {

+ 1 - 1
dtool/src/dtoolutil/filename_ext.cxx

@@ -55,7 +55,7 @@ __init__(PyObject *path) {
 
   if (Py_TYPE(path) == &Dtool_Filename._PyType) {
     // Copy constructor.
-    (*_this) = *((Filename *)((Dtool_PyInstDef *)path)->_ptr_to_object);
+    *_this = *(Filename *)DtoolInstance_VOID_PTR(path);
     return;
   }
 

+ 5 - 5
dtool/src/dtoolutil/load_dso.cxx

@@ -47,20 +47,20 @@ load_dso(const DSearchPath &path, const Filename &filename) {
   if (!abspath.is_regular_file()) {
     return NULL;
   }
-  string os_specific = abspath.to_os_specific();
+  wstring os_specific_w = abspath.to_os_specific_w();
 
   // Try using LoadLibraryEx, if possible.
-  typedef HMODULE (WINAPI *tLoadLibraryEx)(LPCTSTR, HANDLE, DWORD);
+  typedef HMODULE (WINAPI *tLoadLibraryEx)(LPCWSTR, HANDLE, DWORD);
   tLoadLibraryEx pLoadLibraryEx;
   HINSTANCE hLib = LoadLibrary("kernel32.dll");
   if (hLib) {
-    pLoadLibraryEx = (tLoadLibraryEx)GetProcAddress(hLib, "LoadLibraryExA");
+    pLoadLibraryEx = (tLoadLibraryEx)GetProcAddress(hLib, "LoadLibraryExW");
     if (pLoadLibraryEx) {
-      return pLoadLibraryEx(os_specific.c_str(), NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
+      return pLoadLibraryEx(os_specific_w.c_str(), NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
     }
   }
 
-  return LoadLibrary(os_specific.c_str());
+  return LoadLibraryW(os_specific_w.c_str());
 }
 
 bool

+ 3 - 3
dtool/src/dtoolutil/pandaFileStream.h

@@ -29,7 +29,7 @@
 class EXPCL_DTOOL IFileStream : public istream {
 PUBLISHED:
   INLINE IFileStream();
-  INLINE IFileStream(const char *filename, ios::openmode mode = ios::in);
+  INLINE explicit IFileStream(const char *filename, ios::openmode mode = ios::in);
   INLINE ~IFileStream();
 
   INLINE void open(const char *filename, ios::openmode mode = ios::in);
@@ -57,7 +57,7 @@ private:
 class EXPCL_DTOOL OFileStream : public ostream {
 PUBLISHED:
   INLINE OFileStream();
-  INLINE OFileStream(const char *filename, ios::openmode mode = ios::out);
+  INLINE explicit OFileStream(const char *filename, ios::openmode mode = ios::out);
   INLINE ~OFileStream();
 
   INLINE void open(const char *filename, ios::openmode mode = ios::out);
@@ -86,7 +86,7 @@ private:
 class EXPCL_DTOOL FileStream : public iostream {
 PUBLISHED:
   INLINE FileStream();
-  INLINE FileStream(const char *filename, ios::openmode mode = ios::in);
+  INLINE explicit FileStream(const char *filename, ios::openmode mode = ios::in);
   INLINE ~FileStream();
 
   INLINE void open(const char *filename, ios::openmode mode = ios::in);

+ 15 - 0
dtool/src/dtoolutil/pandaSystem.h

@@ -48,6 +48,21 @@ PUBLISHED:
 
   static string get_platform();
 
+  MAKE_PROPERTY(version_string, get_version_string);
+  MAKE_PROPERTY(major_version, get_major_version);
+  MAKE_PROPERTY(minor_version, get_minor_version);
+  MAKE_PROPERTY(sequence_version, get_sequence_version);
+  MAKE_PROPERTY(official_version, is_official_version);
+
+  MAKE_PROPERTY(memory_alignment, get_memory_alignment);
+
+  MAKE_PROPERTY(distributor, get_distributor);
+  MAKE_PROPERTY(compiler, get_compiler);
+  MAKE_PROPERTY(build_date, get_build_date);
+  MAKE_PROPERTY(git_commit, get_git_commit);
+
+  MAKE_PROPERTY(platform, get_platform);
+
   bool has_system(const string &system) const;
   size_t get_num_systems() const;
   string get_system(size_t n) const;

+ 1 - 0
dtool/src/dtoolutil/textEncoder.h

@@ -46,6 +46,7 @@ PUBLISHED:
 
   INLINE static void set_default_encoding(Encoding encoding);
   INLINE static Encoding get_default_encoding();
+  MAKE_PROPERTY(default_encoding, get_default_encoding, set_default_encoding);
 
   INLINE void set_text(const string &text);
   INLINE void set_text(const string &text, Encoding encoding);

+ 28 - 0
dtool/src/interrogate/README.md

@@ -0,0 +1,28 @@
+A key advantage of Panda3D is that it provides developers with the
+ability to use both C++ and Python simultaneously. Essentially, Panda3D
+gives programmers the best of both worlds, as they are able to take
+advantage of the high performance and low-level programming found in
+C++ in addition to the flexibility, interactive scripting, and
+rapid-prototyping capabilities of Python. This feature is made possible
+due to Python’s ability to call C libraries, and ultimately make use of
+Panda3D’s Interrogate System: an automated C++ Extension Module
+generation utility similar to SWIG.  Although Python is the favored
+scripting language of Panda3D, the engine is highly extensible in this
+aspect, as any language that has a foreign function interface can make
+use of the Interrogate System.
+
+The Interrogate System works like a compiler by scanning and parsing
+C++ code for the Panda3D-specific, “PUBLISHED” keyword. This keyword
+marks the particular methods of a class that are to be exposed within a
+C++ Extension Module for that class which is eventually generated. One
+benefit of using the “PUBLISHED” keyword is that it alleviates the need
+for an interface file that provides function prototypes for the class
+methods that will be exposed within the extension module, as is the
+case with SWIG. Interrogate turns a class into a loose collection of
+Python interface wrapper functions that make up the C++ Extension
+Module.
+
+This package depends on the 'cppparser' package, which contains the
+code that parses the C++ headers, and the 'interrogatedb' package,
+which contains the intermediate representation of the interfaces that
+can be saved to a database file for FFI generation tools to consume.

+ 17 - 11
dtool/src/interrogate/functionRemap.cxx

@@ -772,16 +772,14 @@ setup_properties(const InterrogateFunction &ifunc, InterfaceMaker *interface_mak
     first_param = 1;
   }
 
-  if (_has_this || _type == T_constructor) {
-    if (_parameters.size() > (size_t)first_param && _parameters[first_param]._name == "self" &&
-        TypeManager::is_pointer_to_PyObject(_parameters[first_param]._remap->get_orig_type())) {
-      // Here's a special case.  If the first parameter of a nonstatic method
-      // is a PyObject * called "self", then we will automatically fill it in
-      // from the this pointer, and remove it from the generated parameter
-      // list.
-      _parameters.erase(_parameters.begin() + first_param);
-      _flags |= F_explicit_self;
-    }
+  if (_parameters.size() > (size_t)first_param && _parameters[first_param]._name == "self" &&
+      TypeManager::is_pointer_to_PyObject(_parameters[first_param]._remap->get_orig_type())) {
+    // Here's a special case.  If the first parameter of a nonstatic method
+    // is a PyObject * called "self", then we will automatically fill it in
+    // from the this pointer, and remove it from the generated parameter
+    // list.
+    _parameters.erase(_parameters.begin() + first_param);
+    _flags |= F_explicit_self;
   }
 
   if ((int)_parameters.size() == first_param) {
@@ -799,6 +797,7 @@ setup_properties(const InterrogateFunction &ifunc, InterfaceMaker *interface_mak
         (_parameters[first_param + 1]._name == "kwargs" ||
           _parameters[first_param + 1]._name == "kwds")) {
       _flags |= F_explicit_args;
+      _args_type = InterfaceMaker::AT_keyword_args;
     }
   }
 
@@ -848,7 +847,7 @@ setup_properties(const InterrogateFunction &ifunc, InterfaceMaker *interface_mak
       }
 
     } else if (fname == "__iter__") {
-      if (_has_this && _parameters.size() == 1 &&
+      if ((int)_parameters.size() == first_param &&
           TypeManager::is_pointer(_return_type->get_new_type())) {
         // It receives no parameters, and returns a pointer.
         _flags |= F_iter;
@@ -911,6 +910,13 @@ setup_properties(const InterrogateFunction &ifunc, InterfaceMaker *interface_mak
             break;
           }
         }
+      } else if (_args_type == InterfaceMaker::AT_single_arg) {
+        // If it takes an argument named "args", we are directly passing the
+        // "args" tuple to the function.
+        if (_parameters[first_param]._name == "args") {
+          _flags |= F_explicit_args;
+          _args_type = InterfaceMaker::AT_varargs;
+        }
       }
     }
     break;

+ 10 - 9
dtool/src/interrogate/interfaceMaker.cxx

@@ -75,8 +75,8 @@ InterfaceMaker::MakeSeq::
 MakeSeq(const string &name, const InterrogateMakeSeq &imake_seq) :
   _name(name),
   _imake_seq(imake_seq),
-  _length_getter(NULL),
-  _element_getter(NULL)
+  _length_getter(nullptr),
+  _element_getter(nullptr)
 {
 }
 
@@ -86,12 +86,13 @@ MakeSeq(const string &name, const InterrogateMakeSeq &imake_seq) :
 InterfaceMaker::Property::
 Property(const InterrogateElement &ielement) :
   _ielement(ielement),
-  _length_function(NULL),
-  _getter(NULL),
-  _setter(NULL),
-  _has_function(NULL),
-  _clear_function(NULL),
-  _deleter(NULL)
+  _length_function(nullptr),
+  _has_function(nullptr),
+  _clear_function(nullptr),
+  _deleter(nullptr),
+  _inserter(nullptr),
+  _getkey_function(nullptr),
+  _has_this(false)
 {
 }
 
@@ -615,7 +616,7 @@ record_function(const InterrogateType &itype, FunctionIndex func_index) {
 
           // If *any* of the variants of this function has a "this" pointer,
           // the entire set of functions is deemed to have a "this" pointer.
-          if (remap->_has_this) {
+          if (remap->_has_this || (remap->_flags & FunctionRemap::F_explicit_self) != 0) {
             func->_has_this = true;
           }
 

+ 5 - 2
dtool/src/interrogate/interfaceMaker.h

@@ -126,12 +126,15 @@ public:
     Property(const InterrogateElement &ielement);
 
     const InterrogateElement &_ielement;
+    vector<FunctionRemap *> _getter_remaps;
+    vector<FunctionRemap *> _setter_remaps;
     Function *_length_function;
-    Function *_getter;
-    Function *_setter;
     Function *_has_function;
     Function *_clear_function;
     Function *_deleter;
+    Function *_inserter;
+    Function *_getkey_function;
+    bool _has_this;
   };
   typedef vector<Property *> Properties;
 

+ 1 - 1
dtool/src/interrogate/interfaceMakerPython.cxx

@@ -51,7 +51,7 @@ test_assert(ostream &out, int indent_level) const {
     indent(out, indent_level)
       << "Notify *notify = Notify::ptr();\n";
     indent(out, indent_level)
-      << "if (notify->has_assert_failed()) {\n";
+      << "if (UNLIKELY(notify->has_assert_failed())) {\n";
     indent(out, indent_level + 2)
       << "PyErr_SetString(PyExc_AssertionError, notify->get_assert_error_message().c_str());\n";
     indent(out, indent_level + 2)

Plik diff jest za duży
+ 458 - 275
dtool/src/interrogate/interfaceMakerPythonNative.cxx


+ 4 - 0
dtool/src/interrogate/interfaceMakerPythonNative.h

@@ -47,6 +47,7 @@ public:
   virtual bool separate_overloading();
 
   virtual Object *record_object(TypeIndex type_index);
+  Property *record_property(const InterrogateType &itype, ElementIndex element_index);
 
 protected:
   virtual string get_wrapper_prefix();
@@ -111,6 +112,9 @@ private:
 
     // Decref temporary args object before returning.
     RF_decref_args = 0x1000,
+
+    // This raises a KeyError on falsey (or -1) return value.
+    RF_raise_keyerror = 0x4000,
   };
 
   class SlottedFunctionDef {

+ 169 - 37
dtool/src/interrogate/interrogateBuilder.cxx

@@ -1798,30 +1798,39 @@ get_make_property(CPPMakeProperty *make_property, CPPStructType *struct_type, CP
   }
 
   string property_name = make_property->get_local_name(&parser);
+  InterrogateDatabase *idb = InterrogateDatabase::get_ptr();
 
   // First, check to see if it's already there.
+  ElementIndex index = 0;
   PropertiesByName::const_iterator tni =
     _properties_by_name.find(property_name);
   if (tni != _properties_by_name.end()) {
-    ElementIndex index = (*tni).second;
-    return index;
+    index = (*tni).second;
+    const InterrogateElement &ielem = idb->get_element(index);
+    if (ielem._make_property == make_property) {
+      // This is the same property.
+      return index;
+    }
+
+    // It is possible to have property definitions with the same name, but
+    // they cannot define conflicting interfaces.
+    if ((ielem.is_sequence() || ielem.is_mapping()) !=
+        (make_property->_type != CPPMakeProperty::T_normal)) {
+      cerr << "Conflicting property definitions for " << property_name << "!\n";
+      return index;
+    }
   }
 
   // If we have a length function (ie. this is a sequence property), we should
   // find the function that will give us the length.
   FunctionIndex length_function = 0;
-  bool is_seq = false;
-
-  CPPFunctionGroup::Instances::const_iterator fi;
   CPPFunctionGroup *fgroup = make_property->_length_function;
-  if (fgroup != NULL) {
-    is_seq = true;
-
+  if (fgroup != nullptr) {
+    CPPFunctionGroup::Instances::const_iterator fi;
     for (fi = fgroup->_instances.begin(); fi != fgroup->_instances.end(); ++fi) {
       CPPInstance *function = (*fi);
-      CPPFunctionType *ftype =
-        function->_type->as_function_type();
-      if (ftype != NULL) {
+      CPPFunctionType *ftype = function->_type->as_function_type();
+      if (ftype != nullptr) {
         length_function = get_function(function, "", struct_type,
                                        struct_type->get_scope(), 0);
         if (length_function != 0) {
@@ -1840,6 +1849,9 @@ get_make_property(CPPMakeProperty *make_property, CPPStructType *struct_type, CP
   CPPInstance *getter = NULL;
   CPPType *return_type = NULL;
 
+  // How many arguments we expect the getter to have.
+  size_t num_args = (size_t)(make_property->_type != CPPMakeProperty::T_normal);
+
   fgroup = make_property->_get_function;
   if (fgroup != NULL) {
     CPPFunctionGroup::Instances::const_iterator fi;
@@ -1850,12 +1862,29 @@ get_make_property(CPPMakeProperty *make_property, CPPStructType *struct_type, CP
         continue;
       }
 
+      const CPPParameterList::Parameters &params = ftype->_parameters->_parameters;
+
+      size_t expected_num_args = 0;
+      size_t index_arg = 0;
+
+      if (make_property->_type != CPPMakeProperty::T_normal) {
+        ++expected_num_args;
+      }
+
+      if (!params.empty() && params[0]->get_simple_name() == "self" &&
+          TypeManager::is_pointer_to_PyObject(params[0]->_type)) {
+        // Taking a PyObject *self argument.
+        expected_num_args += 1;
+        index_arg += 1;
+      }
+
       // The getter must either take no arguments, or all defaults.
-      if (ftype->_parameters->_parameters.size() == (size_t)is_seq ||
-          (ftype->_parameters->_parameters.size() > (size_t)is_seq &&
-           ftype->_parameters->_parameters[(size_t)is_seq]->_initializer != NULL)) {
+      if (params.size() == expected_num_args ||
+          (params.size() > expected_num_args &&
+           params[expected_num_args]->_initializer != NULL)) {
         // If this is a sequence getter, it must take an index argument.
-        if (is_seq && !TypeManager::is_integer(ftype->_parameters->_parameters[0]->_type)) {
+        if (make_property->_type == CPPMakeProperty::T_sequence &&
+            !TypeManager::is_integer(params[index_arg]->_type)) {
           continue;
         }
 
@@ -1887,13 +1916,14 @@ get_make_property(CPPMakeProperty *make_property, CPPStructType *struct_type, CP
       CPPInstance *function = (*fi);
       CPPFunctionType *ftype =
         function->_type->as_function_type();
-      if (ftype != NULL && TypeManager::is_bool(ftype->_return_type)) {
+      if (ftype != nullptr && (TypeManager::is_integer(ftype->_return_type) ||
+                               TypeManager::is_pointer(ftype->_return_type))) {
         hasser = function;
         break;
       }
     }
 
-    if (hasser == NULL || return_type == NULL) {
+    if (hasser == nullptr) {
       cerr << "No instance of has-function '"
            << fgroup->_name << "' is suitable!\n";
       return 0;
@@ -1909,44 +1939,127 @@ get_make_property(CPPMakeProperty *make_property, CPPStructType *struct_type, CP
     for (fi = fgroup->_instances.begin(); fi != fgroup->_instances.end(); ++fi) {
       CPPInstance *function = (*fi);
       CPPFunctionType *ftype = function->_type->as_function_type();
-      if (ftype != NULL && ftype->_parameters->_parameters.size() == (size_t)is_seq) {
-        deleter = function;
-        break;
+      if (ftype != nullptr) {
+        const CPPParameterList::Parameters &params = ftype->_parameters->_parameters;
+        if (params.size() == num_args ||
+            (params.size() > num_args && params[num_args]->_initializer != nullptr)) {
+          deleter = function;
+          break;
+        }
       }
     }
 
-    if (deleter == NULL || return_type == NULL) {
+    if (deleter == nullptr) {
       cerr << "No instance of delete-function '"
            << fgroup->_name << "' is suitable!\n";
       return 0;
     }
   }
 
-  InterrogateDatabase *idb = InterrogateDatabase::get_ptr();
-  // It isn't here, so we'll have to define it.
-  ElementIndex index = idb->get_next_index();
-  _properties_by_name[property_name] = index;
+  // And the "inserter".
+  CPPInstance *inserter = nullptr;
+
+  fgroup = make_property->_insert_function;
+  if (fgroup != nullptr) {
+    CPPFunctionGroup::Instances::const_iterator fi;
+    for (fi = fgroup->_instances.begin(); fi != fgroup->_instances.end(); ++fi) {
+      CPPInstance *function = (*fi);
+      CPPFunctionType *ftype = function->_type->as_function_type();
+      if (ftype != nullptr && ftype->_parameters->_parameters.size() == 2) {
+        inserter = function;
+        break;
+      }
+    }
+
+    if (inserter == nullptr) {
+      cerr << "No instance of insert-function '"
+           << fgroup->_name << "' is suitable!\n";
+      return 0;
+    }
+  }
+
+  // And the function that returns a key by index.
+  CPPInstance *getkey_function = nullptr;
 
-  InterrogateElement iproperty;
-  iproperty._name = make_property->get_simple_name();
-  iproperty._scoped_name = descope(make_property->get_local_name(&parser));
+  fgroup = make_property->_get_key_function;
+  if (fgroup != nullptr) {
+    CPPFunctionGroup::Instances::const_iterator fi;
+    for (fi = fgroup->_instances.begin(); fi != fgroup->_instances.end(); ++fi) {
+      CPPInstance *function = (*fi);
+      CPPFunctionType *ftype = function->_type->as_function_type();
+      if (ftype != nullptr) {
+        getkey_function = function;
+        break;
+      }
+    }
+
+    if (getkey_function == nullptr) {
+      cerr << "No instance of get-key-function '"
+           << fgroup->_name << "' is suitable!\n";
+      return 0;
+    }
+  }
+
+  if (index == 0) {
+    // It isn't here, so we'll have to define it.
+    index = idb->get_next_index();
+    _properties_by_name[property_name] = index;
+
+    InterrogateElement iproperty;
+    iproperty._name = make_property->get_simple_name();
+    iproperty._scoped_name = descope(make_property->get_local_name(&parser));
+    idb->add_element(index, iproperty);
+  }
+
+  InterrogateElement &iproperty = idb->update_element(index);
 
   if (return_type != NULL) {
-    iproperty._type = get_type(TypeManager::unwrap_reference(return_type), false);
+    TypeIndex return_index = get_type(TypeManager::unwrap_reference(return_type), false);
+    if (iproperty._type != 0 && iproperty._type != return_index) {
+      cerr << "Property " << property_name << " has inconsistent element type!\n";
+    }
   } else {
     iproperty._type = 0;
   }
 
-  if (length_function != 0) {
+  if (make_property->_type & CPPMakeProperty::T_sequence) {
     iproperty._flags |= InterrogateElement::F_sequence;
     iproperty._length_function = length_function;
+    assert(length_function != 0);
   }
 
-  if (getter != NULL) {
-    iproperty._flags |= InterrogateElement::F_has_getter;
-    iproperty._getter = get_function(getter, "", struct_type,
-                                     struct_type->get_scope(), 0);
-    nassertr(iproperty._getter, 0);
+  if (make_property->_type & CPPMakeProperty::T_mapping) {
+    iproperty._flags |= InterrogateElement::F_mapping;
+    iproperty._length_function = length_function;
+  }
+
+  if (make_property->_type == CPPMakeProperty::T_normal) {
+    if (getter != NULL) {
+      iproperty._flags |= InterrogateElement::F_has_getter;
+      iproperty._getter = get_function(getter, "", struct_type,
+                                      struct_type->get_scope(), 0);
+      nassertr(iproperty._getter, 0);
+    }
+  } else {
+    // We could have a mixed sequence/mapping property, so synthesize a
+    // getitem function.  We don't really care what's in here; we just use
+    // this to store the remaps.
+    if (!iproperty.has_getter()) {
+      iproperty._flags |= InterrogateElement::F_has_getter;
+      iproperty._getter = InterrogateDatabase::get_ptr()->get_next_index();
+      InterrogateFunction *ifunction = new InterrogateFunction;
+      ifunction->_instances = new InterrogateFunction::Instances;
+      InterrogateDatabase::get_ptr()->add_function(iproperty._getter, ifunction);
+    }
+
+    // Add our getter to the generated getitem function.
+    string signature = TypeManager::get_function_signature(getter);
+    InterrogateFunction &ifunction =
+      InterrogateDatabase::get_ptr()->update_function(iproperty._getter);
+    if (ifunction._instances == nullptr) {
+      ifunction._instances = new InterrogateFunction::Instances;
+    }
+    ifunction._instances->insert(InterrogateFunction::Instances::value_type(signature, getter));
   }
 
   if (hasser != NULL) {
@@ -1963,6 +2076,20 @@ get_make_property(CPPMakeProperty *make_property, CPPStructType *struct_type, CP
     nassertr(iproperty._del_function, 0);
   }
 
+  if (inserter != NULL) {
+    iproperty._flags |= InterrogateElement::F_has_insert_function;
+    iproperty._insert_function = get_function(inserter, "", struct_type,
+                                              struct_type->get_scope(), 0);
+    nassertr(iproperty._insert_function, 0);
+  }
+
+  if (getkey_function != NULL) {
+    iproperty._flags |= InterrogateElement::F_has_getkey_function;
+    iproperty._getkey_function = get_function(getkey_function, "", struct_type,
+                                              struct_type->get_scope(), 0);
+    nassertr(iproperty._getkey_function, 0);
+  }
+
   // See if there happens to be a comment before the MAKE_PROPERTY macro.
   if (make_property->_leading_comment != (CPPCommentBlock *)NULL) {
     iproperty._comment = trim_blanks(make_property->_leading_comment->_comment);
@@ -1999,7 +2126,6 @@ get_make_property(CPPMakeProperty *make_property, CPPStructType *struct_type, CP
     }
   }
 
-  idb->add_element(index, iproperty);
   return index;
 }
 
@@ -2428,6 +2554,10 @@ define_struct_type(InterrogateType &itype, CPPStructType *cpptype,
     break;
   }
 
+  if (cpptype->is_final()) {
+    itype._flags |= InterrogateType::F_final;
+  }
+
   if (cpptype->_file.is_c_file()) {
     // This type declaration appears in a .C file.  We can only export types
     // defined in a .h file.
@@ -2605,7 +2735,9 @@ define_struct_type(InterrogateType &itype, CPPStructType *cpptype,
 
     } else if ((*di)->get_subtype() == CPPDeclaration::ST_make_property) {
       ElementIndex element_index = get_make_property((*di)->as_make_property(), cpptype, scope);
-      itype._elements.push_back(element_index);
+      if (find(itype._elements.begin(), itype._elements.end(), element_index) == itype._elements.end()) {
+        itype._elements.push_back(element_index);
+      }
 
     } else if ((*di)->get_subtype() == CPPDeclaration::ST_make_seq) {
       MakeSeqIndex make_seq_index = get_make_seq((*di)->as_make_seq(), cpptype);

+ 0 - 52
dtool/src/interrogate/typeManager.cxx

@@ -2517,55 +2517,3 @@ is_local(CPPType *source_type) {
  return false;
  */
 }
-
-/**
- * Returns true if the type is trivial (or trivial enough for our purposes).
- */
-bool TypeManager::
-is_trivial(CPPType *source_type) {
-  switch (source_type->get_subtype()) {
-  case CPPDeclaration::ST_const:
-    return is_trivial(source_type->as_const_type()->_wrapped_around);
-
-  case CPPDeclaration::ST_reference:
-    return false;
-
-  case CPPDeclaration::ST_pointer:
-    return true;
-
-  case CPPDeclaration::ST_simple:
-    return true;
-
-  case CPPDeclaration::ST_typedef:
-    return is_trivial(source_type->as_typedef_type()->_type);
-
-  default:
-    if (source_type->is_trivial() || is_handle(source_type)) {
-      return true;
-    } else {
-      // This is a bit of a hack.  is_trivial() returns false for types that
-      // have an empty constructor (since we can't use =default yet). For the
-      // other classes, it's just convenient to consider them trivial even if
-      // they aren't, since they are simple enough.
-      string name = source_type->get_simple_name();
-      return (name == "ButtonHandle" || name == "DatagramIterator" ||
-              name == "BitMask" || name == "Filename" || name == "pixel" ||
-              name == "NodePath" || name == "LoaderOptions" ||
-              name == "PointerToArray" || name == "ConstPointerToArray" ||
-              name == "PStatThread" ||
-              (name.size() >= 6 && name.substr(0, 6) == "LPlane") ||
-              (name.size() > 6 && name.substr(0, 6) == "LPoint") ||
-              (name.size() > 7 && name.substr(0, 7) == "LVector") ||
-              (name.size() > 7 && name.substr(0, 7) == "LMatrix") ||
-              (name.size() > 8 && name.substr(0, 8) == "LVecBase") ||
-              (name.size() >= 9 && name.substr(0, 9) == "LParabola") ||
-              (name.size() >= 9 && name.substr(0, 9) == "LRotation") ||
-              (name.size() >= 11 && name.substr(0, 11) == "LQuaternion") ||
-              (name.size() >= 12 && name.substr(0, 12) == "LOrientation") ||
-              (name.size() > 16 && name.substr(0, 16) == "UnalignedLMatrix") ||
-              (name.size() > 17 && name.substr(0, 17) == "UnalignedLVecBase"));
-    }
-  }
-
-  return false;
-}

+ 0 - 1
dtool/src/interrogate/typeManager.h

@@ -149,7 +149,6 @@ public:
 
   static bool is_exported(CPPType *type);
   static bool is_local(CPPType *type);
-  static bool is_trivial(CPPType *type);
 };
 
 #endif

+ 1 - 1
dtool/src/interrogatedb/interrogateDatabase.cxx

@@ -20,7 +20,7 @@ InterrogateDatabase *InterrogateDatabase::_global_ptr = NULL;
 int InterrogateDatabase::_file_major_version = 0;
 int InterrogateDatabase::_file_minor_version = 0;
 int InterrogateDatabase::_current_major_version = 3;
-int InterrogateDatabase::_current_minor_version = 2;
+int InterrogateDatabase::_current_minor_version = 3;
 
 /**
  *

+ 46 - 0
dtool/src/interrogatedb/interrogateElement.I

@@ -25,7 +25,10 @@ InterrogateElement(InterrogateModuleDef *def) :
   _has_function = 0;
   _clear_function = 0;
   _del_function = 0;
+  _insert_function = 0;
+  _getkey_function = 0;
   _length_function = 0;
+  _make_property = nullptr;
 }
 
 /**
@@ -51,7 +54,10 @@ operator = (const InterrogateElement &copy) {
   _has_function = copy._has_function;
   _clear_function = copy._clear_function;
   _del_function = copy._del_function;
+  _insert_function = copy._insert_function;
+  _getkey_function = copy._getkey_function;
   _length_function = copy._length_function;
+  _make_property = copy._make_property;
 }
 
 /**
@@ -183,6 +189,38 @@ get_del_function() const {
   return _del_function;
 }
 
+/**
+ *
+ */
+INLINE bool InterrogateElement::
+has_insert_function() const {
+  return (_flags & F_has_insert_function) != 0;
+}
+
+/**
+ *
+ */
+INLINE FunctionIndex InterrogateElement::
+get_insert_function() const {
+  return _insert_function;
+}
+
+/**
+ *
+ */
+INLINE bool InterrogateElement::
+has_getkey_function() const {
+  return (_flags & F_has_getkey_function) != 0;
+}
+
+/**
+ *
+ */
+INLINE FunctionIndex InterrogateElement::
+get_getkey_function() const {
+  return _getkey_function;
+}
+
 /**
  *
  */
@@ -199,6 +237,14 @@ get_length_function() const {
   return _length_function;
 }
 
+/**
+ *
+ */
+INLINE bool InterrogateElement::
+is_mapping() const {
+  return (_flags & F_mapping) != 0;
+}
+
 
 INLINE ostream &
 operator << (ostream &out, const InterrogateElement &element) {

+ 8 - 1
dtool/src/interrogatedb/interrogateElement.cxx

@@ -29,7 +29,9 @@ output(ostream &out) const {
       << _has_function << " "
       << _clear_function << " "
       << _del_function << " "
-      << _length_function << " ";
+      << _length_function << " "
+      << _insert_function << " "
+      << _getkey_function << " ";
   idf_output_string(out, _scoped_name);
   idf_output_string(out, _comment, '\n');
 }
@@ -45,6 +47,9 @@ input(istream &in) {
     in >> _has_function >> _clear_function;
     if (InterrogateDatabase::get_file_minor_version() >= 2) {
       in >> _del_function >> _length_function;
+      if (InterrogateDatabase::get_file_minor_version() >= 3) {
+        in >> _insert_function >> _getkey_function;
+      }
     }
   }
   idf_input_string(in, _scoped_name);
@@ -63,5 +68,7 @@ remap_indices(const IndexRemapper &remap) {
   _has_function = remap.map_from(_has_function);
   _clear_function = remap.map_from(_clear_function);
   _del_function = remap.map_from(_del_function);
+  _insert_function = remap.map_from(_insert_function);
+  _getkey_function = remap.map_from(_getkey_function);
   _length_function = remap.map_from(_length_function);
 }

+ 12 - 0
dtool/src/interrogatedb/interrogateElement.h

@@ -19,6 +19,7 @@
 #include "interrogateComponent.h"
 
 class IndexRemapper;
+class CPPMakeProperty;
 
 /**
  * An internal representation of a data element, like a data member or a
@@ -49,8 +50,13 @@ public:
   INLINE FunctionIndex get_clear_function() const;
   INLINE bool has_del_function() const;
   INLINE FunctionIndex get_del_function() const;
+  INLINE bool has_insert_function() const;
+  INLINE FunctionIndex get_insert_function() const;
+  INLINE bool has_getkey_function() const;
+  INLINE FunctionIndex get_getkey_function() const;
   INLINE bool is_sequence() const;
   INLINE FunctionIndex get_length_function() const;
+  INLINE bool is_mapping() const;
 
   void output(ostream &out) const;
   void input(istream &in);
@@ -67,6 +73,8 @@ private:
     F_has_del_function= 0x0020,
     F_sequence        = 0x0040,
     F_mapping         = 0x0080,
+    F_has_insert_function= 0x0100,
+    F_has_getkey_function= 0x0200,
   };
 
   int _flags;
@@ -79,6 +87,10 @@ private:
   FunctionIndex _has_function;
   FunctionIndex _clear_function;
   FunctionIndex _del_function;
+  FunctionIndex _insert_function;
+  FunctionIndex _getkey_function;
+
+  CPPMakeProperty *_make_property;
 
   friend class InterrogateBuilder;
 };

+ 8 - 0
dtool/src/interrogatedb/interrogateType.I

@@ -289,6 +289,14 @@ is_union() const {
   return (_flags & F_union) != 0;
 }
 
+/**
+ *
+ */
+INLINE bool InterrogateType::
+is_final() const {
+  return (_flags & F_final) != 0;
+}
+
 /**
  *
  */

+ 2 - 0
dtool/src/interrogatedb/interrogateType.h

@@ -75,6 +75,7 @@ public:
   INLINE bool is_struct() const;
   INLINE bool is_class() const;
   INLINE bool is_union() const;
+  INLINE bool is_final() const;
 
   INLINE bool is_fully_defined() const;
   INLINE bool is_unpublished() const;
@@ -139,6 +140,7 @@ private:
     F_typedef              = 0x200000,
     F_array                = 0x400000,
     F_scoped_enum          = 0x800000,
+    F_final                =0x1000000,
   };
 
 public:

+ 2 - 4
dtool/src/interrogatedb/p3interrogatedb_composite2.cxx

@@ -5,7 +5,5 @@
 #include "interrogate_interface.cxx"
 #include "interrogate_request.cxx"
 #include "py_panda.cxx"
-
-
-
-
+#include "py_compat.cxx"
+#include "py_wrappers.cxx"

+ 54 - 0
dtool/src/interrogatedb/py_compat.cxx

@@ -0,0 +1,54 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file py_compat.cxx
+ * @author rdb
+ * @date 2017-12-03
+ */
+
+#include "py_compat.h"
+
+#ifdef HAVE_PYTHON
+
+PyTupleObject Dtool_EmptyTuple = {PyVarObject_HEAD_INIT(nullptr, 0)};
+
+#if PY_MAJOR_VERSION < 3
+/**
+ * Given a long or int, returns a size_t, or raises an OverflowError if it is
+ * out of range.
+ */
+size_t PyLongOrInt_AsSize_t(PyObject *vv) {
+  if (PyInt_Check(vv)) {
+    long value = PyInt_AS_LONG(vv);
+    if (value < 0) {
+      PyErr_SetString(PyExc_OverflowError,
+                      "can't convert negative value to size_t");
+      return (size_t)-1;
+    }
+    return (size_t)value;
+  }
+
+  if (!PyLong_Check(vv)) {
+    Dtool_Raise_TypeError("a long or int was expected");
+    return (size_t)-1;
+  }
+
+  size_t bytes;
+  int one = 1;
+  int res = _PyLong_AsByteArray((PyLongObject *)vv, (unsigned char *)&bytes,
+                                SIZEOF_SIZE_T, (int)*(unsigned char*)&one, 0);
+
+  if (res < 0) {
+    return (size_t)res;
+  } else {
+    return bytes;
+  }
+}
+#endif
+
+#endif  // HAVE_PYTHON

+ 175 - 0
dtool/src/interrogatedb/py_compat.h

@@ -0,0 +1,175 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file py_compat.h
+ * @author rdb
+ * @date 2017-12-02
+ */
+
+#ifndef PY_COMPAT_H
+#define PY_COMPAT_H
+
+#include "dtoolbase.h"
+
+#ifdef HAVE_PYTHON
+
+// The contents of this file were originally part of py_panda.h.  It
+// specifically contains polyfills that are required to maintain compatibility
+// with Python 2 and older versions of Python 3.
+
+// These compatibility hacks are sorted by Python version that removes the
+// need for the respective hack.
+
+#ifdef _POSIX_C_SOURCE
+#  undef _POSIX_C_SOURCE
+#endif
+
+#ifdef _XOPEN_SOURCE
+#  undef _XOPEN_SOURCE
+#endif
+
+// See PEP 353
+#define PY_SSIZE_T_CLEAN 1
+
+#include "Python.h"
+
+/* Python 2.4 */
+
+// 2.4 macros which aren't available in 2.3
+#ifndef Py_RETURN_NONE
+#  define Py_RETURN_NONE return Py_INCREF(Py_None), Py_None
+#endif
+
+#ifndef Py_RETURN_TRUE
+#  define Py_RETURN_TRUE return Py_INCREF(Py_True), Py_True
+#endif
+
+#ifndef Py_RETURN_FALSE
+#  define Py_RETURN_FALSE return Py_INCREF(Py_False), Py_False
+#endif
+
+/* Python 2.5 */
+
+// Prior to Python 2.5, we didn't have Py_ssize_t.
+#if PY_VERSION_HEX < 0x02050000
+typedef int Py_ssize_t;
+#  define PyInt_FromSsize_t PyInt_FromLong
+#  define PyInt_AsSsize_t PyInt_AsLong
+#endif
+
+/* Python 2.6 */
+
+#ifndef Py_TYPE
+#  define Py_TYPE(ob) (((PyObject*)(ob))->ob_type)
+#endif
+
+/* Python 2.7, 3.1 */
+
+#ifndef PyVarObject_HEAD_INIT
+  #define PyVarObject_HEAD_INIT(type, size) \
+    PyObject_HEAD_INIT(type) size,
+#endif
+
+/* Python 2.7, 3.2 */
+
+#if PY_VERSION_HEX < 0x03020000
+#  define PyErr_NewExceptionWithDoc(name, doc, base, dict) \
+          PyErr_NewException(name, base, dict)
+#endif
+
+/* Python 3.0 */
+
+// Always on in Python 3
+#ifndef Py_TPFLAGS_CHECKTYPES
+#  define Py_TPFLAGS_CHECKTYPES 0
+#endif
+
+// Macros for writing code that will compile in both versions.
+#if PY_MAJOR_VERSION >= 3
+#  define nb_nonzero nb_bool
+#  define nb_divide nb_true_divide
+#  define nb_inplace_divide nb_inplace_true_divide
+
+#  define PyLongOrInt_Check(x) PyLong_Check(x)
+#  define PyLongOrInt_AS_LONG PyLong_AS_LONG
+#  define PyInt_Check PyLong_Check
+#  define PyInt_AsLong PyLong_AsLong
+#  define PyInt_AS_LONG PyLong_AS_LONG
+#  define PyLongOrInt_AsSize_t PyLong_AsSize_t
+#else
+#  define PyLongOrInt_Check(x) (PyInt_Check(x) || PyLong_Check(x))
+// PyInt_FromSize_t automatically picks the right type.
+#  define PyLongOrInt_AS_LONG PyInt_AsLong
+
+EXPCL_INTERROGATEDB size_t PyLongOrInt_AsSize_t(PyObject *);
+#endif
+
+// Which character to use in PyArg_ParseTuple et al for a byte string.
+#if PY_MAJOR_VERSION >= 3
+#  define FMTCHAR_BYTES "y"
+#else
+#  define FMTCHAR_BYTES "s"
+#endif
+
+/* Python 3.2 */
+
+#if PY_VERSION_HEX < 0x03020000
+typedef long Py_hash_t;
+#endif
+
+/* Python 3.3 */
+
+#if PY_MAJOR_VERSION >= 3
+// Python 3 versions before 3.3.3 defined this incorrectly.
+#  undef _PyErr_OCCURRED
+#  define _PyErr_OCCURRED() (PyThreadState_GET()->curexc_type)
+
+// Python versions before 3.3 did not define this.
+#  if PY_VERSION_HEX < 0x03030000
+#    define PyUnicode_AsUTF8 _PyUnicode_AsString
+#    define PyUnicode_AsUTF8AndSize _PyUnicode_AsStringAndSize
+#  endif
+#endif
+
+/* Python 3.6 */
+
+// Used to implement _PyObject_CallNoArg
+extern EXPCL_INTERROGATEDB PyTupleObject Dtool_EmptyTuple;
+
+#ifndef _PyObject_CallNoArg
+#  define _PyObject_CallNoArg(func) PyObject_Call((func), (PyObject *)&Dtool_EmptyTuple, nullptr)
+#endif
+
+// Python versions before 3.6 didn't require longlong support to be enabled.
+#ifndef HAVE_LONG_LONG
+#  define PyLong_FromLongLong(x) PyLong_FromLong((long) (x))
+#  define PyLong_FromUnsignedLongLong(x) PyLong_FromUnsignedLong((unsigned long) (x))
+#  define PyLong_AsLongLong(x) PyLong_AsLong(x)
+#  define PyLong_AsUnsignedLongLong(x) PyLong_AsUnsignedLong(x)
+#  define PyLong_AsUnsignedLongLongMask(x) PyLong_AsUnsignedLongMask(x)
+#  define PyLong_AsLongLongAndOverflow(x) PyLong_AsLongAndOverflow(x)
+#endif
+
+/* Python 3.7 */
+
+#ifndef PyDict_GET_SIZE
+#  define PyDict_GET_SIZE(mp) (((PyDictObject *)mp)->ma_used)
+#endif
+
+/* Other Python implementations */
+
+// _PyErr_OCCURRED is an undocumented macro version of PyErr_Occurred.
+// Some implementations of the CPython API (e.g. PyPy's cpyext) do not define
+// it, so in these cases we just silently fall back to PyErr_Occurred.
+#ifndef _PyErr_OCCURRED
+#  define _PyErr_OCCURRED() PyErr_Occurred()
+#endif
+
+#endif  // HAVE_PYTHON
+
+#endif  // PY_COMPAT_H

+ 42 - 8
dtool/src/interrogatedb/py_panda.I

@@ -11,20 +11,54 @@
  * @date 2016-06-06
  */
 
+#ifdef _MSC_VER
+#define _IS_FINAL(T) (__is_sealed(T))
+#elif defined(__GNUC__)
+#define _IS_FINAL(T) (__is_final(T))
+#else
+#define _IS_FINAL(T) (0)
+#endif
+
 /**
  * Template function that can be used to extract any TypedObject pointer from
  * a wrapped Python object.
  */
 template<class T> INLINE bool
-DTOOL_Call_ExtractThisPointer(PyObject *self, T *&into) {
-  if (DtoolCanThisBeAPandaInstance(self)) {
+DtoolInstance_GetPointer(PyObject *self, T *&into) {
+  if (DtoolInstance_Check(self)) {
     Dtool_PyTypedObject *target_class = Dtool_RuntimeTypeDtoolType(get_type_handle(T).get_index());
-    if (target_class != NULL) {
-      into = (T*) ((Dtool_PyInstDef *)self)->_My_Type->_Dtool_UpcastInterface(self, target_class);
-      return (into != NULL);
+    if (target_class != nullptr) {
+      if (_IS_FINAL(T)) {
+        if (DtoolInstance_TYPE(self) == target_class) {
+          into = (T *)DtoolInstance_VOID_PTR(self);
+        }
+      } else {
+        into = (T *)DtoolInstance_UPCAST(self, *target_class);
+      }
+      return (into != nullptr);
+    }
+  }
+  into = nullptr;
+  return false;
+}
+
+/**
+ * Template function that can be used to extract any TypedObject pointer from
+ * a wrapped Python object.  In this case, the Dtool_PyTypedObject is known.
+ */
+template<class T> INLINE bool
+DtoolInstance_GetPointer(PyObject *self, T *&into, Dtool_PyTypedObject &target_class) {
+  if (DtoolInstance_Check(self)) {
+    if (_IS_FINAL(T)) {
+      if (DtoolInstance_TYPE(self) == &target_class) {
+        into = (T *)DtoolInstance_VOID_PTR(self);
+      }
+    } else {
+      into = (T *)DtoolInstance_UPCAST(self, target_class);
     }
+    return (into != nullptr);
   }
-  into = NULL;
+  into = nullptr;
   return false;
 }
 
@@ -73,7 +107,7 @@ Dtool_CheckNoArgs(PyObject *args) {
 ALWAYS_INLINE bool
 Dtool_CheckNoArgs(PyObject *args, PyObject *kwds) {
   return PyTuple_GET_SIZE(args) == 0 &&
-    (kwds == NULL || ((PyDictObject *)kwds)->ma_used == 0);
+    (kwds == nullptr || PyDict_GET_SIZE(kwds) == 0);
 }
 
 /**
@@ -222,7 +256,7 @@ ALWAYS_INLINE PyObject *Dtool_WrapValue(PyObject *value) {
   return value;
 }
 
-ALWAYS_INLINE PyObject *Dtool_WrapValue(const std::vector<unsigned char> &value) {
+ALWAYS_INLINE PyObject *Dtool_WrapValue(const vector_uchar &value) {
 #if PY_MAJOR_VERSION >= 3
   return PyBytes_FromStringAndSize((char *)value.data(), (Py_ssize_t)value.size());
 #else

+ 73 - 207
dtool/src/interrogatedb/py_panda.cxx

@@ -31,76 +31,14 @@ static RuntimeTypeMap runtime_type_map;
 static RuntimeTypeSet runtime_type_set;
 static NamedTypeMap named_type_map;
 
-#if PY_MAJOR_VERSION < 3
-/**
- * Given a long or int, returns a size_t, or raises an OverflowError if it is
- * out of range.
- */
-size_t PyLongOrInt_AsSize_t(PyObject *vv) {
-  if (PyInt_Check(vv)) {
-    long value = PyInt_AS_LONG(vv);
-    if (value < 0) {
-      PyErr_SetString(PyExc_OverflowError,
-                      "can't convert negative value to size_t");
-      return (size_t)-1;
-    }
-    return (size_t)value;
-  }
-
-  if (!PyLong_Check(vv)) {
-    Dtool_Raise_TypeError("a long or int was expected");
-    return (size_t)-1;
-  }
-
-  size_t bytes;
-  int one = 1;
-  int res = _PyLong_AsByteArray((PyLongObject *)vv, (unsigned char *)&bytes,
-                                SIZEOF_SIZE_T, (int)*(unsigned char*)&one, 0);
-
-  if (res < 0) {
-    return (size_t)res;
-  } else {
-    return bytes;
-  }
-}
-#endif
-
-#if PY_VERSION_HEX < 0x03060000
-INLINE static PyObject *_PyObject_CallNoArg(PyObject *func) {
-  PyObject *args = PyTuple_New(0);
-  PyObject *result = PyObject_Call(func, args, NULL);
-  Py_DECREF(args);
-  return result;
-}
-#endif
-
-/**
- * Given a valid (non-NULL) PyObject, does a simple check to see if it might
- * be an instance of a Panda type.  It does this using a signature that is
- * encoded on each instance.
- */
-bool DtoolCanThisBeAPandaInstance(PyObject *self) {
-  // simple sanity check for the class type..size.. will stop basic foobars..
-  // It is arguably better to use something like this:
-  // PyType_IsSubtype(Py_TYPE(self), &Dtool_DTOOL_SUPER_BASE._PyType) ...but
-  // probably not as fast.
-  if (Py_TYPE(self)->tp_basicsize >= (int)sizeof(Dtool_PyInstDef)) {
-    Dtool_PyInstDef *pyself = (Dtool_PyInstDef *) self;
-    if (pyself->_signature == PY_PANDA_SIGNATURE) {
-      return true;
-    }
-  }
-  return false;
-}
-
 /**
 
  */
 void DTOOL_Call_ExtractThisPointerForType(PyObject *self, Dtool_PyTypedObject *classdef, void **answer) {
-  if (DtoolCanThisBeAPandaInstance(self)) {
-    *answer = ((Dtool_PyInstDef *)self)->_My_Type->_Dtool_UpcastInterface(self, classdef);
+  if (DtoolInstance_Check(self)) {
+    *answer = DtoolInstance_UPCAST(self, *classdef);
   } else {
-    *answer = NULL;
+    *answer = nullptr;
   }
 }
 
@@ -110,12 +48,12 @@ void DTOOL_Call_ExtractThisPointerForType(PyObject *self, Dtool_PyTypedObject *c
  * was of the wrong type, raises an AttributeError.
  */
 bool Dtool_Call_ExtractThisPointer(PyObject *self, Dtool_PyTypedObject &classdef, void **answer) {
-  if (self == NULL || !DtoolCanThisBeAPandaInstance(self) || ((Dtool_PyInstDef *)self)->_ptr_to_object == NULL) {
+  if (self == nullptr || !DtoolInstance_Check(self) || DtoolInstance_VOID_PTR(self) == nullptr) {
     Dtool_Raise_TypeError("C++ object is not yet constructed, or already destructed.");
     return false;
   }
 
-  *answer = ((Dtool_PyInstDef *)self)->_My_Type->_Dtool_UpcastInterface(self, &classdef);
+  *answer = DtoolInstance_UPCAST(self, classdef);
   return true;
 }
 
@@ -130,12 +68,12 @@ bool Dtool_Call_ExtractThisPointer(PyObject *self, Dtool_PyTypedObject &classdef
 bool Dtool_Call_ExtractThisPointer_NonConst(PyObject *self, Dtool_PyTypedObject &classdef,
                                             void **answer, const char *method_name) {
 
-  if (self == NULL || !DtoolCanThisBeAPandaInstance(self) || ((Dtool_PyInstDef *)self)->_ptr_to_object == NULL) {
+  if (self == nullptr || !DtoolInstance_Check(self) || DtoolInstance_VOID_PTR(self) == nullptr) {
     Dtool_Raise_TypeError("C++ object is not yet constructed, or already destructed.");
     return false;
   }
 
-  if (((Dtool_PyInstDef *)self)->_is_const) {
+  if (DtoolInstance_IS_CONST(self)) {
     // All overloads of this function are non-const.
     PyErr_Format(PyExc_TypeError,
                  "Cannot call %s() on a const object.",
@@ -143,7 +81,7 @@ bool Dtool_Call_ExtractThisPointer_NonConst(PyObject *self, Dtool_PyTypedObject
     return false;
   }
 
-  *answer = ((Dtool_PyInstDef *)self)->_My_Type->_Dtool_UpcastInterface(self, &classdef);
+  *answer = DtoolInstance_UPCAST(self, classdef);
   return true;
 }
 
@@ -173,19 +111,19 @@ void *
 DTOOL_Call_GetPointerThisClass(PyObject *self, Dtool_PyTypedObject *classdef,
                                int param, const string &function_name, bool const_ok,
                                bool report_errors) {
-  // if (PyErr_Occurred()) { return NULL; }
-  if (self == NULL) {
+  // if (PyErr_Occurred()) { return nullptr; }
+  if (self == nullptr) {
     if (report_errors) {
-      return Dtool_Raise_TypeError("self is NULL");
+      return Dtool_Raise_TypeError("self is nullptr");
     }
-    return NULL;
+    return nullptr;
   }
 
-  if (DtoolCanThisBeAPandaInstance(self)) {
-    void *result = ((Dtool_PyInstDef *)self)->_My_Type->_Dtool_UpcastInterface(self, classdef);
+  if (DtoolInstance_Check(self)) {
+    void *result = DtoolInstance_UPCAST(self, *classdef);
 
-    if (result != NULL) {
-      if (const_ok || !((Dtool_PyInstDef *)self)->_is_const) {
+    if (result != nullptr) {
+      if (const_ok || !DtoolInstance_IS_CONST(self)) {
         return result;
       }
 
@@ -194,7 +132,7 @@ DTOOL_Call_GetPointerThisClass(PyObject *self, Dtool_PyTypedObject *classdef,
                             "%s() argument %d may not be const",
                             function_name.c_str(), param);
       }
-      return NULL;
+      return nullptr;
     }
   }
 
@@ -202,17 +140,14 @@ DTOOL_Call_GetPointerThisClass(PyObject *self, Dtool_PyTypedObject *classdef,
     return Dtool_Raise_ArgTypeError(self, param, function_name.c_str(), classdef->_PyType.tp_name);
   }
 
-  return NULL;
+  return nullptr;
 }
 
 void *DTOOL_Call_GetPointerThis(PyObject *self) {
-  if (self != NULL) {
-    if (DtoolCanThisBeAPandaInstance(self)) {
-      Dtool_PyInstDef * pyself = (Dtool_PyInstDef *) self;
-      return pyself->_ptr_to_object;
-    }
+  if (self != nullptr && DtoolInstance_Check(self)) {
+    return DtoolInstance_VOID_PTR(self);
   }
-  return NULL;
+  return nullptr;
 }
 
 /**
@@ -329,11 +264,11 @@ PyObject *_Dtool_Raise_BadArgumentsError() {
  * NULL, otherwise Py_None.
  */
 PyObject *_Dtool_Return_None() {
-  if (_PyErr_OCCURRED()) {
+  if (UNLIKELY(_PyErr_OCCURRED())) {
     return NULL;
   }
 #ifndef NDEBUG
-  if (Notify::ptr()->has_assert_failed()) {
+  if (UNLIKELY(Notify::ptr()->has_assert_failed())) {
     return Dtool_Raise_AssertionError();
   }
 #endif
@@ -346,11 +281,11 @@ PyObject *_Dtool_Return_None() {
  * NULL, otherwise the given boolean value as a PyObject *.
  */
 PyObject *Dtool_Return_Bool(bool value) {
-  if (_PyErr_OCCURRED()) {
+  if (UNLIKELY(_PyErr_OCCURRED())) {
     return NULL;
   }
 #ifndef NDEBUG
-  if (Notify::ptr()->has_assert_failed()) {
+  if (UNLIKELY(Notify::ptr()->has_assert_failed())) {
     return Dtool_Raise_AssertionError();
   }
 #endif
@@ -365,11 +300,11 @@ PyObject *Dtool_Return_Bool(bool value) {
  * increased.
  */
 PyObject *_Dtool_Return(PyObject *value) {
-  if (_PyErr_OCCURRED()) {
+  if (UNLIKELY(_PyErr_OCCURRED())) {
     return NULL;
   }
 #ifndef NDEBUG
-  if (Notify::ptr()->has_assert_failed()) {
+  if (UNLIKELY(Notify::ptr()->has_assert_failed())) {
     return Dtool_Raise_AssertionError();
   }
 #endif
@@ -618,10 +553,45 @@ PyObject *Dtool_PyModuleInitHelper(LibraryDef *defs[], const char *modulename) {
     dtool_inited = true;
 
     if (PyType_Ready(&Dtool_SequenceWrapper_Type) < 0) {
-      PyErr_SetString(PyExc_TypeError, "PyType_Ready(Dtool_SequenceWrapper)");
-      return NULL;
+      return Dtool_Raise_TypeError("PyType_Ready(Dtool_SequenceWrapper)");
     }
 
+    if (PyType_Ready(&Dtool_MutableSequenceWrapper_Type) < 0) {
+      return Dtool_Raise_TypeError("PyType_Ready(Dtool_MutableSequenceWrapper)");
+    }
+
+    if (PyType_Ready(&Dtool_MappingWrapper_Type) < 0) {
+      return Dtool_Raise_TypeError("PyType_Ready(Dtool_MappingWrapper)");
+    }
+
+    if (PyType_Ready(&Dtool_MutableMappingWrapper_Type) < 0) {
+      return Dtool_Raise_TypeError("PyType_Ready(Dtool_MutableMappingWrapper)");
+    }
+
+    if (PyType_Ready(&Dtool_MappingWrapper_Keys_Type) < 0) {
+      return Dtool_Raise_TypeError("PyType_Ready(Dtool_MappingWrapper_Keys)");
+    }
+
+    if (PyType_Ready(&Dtool_MappingWrapper_Values_Type) < 0) {
+      return Dtool_Raise_TypeError("PyType_Ready(Dtool_MappingWrapper_Values)");
+    }
+
+    if (PyType_Ready(&Dtool_MappingWrapper_Items_Type) < 0) {
+      return Dtool_Raise_TypeError("PyType_Ready(Dtool_MappingWrapper_Items)");
+    }
+
+    if (PyType_Ready(&Dtool_GeneratorWrapper_Type) < 0) {
+      return Dtool_Raise_TypeError("PyType_Ready(Dtool_GeneratorWrapper)");
+    }
+
+    if (PyType_Ready(&Dtool_StaticProperty_Type) < 0) {
+      return Dtool_Raise_TypeError("PyType_Ready(Dtool_StaticProperty_Type)");
+    }
+
+#ifdef Py_TRACE_REFS
+    _Py_AddToAllObjects((PyObject *)&Dtool_EmptyTuple, 0);
+#endif
+
     // Initialize the base class of everything.
     Dtool_PyModuleClassInit_DTOOL_SUPER_BASE(NULL);
   }
@@ -720,7 +690,7 @@ PyObject *Dtool_BorrowThisReference(PyObject *self, PyObject *args) {
   PyObject *to_in = NULL;
   if (PyArg_UnpackTuple(args, "Dtool_BorrowThisReference", 2, 2, &to_in, &from_in)) {
 
-    if (DtoolCanThisBeAPandaInstance(from_in) && DtoolCanThisBeAPandaInstance(to_in)) {
+    if (DtoolInstance_Check(from_in) && DtoolInstance_Check(to_in)) {
       Dtool_PyInstDef *from = (Dtool_PyInstDef *) from_in;
       Dtool_PyInstDef *to = (Dtool_PyInstDef *) to_in;
 
@@ -765,9 +735,8 @@ PyObject *Dtool_AddToDictionary(PyObject *self1, PyObject *args) {
 }
 
 Py_hash_t DTOOL_PyObject_HashPointer(PyObject *self) {
-  if (self != NULL && DtoolCanThisBeAPandaInstance(self)) {
-    Dtool_PyInstDef * pyself = (Dtool_PyInstDef *) self;
-    return (Py_hash_t) pyself->_ptr_to_object;
+  if (self != nullptr && DtoolInstance_Check(self)) {
+    return (Py_hash_t)DtoolInstance_VOID_PTR(self);
   }
   return -1;
 }
@@ -940,14 +909,14 @@ bool Dtool_ExtractArg(PyObject **result, PyObject *args, PyObject *kwds,
                       const char *keyword) {
 
   if (PyTuple_GET_SIZE(args) == 1) {
-    if (kwds == NULL || ((PyDictObject *)kwds)->ma_used == 0) {
+    if (kwds == nullptr || PyDict_GET_SIZE(kwds) == 0) {
       *result = PyTuple_GET_ITEM(args, 0);
       return true;
     }
   } else if (PyTuple_GET_SIZE(args) == 0) {
     PyObject *key;
     Py_ssize_t ppos = 0;
-    if (kwds != NULL && ((PyDictObject *)kwds)->ma_used == 1 &&
+    if (kwds != nullptr && PyDict_GET_SIZE(kwds) == 1 &&
         PyDict_Next(kwds, &ppos, &key, result)) {
       // We got the item, we just need to make sure that it had the right key.
 #if PY_VERSION_HEX >= 0x03060000
@@ -968,7 +937,7 @@ bool Dtool_ExtractArg(PyObject **result, PyObject *args, PyObject *kwds,
  */
 bool Dtool_ExtractArg(PyObject **result, PyObject *args, PyObject *kwds) {
   if (PyTuple_GET_SIZE(args) == 1 &&
-      (kwds == NULL || ((PyDictObject *)kwds)->ma_used == 0)) {
+      (kwds == nullptr || PyDict_GET_SIZE(kwds) == 0)) {
     *result = PyTuple_GET_ITEM(args, 0);
     return true;
   }
@@ -986,12 +955,12 @@ bool Dtool_ExtractOptionalArg(PyObject **result, PyObject *args, PyObject *kwds,
                               const char *keyword) {
 
   if (PyTuple_GET_SIZE(args) == 1) {
-    if (kwds == NULL || ((PyDictObject *)kwds)->ma_used == 0) {
+    if (kwds == nullptr || PyDict_GET_SIZE(kwds) == 0) {
       *result = PyTuple_GET_ITEM(args, 0);
       return true;
     }
   } else if (PyTuple_GET_SIZE(args) == 0) {
-    if (kwds != NULL && ((PyDictObject *)kwds)->ma_used == 1) {
+    if (kwds != nullptr && PyDict_GET_SIZE(kwds) == 1) {
       PyObject *key;
       Py_ssize_t ppos = 0;
       if (!PyDict_Next(kwds, &ppos, &key, result)) {
@@ -1018,7 +987,7 @@ bool Dtool_ExtractOptionalArg(PyObject **result, PyObject *args, PyObject *kwds,
  * Variant of Dtool_ExtractOptionalArg that does not accept a keyword argument.
  */
 bool Dtool_ExtractOptionalArg(PyObject **result, PyObject *args, PyObject *kwds) {
-  if (kwds != NULL && ((PyDictObject *)kwds)->ma_used != 0) {
+  if (kwds != nullptr && PyDict_GET_SIZE(kwds) != 0) {
     return false;
   }
   if (PyTuple_GET_SIZE(args) == 1) {
@@ -1028,107 +997,4 @@ bool Dtool_ExtractOptionalArg(PyObject **result, PyObject *args, PyObject *kwds)
   return (PyTuple_GET_SIZE(args) == 0);
 }
 
-/**
- * This class is returned from properties that require a settable interface,
- * ie. something.children[i] = 3.
- */
-static void Dtool_SequenceWrapper_dealloc(PyObject *self) {
-  Dtool_SequenceWrapper *wrap = (Dtool_SequenceWrapper *)self;
-  nassertv(wrap);
-  Py_DECREF(wrap->_base);
-}
-
-static Py_ssize_t Dtool_SequenceWrapper_length(PyObject *self) {
-  Dtool_SequenceWrapper *wrap = (Dtool_SequenceWrapper *)self;
-  nassertr(wrap, -1);
-  nassertr(wrap->_len_func, -1);
-  return wrap->_len_func(wrap->_base);
-}
-
-static PyObject *Dtool_SequenceWrapper_getitem(PyObject *self, Py_ssize_t index) {
-  Dtool_SequenceWrapper *wrap = (Dtool_SequenceWrapper *)self;
-  nassertr(wrap, NULL);
-  nassertr(wrap->_getitem_func, NULL);
-  return wrap->_getitem_func(wrap->_base, index);
-}
-
-static int Dtool_SequenceWrapper_setitem(PyObject *self, Py_ssize_t index, PyObject *value) {
-  Dtool_SequenceWrapper *wrap = (Dtool_SequenceWrapper *)self;
-  nassertr(wrap, -1);
-  nassertr(wrap->_setitem_func, -1);
-  return wrap->_setitem_func(wrap->_base, index, value);
-}
-
-static PySequenceMethods Dtool_SequenceWrapper_SequenceMethods = {
-  Dtool_SequenceWrapper_length,
-  0, // sq_concat
-  0, // sq_repeat
-  Dtool_SequenceWrapper_getitem,
-  0, // sq_slice
-  Dtool_SequenceWrapper_setitem,
-  0, // sq_ass_slice
-  0, // sq_contains
-  0, // sq_inplace_concat
-  0, // sq_inplace_repeat
-};
-
-PyTypeObject Dtool_SequenceWrapper_Type = {
-  PyVarObject_HEAD_INIT(NULL, 0)
-  "sequence wrapper",
-  sizeof(Dtool_SequenceWrapper),
-  0, // tp_itemsize
-  Dtool_SequenceWrapper_dealloc,
-  0, // tp_print
-  0, // tp_getattr
-  0, // tp_setattr
-#if PY_MAJOR_VERSION >= 3
-  0, // tp_reserved
-#else
-  0, // tp_compare
-#endif
-  0, // tp_repr
-  0, // tp_as_number
-  &Dtool_SequenceWrapper_SequenceMethods,
-  0, // tp_as_mapping
-  0, // tp_hash
-  0, // tp_call
-  0, // tp_str
-  PyObject_GenericGetAttr,
-  PyObject_GenericSetAttr,
-  0, // tp_as_buffer
-  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES,
-  0, // tp_doc
-  0, // tp_traverse
-  0, // tp_clear
-  0, // tp_richcompare
-  0, // tp_weaklistoffset
-  0, // tp_iter
-  0, // tp_iternext
-  0, // tp_methods
-  0, // tp_members
-  0, // tp_getset
-  0, // tp_base
-  0, // tp_dict
-  0, // tp_descr_get
-  0, // tp_descr_set
-  0, // tp_dictoffset
-  0, // tp_init
-  PyType_GenericAlloc,
-  0, // tp_new
-  PyObject_Del,
-  0, // tp_is_gc
-  0, // tp_bases
-  0, // tp_mro
-  0, // tp_cache
-  0, // tp_subclasses
-  0, // tp_weaklist
-  0, // tp_del
-#if PY_VERSION_HEX >= 0x02060000
-  0, // tp_version_tag
-#endif
-#if PY_VERSION_HEX >= 0x03040000
-  0, // tp_finalize
-#endif
-};
-
 #endif  // HAVE_PYTHON

+ 31 - 146
dtool/src/interrogatedb/py_panda.h

@@ -20,124 +20,15 @@
 #define  Py_DEBUG
 #endif
 
-#ifndef NO_RUNTIME_TYPES
-
-#include "dtoolbase.h"
-#include "typedObject.h"
-#include "typeRegistry.h"
-
-#endif
-
 #include "pnotify.h"
+#include "vector_uchar.h"
 
 #if defined(HAVE_PYTHON) && !defined(CPPPARSER)
 
-#ifdef _POSIX_C_SOURCE
-#undef _POSIX_C_SOURCE
-#endif
-
-#ifdef _XOPEN_SOURCE
-#undef _XOPEN_SOURCE
-#endif
-
-#define PY_SSIZE_T_CLEAN 1
-
-#include "Python.h"
+// py_compat.h includes Python.h.
+#include "py_compat.h"
 #include "structmember.h"
 
-#ifndef HAVE_LONG_LONG
-#define PyLong_FromLongLong(x) PyLong_FromLong((long) (x))
-#define PyLong_FromUnsignedLongLong(x) PyLong_FromUnsignedLong((unsigned long) (x))
-#define PyLong_AsLongLong(x) PyLong_AsLong(x)
-#define PyLong_AsUnsignedLongLong(x) PyLong_AsUnsignedLong(x)
-#define PyLong_AsUnsignedLongLongMask(x) PyLong_AsUnsignedLongMask(x)
-#define PyLong_AsLongLongAndOverflow(x) PyLong_AsLongAndOverflow(x)
-#endif
-
-#if PY_VERSION_HEX < 0x02050000
-
-// Prior to Python 2.5, we didn't have Py_ssize_t.
-typedef int Py_ssize_t;
-#define PyInt_FromSsize_t PyInt_FromLong
-#define PyInt_AsSsize_t PyInt_AsLong
-
-#endif  // PY_VERSION_HEX
-
-// 2.4 macros which aren't available in 2.3
-#ifndef Py_RETURN_NONE
-inline PyObject* doPy_RETURN_NONE()
-{   Py_INCREF(Py_None); return Py_None; }
-#define Py_RETURN_NONE return doPy_RETURN_NONE()
-#endif
-
-#ifndef Py_RETURN_TRUE
-inline PyObject* doPy_RETURN_TRUE()
-{Py_INCREF(Py_True); return Py_True;}
-#define Py_RETURN_TRUE return doPy_RETURN_TRUE()
-#endif
-
-#ifndef Py_RETURN_FALSE
-inline PyObject* doPy_RETURN_FALSE()
-{Py_INCREF(Py_False); return Py_False;}
-#define Py_RETURN_FALSE return doPy_RETURN_FALSE()
-#endif
-
-#ifndef PyVarObject_HEAD_INIT
-#define PyVarObject_HEAD_INIT(type, size) \
-  PyObject_HEAD_INIT(type) size,
-#endif
-
-#ifndef Py_TYPE
-#define Py_TYPE(ob) (((PyObject*)(ob))->ob_type)
-#endif
-
-#ifndef Py_TPFLAGS_CHECKTYPES
-// Always on in Python 3
-#define Py_TPFLAGS_CHECKTYPES 0
-#endif
-
-#if PY_MAJOR_VERSION >= 3
-// For writing code that will compile in both versions.
-#define nb_nonzero nb_bool
-#define nb_divide nb_true_divide
-#define nb_inplace_divide nb_inplace_true_divide
-
-#define PyLongOrInt_Check(x) PyLong_Check(x)
-#define PyLongOrInt_AS_LONG PyLong_AS_LONG
-#define PyInt_Check PyLong_Check
-#define PyInt_AsLong PyLong_AsLong
-#define PyInt_AS_LONG PyLong_AS_LONG
-#define PyLongOrInt_AsSize_t PyLong_AsSize_t
-#else
-#define PyLongOrInt_Check(x) (PyInt_Check(x) || PyLong_Check(x))
-// PyInt_FromSize_t automatically picks the right type.
-#define PyLongOrInt_AS_LONG PyInt_AsLong
-
-EXPCL_INTERROGATEDB size_t PyLongOrInt_AsSize_t(PyObject *);
-
-// For more portably defining hash functions.
-typedef long Py_hash_t;
-#endif
-
-#if PY_MAJOR_VERSION >= 3
-// Python 3 versions before 3.3.3 defined this incorrectly.
-#undef _PyErr_OCCURRED
-#define _PyErr_OCCURRED() (PyThreadState_GET()->curexc_type)
-
-// Python versions before 3.3 did not define this.
-#if PY_VERSION_HEX < 0x03030000
-#define PyUnicode_AsUTF8 _PyUnicode_AsString
-#define PyUnicode_AsUTF8AndSize _PyUnicode_AsStringAndSize
-#endif
-#endif
-
-// Which character to use in PyArg_ParseTuple et al for a byte string.
-#if PY_MAJOR_VERSION >= 3
-#define FMTCHAR_BYTES "y"
-#else
-#define FMTCHAR_BYTES "s"
-#endif
-
 using namespace std;
 
 // this is tempory .. untill this is glued better into the panda build system
@@ -236,7 +127,7 @@ static void Dtool_FreeInstance_##CLASS_NAME(PyObject *self) {\
 #else // NDEBUG
 #define Define_Dtool_FreeInstance_Private(CLASS_NAME,CNAME)\
 static void Dtool_FreeInstance_##CLASS_NAME(PyObject *self) {\
-  if (((Dtool_PyInstDef *)self)->_ptr_to_object != NULL) {\
+  if (DtoolInstance_VOID_PTR(self) != nullptr) {\
     if (((Dtool_PyInstDef *)self)->_memory_rules) {\
       cerr << "Detected leak for " << #CLASS_NAME \
            << " which interrogate cannot delete.\n"; \
@@ -248,9 +139,9 @@ static void Dtool_FreeInstance_##CLASS_NAME(PyObject *self) {\
 
 #define Define_Dtool_FreeInstance(CLASS_NAME,CNAME)\
 static void Dtool_FreeInstance_##CLASS_NAME(PyObject *self) {\
-  if (((Dtool_PyInstDef *)self)->_ptr_to_object != NULL) {\
+  if (DtoolInstance_VOID_PTR(self) != nullptr) {\
     if (((Dtool_PyInstDef *)self)->_memory_rules) {\
-      delete ((CNAME *)((Dtool_PyInstDef *)self)->_ptr_to_object);\
+      delete (CNAME *)DtoolInstance_VOID_PTR(self);\
     }\
   }\
   Py_TYPE(self)->tp_free(self);\
@@ -258,9 +149,9 @@ static void Dtool_FreeInstance_##CLASS_NAME(PyObject *self) {\
 
 #define Define_Dtool_FreeInstanceRef(CLASS_NAME,CNAME)\
 static void Dtool_FreeInstance_##CLASS_NAME(PyObject *self) {\
-  if (((Dtool_PyInstDef *)self)->_ptr_to_object != NULL) {\
+  if (DtoolInstance_VOID_PTR(self) != nullptr) {\
     if (((Dtool_PyInstDef *)self)->_memory_rules) {\
-      unref_delete((CNAME *)((Dtool_PyInstDef *)self)->_ptr_to_object);\
+      unref_delete((CNAME *)DtoolInstance_VOID_PTR(self));\
     }\
   }\
   Py_TYPE(self)->tp_free(self);\
@@ -272,8 +163,17 @@ static void Dtool_FreeInstance_##CLASS_NAME(PyObject *self) {\
   Py_TYPE(self)->tp_free(self);\
 }
 
-// Simple Recognition Functions..
-EXPCL_INTERROGATEDB bool DtoolCanThisBeAPandaInstance(PyObject *self);
+// Use DtoolInstance_Check to check whether a PyObject* is a DtoolInstance.
+#define DtoolInstance_Check(obj) \
+  (Py_TYPE(obj)->tp_basicsize >= (int)sizeof(Dtool_PyInstDef) && \
+   ((Dtool_PyInstDef *)obj)->_signature == PY_PANDA_SIGNATURE)
+
+// These macros access the DtoolInstance without error checking.
+#define DtoolInstance_TYPE(obj) (((Dtool_PyInstDef *)obj)->_My_Type)
+#define DtoolInstance_IS_CONST(obj) (((Dtool_PyInstDef *)obj)->_is_const)
+#define DtoolInstance_VOID_PTR(obj) (((Dtool_PyInstDef *)obj)->_ptr_to_object)
+#define DtoolInstance_INIT_PTR(obj, ptr) { ((Dtool_PyInstDef *)obj)->_ptr_to_object = (void*)(ptr); }
+#define DtoolInstance_UPCAST(obj, type) (((Dtool_PyInstDef *)(obj))->_My_Type->_Dtool_UpcastInterface((obj), &(type)))
 
 // ** HACK ** allert.. Need to keep a runtime type dictionary ... that is
 // forward declared of typed object.  We rely on the fact that typed objects
@@ -301,22 +201,16 @@ EXPCL_INTERROGATEDB bool Dtool_Call_ExtractThisPointer(PyObject *self, Dtool_PyT
 EXPCL_INTERROGATEDB bool Dtool_Call_ExtractThisPointer_NonConst(PyObject *self, Dtool_PyTypedObject &classdef,
                                                               void **answer, const char *method_name);
 
-template<class T> INLINE bool DTOOL_Call_ExtractThisPointer(PyObject *self, T *&into);
+template<class T> INLINE bool DtoolInstance_GetPointer(PyObject *self, T *&into);
+template<class T> INLINE bool DtoolInstance_GetPointer(PyObject *self, T *&into, Dtool_PyTypedObject &classdef);
 
 // Functions related to error reporting.
 EXPCL_INTERROGATEDB bool _Dtool_CheckErrorOccurred();
 
-// _PyErr_OCCURRED is an undocumented macro version of PyErr_Occurred.
-// Some implementations of the CPython API (e.g. PyPy's cpyext) do not define
-// it, so in these cases we just silently fall back to PyErr_Occurred.
-#ifndef _PyErr_OCCURRED
-#define _PyErr_OCCURRED() PyErr_Occurred()
-#endif
-
 #ifdef NDEBUG
-#define Dtool_CheckErrorOccurred() (_PyErr_OCCURRED() != NULL)
+#define Dtool_CheckErrorOccurred() (UNLIKELY(_PyErr_OCCURRED() != nullptr))
 #else
-#define Dtool_CheckErrorOccurred() _Dtool_CheckErrorOccurred()
+#define Dtool_CheckErrorOccurred() (UNLIKELY(_Dtool_CheckErrorOccurred()))
 #endif
 
 EXPCL_INTERROGATEDB PyObject *Dtool_Raise_AssertionError();
@@ -333,13 +227,16 @@ EXPCL_INTERROGATEDB PyObject *_Dtool_Raise_BadArgumentsError();
 #define Dtool_Raise_BadArgumentsError(x) Dtool_Raise_TypeError("Arguments must match:\n" x)
 #endif
 
+// These functions are similar to Dtool_WrapValue, except that they also
+// contain code for checking assertions and exceptions when compiling with
+// NDEBUG mode on.
 EXPCL_INTERROGATEDB PyObject *_Dtool_Return_None();
 EXPCL_INTERROGATEDB PyObject *Dtool_Return_Bool(bool value);
 EXPCL_INTERROGATEDB PyObject *_Dtool_Return(PyObject *value);
 
 #ifdef NDEBUG
-#define Dtool_Return_None() (_PyErr_OCCURRED() != NULL ? NULL : (Py_INCREF(Py_None), Py_None))
-#define Dtool_Return(value) (_PyErr_OCCURRED() != NULL ? NULL : value)
+#define Dtool_Return_None() (LIKELY(_PyErr_OCCURRED() == nullptr) ? (Py_INCREF(Py_None), Py_None) : nullptr)
+#define Dtool_Return(value) (LIKELY(_PyErr_OCCURRED() == nullptr) ? value : nullptr)
 #else
 #define Dtool_Return_None() _Dtool_Return_None()
 #define Dtool_Return(value) _Dtool_Return(value)
@@ -458,20 +355,6 @@ copy_from_copy_constructor(PyObject *self, PyObject *noargs);
 EXPCL_INTERROGATEDB PyObject *
 map_deepcopy_to_copy(PyObject *self, PyObject *args);
 
-/**
- * This class is returned from properties that require a settable interface,
- * ie. something.children[i] = 3.
- */
-struct Dtool_SequenceWrapper {
-  PyObject_HEAD
-  PyObject *_base;
-  lenfunc _len_func;
-  ssizeargfunc _getitem_func;
-  ssizeobjargproc _setitem_func;
-};
-
-EXPCL_INTERROGATEDB extern PyTypeObject Dtool_SequenceWrapper_Type;
-
 /**
  * These functions check whether the arguments passed to a function conform to
  * certain expectations.
@@ -511,7 +394,7 @@ ALWAYS_INLINE PyObject *Dtool_WrapValue(char value);
 ALWAYS_INLINE PyObject *Dtool_WrapValue(wchar_t value);
 ALWAYS_INLINE PyObject *Dtool_WrapValue(nullptr_t);
 ALWAYS_INLINE PyObject *Dtool_WrapValue(PyObject *value);
-ALWAYS_INLINE PyObject *Dtool_WrapValue(const std::vector<unsigned char> &value);
+ALWAYS_INLINE PyObject *Dtool_WrapValue(const vector_uchar &value);
 
 #if PY_MAJOR_VERSION >= 0x02060000
 ALWAYS_INLINE PyObject *Dtool_WrapValue(Py_buffer *value);
@@ -527,6 +410,8 @@ EXPCL_INTERROGATEDB extern void Dtool_PyModuleClassInit_DTOOL_SUPER_BASE(PyObjec
 
 #include "py_panda.I"
 
+#include "py_wrappers.h"
+
 #endif  // HAVE_PYTHON && !CPPPARSER
 
 #endif // PY_PANDA_H_

+ 1675 - 0
dtool/src/interrogatedb/py_wrappers.cxx

@@ -0,0 +1,1675 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file py_wrappers.cxx
+ * @author rdb
+ * @date 2017-11-26
+ */
+
+#include "py_wrappers.h"
+
+#ifdef HAVE_PYTHON
+
+#if PY_VERSION_HEX >= 0x03040000
+#define _COLLECTIONS_ABC "_collections_abc"
+#elif PY_VERSION_HEX >= 0x03030000
+#define _COLLECTIONS_ABC "collections.abc"
+#else
+#define _COLLECTIONS_ABC "_abcoll"
+#endif
+
+static void _register_collection(PyTypeObject *type, const char *abc) {
+  PyObject *sys_modules = PyImport_GetModuleDict();
+  if (sys_modules != nullptr) {
+    PyObject *module = PyDict_GetItemString(sys_modules, _COLLECTIONS_ABC);
+    if (module != nullptr) {
+      PyObject *dict = PyModule_GetDict(module);
+      if (module != nullptr) {
+#if PY_MAJOR_VERSION >= 3
+        static PyObject *register_str = PyUnicode_InternFromString("register");
+#else
+        static PyObject *register_str = PyString_InternFromString("register");
+#endif
+        PyObject *sequence = PyDict_GetItemString(dict, abc);
+        if (sequence != nullptr) {
+          if (PyObject_CallMethodObjArgs(sequence, register_str, (PyObject *)type, nullptr) == nullptr) {
+            PyErr_Print();
+          }
+        }
+      }
+    }
+  }
+}
+
+/**
+ * These classes are returned from properties that require a subscript
+ * interface, ie. something.children[i] = 3.
+ */
+static void Dtool_WrapperBase_dealloc(PyObject *self) {
+  Dtool_WrapperBase *wrap = (Dtool_WrapperBase *)self;
+  nassertv(wrap);
+  Py_XDECREF(wrap->_self);
+}
+
+static PyObject *Dtool_WrapperBase_repr(PyObject *self) {
+  Dtool_WrapperBase *wrap = (Dtool_WrapperBase *)self;
+  nassertr(wrap, nullptr);
+
+  PyObject *repr = PyObject_Repr(wrap->_self);
+  PyObject *result;
+#if PY_MAJOR_VERSION >= 3
+  result = PyUnicode_FromFormat("<%s[] of %s>", wrap->_name, PyUnicode_AsUTF8(repr));
+#else
+  result = PyString_FromFormat("<%s[] of %s>", wrap->_name, PyString_AS_STRING(repr));
+#endif
+  Py_DECREF(repr);
+  return result;
+}
+
+static PyObject *Dtool_SequenceWrapper_repr(PyObject *self) {
+  Dtool_SequenceWrapper *wrap = (Dtool_SequenceWrapper *)self;
+  nassertr(wrap, nullptr);
+
+  Py_ssize_t len = -1;
+  if (wrap->_len_func != nullptr) {
+    len = wrap->_len_func(wrap->_base._self);
+  }
+
+  if (len < 0) {
+    PyErr_Restore(nullptr, nullptr, nullptr);
+    return Dtool_WrapperBase_repr(self);
+  }
+
+  PyObject *repr = PyObject_Repr(wrap->_base._self);
+  PyObject *result;
+#if PY_MAJOR_VERSION >= 3
+  result = PyUnicode_FromFormat("<%s[%zd] of %s>", wrap->_base._name, len, PyUnicode_AsUTF8(repr));
+#else
+  result = PyString_FromFormat("<%s[%zd] of %s>", wrap->_base._name, len, PyString_AS_STRING(repr));
+#endif
+  Py_DECREF(repr);
+  return result;
+}
+
+static Py_ssize_t Dtool_SequenceWrapper_length(PyObject *self) {
+  Dtool_SequenceWrapper *wrap = (Dtool_SequenceWrapper *)self;
+  nassertr(wrap, -1);
+  if (wrap->_len_func != nullptr) {
+    return wrap->_len_func(wrap->_base._self);
+  } else {
+    Dtool_Raise_TypeError("property does not support len()");
+    return -1;
+  }
+}
+
+static PyObject *Dtool_SequenceWrapper_getitem(PyObject *self, Py_ssize_t index) {
+  Dtool_SequenceWrapper *wrap = (Dtool_SequenceWrapper *)self;
+  nassertr(wrap, nullptr);
+  nassertr(wrap->_getitem_func, nullptr);
+  return wrap->_getitem_func(wrap->_base._self, index);
+}
+
+/**
+ * Implementation of (x in property)
+ */
+static int Dtool_SequenceWrapper_contains(PyObject *self, PyObject *value) {
+  Dtool_SequenceWrapper *wrap = (Dtool_SequenceWrapper *)self;
+  nassertr(wrap, -1);
+  nassertr(wrap->_len_func, -1);
+  nassertr(wrap->_getitem_func, -1);
+
+  Py_ssize_t length = wrap->_len_func(wrap->_base._self);
+
+  // Iterate through the items, invoking the equality function for each, until
+  // we have found the matching one.
+  for (Py_ssize_t index = 0; index < length; ++index) {
+    PyObject *item = wrap->_getitem_func(wrap->_base._self, index);
+    if (item != nullptr) {
+      int cmp = PyObject_RichCompareBool(item, value, Py_EQ);
+      if (cmp > 0) {
+        return 1;
+      }
+      if (cmp < 0) {
+        return -1;
+      }
+    } else {
+      return -1;
+    }
+  }
+  return 0;
+}
+
+/**
+ * Implementation of property.index(x) which returns the index of the first
+ * occurrence of x in the sequence, or raises a ValueError if it isn't found.
+ */
+static PyObject *Dtool_SequenceWrapper_index(PyObject *self, PyObject *value) {
+  Dtool_SequenceWrapper *wrap = (Dtool_SequenceWrapper *)self;
+  nassertr(wrap, nullptr);
+  nassertr(wrap->_len_func, nullptr);
+  nassertr(wrap->_getitem_func, nullptr);
+
+  Py_ssize_t length = wrap->_len_func(wrap->_base._self);
+
+  // Iterate through the items, invoking the equality function for each, until
+  // we have found the right one.
+  for (Py_ssize_t index = 0; index < length; ++index) {
+    PyObject *item = wrap->_getitem_func(wrap->_base._self, index);
+    if (item != nullptr) {
+      int cmp = PyObject_RichCompareBool(item, value, Py_EQ);
+      if (cmp > 0) {
+        return Dtool_WrapValue(index);
+      }
+      if (cmp < 0) {
+        return nullptr;
+      }
+    } else {
+      return nullptr;
+    }
+  }
+  // Not found, raise ValueError.
+  return PyErr_Format(PyExc_ValueError, "%s.index() did not find value", wrap->_base._name);
+}
+
+/**
+ * Implementation of property.count(x) which returns the number of occurrences
+ * of x in the sequence.
+ */
+static PyObject *Dtool_SequenceWrapper_count(PyObject *self, PyObject *value) {
+  Dtool_SequenceWrapper *wrap = (Dtool_SequenceWrapper *)self;
+  nassertr(wrap, nullptr);
+  Py_ssize_t index = 0;
+  if (wrap->_len_func != nullptr) {
+    index = wrap->_len_func(wrap->_base._self);
+  } else {
+    return Dtool_Raise_TypeError("property does not support count()");
+  }
+  // Iterate through the items, invoking the == operator for each.
+  long count = 0;
+  nassertr(wrap->_getitem_func, nullptr);
+  while (index > 0) {
+    --index;
+    PyObject *item = wrap->_getitem_func(wrap->_base._self, index);
+    if (item == nullptr) {
+      return nullptr;
+    }
+    int cmp = PyObject_RichCompareBool(item, value, Py_EQ);
+    if (cmp > 0) {
+      ++count;
+    }
+    if (cmp < 0) {
+      return nullptr;
+    }
+  }
+#if PY_MAJOR_VERSION >= 3
+  return PyLong_FromLong(count);
+#else
+  return PyInt_FromLong(count);
+#endif
+}
+
+/**
+ * Implementation of `property[i] = x`
+ */
+static int Dtool_MutableSequenceWrapper_setitem(PyObject *self, Py_ssize_t index, PyObject *value) {
+  Dtool_MutableSequenceWrapper *wrap = (Dtool_MutableSequenceWrapper *)self;
+  nassertr(wrap, -1);
+  if (wrap->_setitem_func != nullptr) {
+    return wrap->_setitem_func(wrap->_base._self, index, value);
+  } else {
+    Dtool_Raise_TypeError("property does not support item assignment");
+    return -1;
+  }
+}
+
+/**
+ * Implementation of property.clear() which removes all elements in the
+ * sequence, starting with the last.
+ */
+static PyObject *Dtool_MutableSequenceWrapper_clear(PyObject *self, PyObject *) {
+  Dtool_MutableSequenceWrapper *wrap = (Dtool_MutableSequenceWrapper *)self;
+  nassertr(wrap, nullptr);
+  Py_ssize_t index = 0;
+  if (wrap->_len_func != nullptr && wrap->_setitem_func != nullptr) {
+    index = wrap->_len_func(wrap->_base._self);
+  } else {
+    return Dtool_Raise_TypeError("property does not support clear()");
+  }
+
+  // Iterate through the items, invoking the delete function for each.  We do
+  // this in reverse order, which may be more efficient.
+  while (index > 0) {
+    --index;
+    if (wrap->_setitem_func(wrap->_base._self, index, nullptr) != 0) {
+      return nullptr;
+    }
+  }
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+/**
+ * Implementation of property.remove(x) which removes the first occurrence of
+ * x in the sequence, or raises a ValueError if it isn't found.
+ */
+static PyObject *Dtool_MutableSequenceWrapper_remove(PyObject *self, PyObject *value) {
+  Dtool_MutableSequenceWrapper *wrap = (Dtool_MutableSequenceWrapper *)self;
+  nassertr(wrap, nullptr);
+  Py_ssize_t length = 0;
+  if (wrap->_len_func != nullptr && wrap->_setitem_func != nullptr) {
+    length = wrap->_len_func(wrap->_base._self);
+  } else {
+    return Dtool_Raise_TypeError("property does not support remove()");
+  }
+
+  // Iterate through the items, invoking the equality function for each, until
+  // we have found the right one.
+  nassertr(wrap->_getitem_func, nullptr);
+  for (Py_ssize_t index = 0; index < length; ++index) {
+    PyObject *item = wrap->_getitem_func(wrap->_base._self, index);
+    if (item != nullptr) {
+      int cmp = PyObject_RichCompareBool(item, value, Py_EQ);
+      if (cmp > 0) {
+        if (wrap->_setitem_func(wrap->_base._self, index, nullptr) == 0) {
+          Py_INCREF(Py_None);
+          return Py_None;
+        } else {
+          return nullptr;
+        }
+      }
+      if (cmp < 0) {
+        return nullptr;
+      }
+    } else {
+      return nullptr;
+    }
+  }
+  // Not found, raise ValueError.
+  return PyErr_Format(PyExc_ValueError, "%s.remove() did not find value", wrap->_base._name);
+}
+
+/**
+ * Implementation of property.pop([i=-1]) which returns and removes the
+ * element at the indicated index in the sequence.  If no index is provided,
+ * it removes from the end of the list.
+ */
+static PyObject *Dtool_MutableSequenceWrapper_pop(PyObject *self, PyObject *args) {
+  Dtool_MutableSequenceWrapper *wrap = (Dtool_MutableSequenceWrapper *)self;
+  nassertr(wrap, nullptr);
+  if (wrap->_getitem_func == nullptr || wrap->_setitem_func == nullptr ||
+      wrap->_len_func == nullptr) {
+    return Dtool_Raise_TypeError("property does not support pop()");
+  }
+
+  Py_ssize_t length = wrap->_len_func(wrap->_base._self);
+  Py_ssize_t index;
+  switch (PyTuple_GET_SIZE(args)) {
+  case 0:
+    index = length - 1;
+    break;
+  case 1:
+    index = PyNumber_AsSsize_t(PyTuple_GET_ITEM(args, 0), PyExc_IndexError);
+    if (index == -1 && _PyErr_OCCURRED()) {
+      return nullptr;
+    }
+    if (index < 0) {
+      index += length;
+    }
+    break;
+  default:
+    return Dtool_Raise_TypeError("pop([i=-1]) takes 0 or 1 arguments");
+  }
+
+  if (length <= 0) {
+    return PyErr_Format(PyExc_IndexError, "%s.pop() from empty sequence", wrap->_base._name);
+  }
+
+  // Index error will be caught by getitem_func.
+  PyObject *value = wrap->_getitem_func(wrap->_base._self, index);
+  if (value != nullptr) {
+    if (wrap->_setitem_func(wrap->_base._self, index, nullptr) != 0) {
+      return nullptr;
+    }
+    return value;
+  }
+  return nullptr;
+}
+
+/**
+ * Implementation of property.append(x) which is an alias for
+ * property.insert(len(property), x).
+ */
+static PyObject *Dtool_MutableSequenceWrapper_append(PyObject *self, PyObject *arg) {
+  Dtool_MutableSequenceWrapper *wrap = (Dtool_MutableSequenceWrapper *)self;
+  nassertr(wrap, nullptr);
+  if (wrap->_insert_func == nullptr) {
+    return Dtool_Raise_TypeError("property does not support append()");
+  }
+  return wrap->_insert_func(wrap->_base._self, (size_t)-1, arg);
+}
+
+/**
+ * Implementation of property.insert(i, x) which inserts the given item at the
+ * given position.
+ */
+static PyObject *Dtool_MutableSequenceWrapper_insert(PyObject *self, PyObject *args) {
+  Dtool_MutableSequenceWrapper *wrap = (Dtool_MutableSequenceWrapper *)self;
+  nassertr(wrap, nullptr);
+  if (wrap->_insert_func == nullptr) {
+    return Dtool_Raise_TypeError("property does not support insert()");
+  }
+  if (PyTuple_GET_SIZE(args) != 2) {
+    return Dtool_Raise_TypeError("insert() takes exactly 2 arguments");
+  }
+  Py_ssize_t index = PyNumber_AsSsize_t(PyTuple_GET_ITEM(args, 0), PyExc_IndexError);
+  if (index == -1 && _PyErr_OCCURRED()) {
+    return nullptr;
+  }
+  if (index < 0) {
+    if (wrap->_len_func != nullptr) {
+      index += wrap->_len_func(wrap->_base._self);
+    } else {
+      return PyErr_Format(PyExc_TypeError, "%s.insert() does not support negative indices", wrap->_base._name);
+    }
+  }
+  return wrap->_insert_func(wrap->_base._self, (ssize_t)max(index, (Py_ssize_t)0), PyTuple_GET_ITEM(args, 1));
+}
+
+/**
+ * Implementation of property.extend(seq) which is equivalent to:
+ * @code
+ * for x in seq:
+ *   property.append(seq)
+ * @endcode
+ */
+static PyObject *Dtool_MutableSequenceWrapper_extend(PyObject *self, PyObject *arg) {
+  Dtool_MutableSequenceWrapper *wrap = (Dtool_MutableSequenceWrapper *)self;
+  nassertr(wrap, nullptr);
+  if (wrap->_insert_func == nullptr) {
+    return Dtool_Raise_TypeError("property does not support extend()");
+  }
+  PyObject *iter = PyObject_GetIter(arg);
+  if (iter == nullptr) {
+    return nullptr;
+  }
+  PyObject *next = PyIter_Next(iter);
+  PyObject *retval = nullptr;
+  while (next != nullptr) {
+    retval = wrap->_insert_func(wrap->_base._self, (size_t)-1, next);
+    Py_DECREF(next);
+    if (retval == nullptr) {
+      Py_DECREF(iter);
+      return nullptr;
+    }
+    Py_DECREF(retval);
+    next = PyIter_Next(iter);
+  }
+
+  Py_DECREF(iter);
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+/**
+ * Implementation of `x in mapping`.
+ */
+static int Dtool_MappingWrapper_contains(PyObject *self, PyObject *key) {
+  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)self;
+  nassertr(wrap, -1);
+  nassertr(wrap->_getitem_func, -1);
+  PyObject *value = wrap->_getitem_func(wrap->_base._self, key);
+  if (value != nullptr) {
+    Py_DECREF(value);
+    return 1;
+  } else if (_PyErr_OCCURRED() == PyExc_KeyError ||
+             _PyErr_OCCURRED() == PyExc_TypeError) {
+    PyErr_Restore(nullptr, nullptr, nullptr);
+    return 0;
+  } else {
+    return -1;
+  }
+}
+
+static PyObject *Dtool_MappingWrapper_getitem(PyObject *self, PyObject *key) {
+  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)self;
+  nassertr(wrap, nullptr);
+  nassertr(wrap->_getitem_func, nullptr);
+  return wrap->_getitem_func(wrap->_base._self, key);
+}
+
+/**
+ * Implementation of iter(property) that returns an iterable over all the
+ * keys.
+ */
+static PyObject *Dtool_MappingWrapper_iter(PyObject *self) {
+  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)self;
+  nassertr(wrap, nullptr);
+
+  if (wrap->_keys._len_func == nullptr || wrap->_keys._getitem_func == nullptr) {
+    return PyErr_Format(PyExc_TypeError, "%s is not iterable", wrap->_base._name);
+  }
+
+  Dtool_SequenceWrapper *keys = Dtool_NewSequenceWrapper(wrap->_base._self, wrap->_base._name);
+  if (keys != nullptr) {
+    keys->_len_func = wrap->_keys._len_func;
+    keys->_getitem_func = wrap->_keys._getitem_func;
+    return PySeqIter_New((PyObject *)keys);
+  } else {
+    return nullptr;
+  }
+}
+
+/**
+ * Implementation of property.get(key[,def=None]) which returns the value with
+ * the given key in the mapping, or the given default value (which defaults to
+ * None) if the key isn't found in the mapping.
+ */
+static PyObject *Dtool_MappingWrapper_get(PyObject *self, PyObject *args) {
+  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)self;
+  nassertr(wrap, nullptr);
+  nassertr(wrap->_getitem_func, nullptr);
+  Py_ssize_t size = PyTuple_GET_SIZE(args);
+  if (size != 1 && size != 2) {
+    return PyErr_Format(PyExc_TypeError, "%s.get() takes 1 or 2 arguments", wrap->_base._name);
+  }
+  PyObject *defvalue = Py_None;
+  if (size >= 2) {
+    defvalue = PyTuple_GET_ITEM(args, 1);
+  }
+  PyObject *key = PyTuple_GET_ITEM(args, 0);
+  PyObject *value = wrap->_getitem_func(wrap->_base._self, key);
+  if (value != nullptr) {
+    return value;
+  } else if (_PyErr_OCCURRED() == PyExc_KeyError) {
+    PyErr_Restore(nullptr, nullptr, nullptr);
+    Py_INCREF(defvalue);
+    return defvalue;
+  } else {
+    return nullptr;
+  }
+}
+
+/**
+ * Implementation of property.keys(...) that returns a view of all the keys.
+ */
+static PyObject *Dtool_MappingWrapper_keys(PyObject *self, PyObject *) {
+  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)self;
+  nassertr(wrap, nullptr);
+
+  if (wrap->_keys._len_func == nullptr || wrap->_keys._getitem_func == nullptr) {
+    return Dtool_Raise_TypeError("property does not support keys()");
+  }
+
+  Dtool_MappingWrapper *keys = (Dtool_MappingWrapper *)PyObject_MALLOC(sizeof(Dtool_MappingWrapper));
+  if (keys == nullptr) {
+    return PyErr_NoMemory();
+  }
+
+  // If the collections.abc module is loaded, register this as a subclass.
+  static bool registered = false;
+  if (!registered) {
+    registered = true;
+    _register_collection((PyTypeObject *)&Dtool_MappingWrapper_Keys_Type, "MappingView");
+  }
+
+  PyObject_INIT(keys, &Dtool_MappingWrapper_Keys_Type);
+  Py_XINCREF(wrap->_base._self);
+  keys->_base._self = wrap->_base._self;
+  keys->_base._name = wrap->_base._name;
+  keys->_keys._len_func = wrap->_keys._len_func;
+  keys->_keys._getitem_func = wrap->_keys._getitem_func;
+  keys->_getitem_func = wrap->_getitem_func;
+  keys->_setitem_func = nullptr;
+  return (PyObject *)keys;
+}
+
+/**
+ * Implementation of property.values(...) that returns a view of the values.
+ */
+static PyObject *Dtool_MappingWrapper_values(PyObject *self, PyObject *) {
+  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)self;
+  nassertr(wrap, nullptr);
+  nassertr(wrap->_getitem_func, nullptr);
+
+  if (wrap->_keys._len_func == nullptr || wrap->_keys._getitem_func == nullptr) {
+    return Dtool_Raise_TypeError("property does not support values()");
+  }
+
+  Dtool_MappingWrapper *values = (Dtool_MappingWrapper *)PyObject_MALLOC(sizeof(Dtool_MappingWrapper));
+  if (values == nullptr) {
+    return PyErr_NoMemory();
+  }
+
+  // If the collections.abc module is loaded, register this as a subclass.
+  static bool registered = false;
+  if (!registered) {
+    registered = true;
+    _register_collection((PyTypeObject *)&Dtool_MappingWrapper_Values_Type, "ValuesView");
+  }
+
+  PyObject_INIT(values, &Dtool_MappingWrapper_Values_Type);
+  Py_XINCREF(wrap->_base._self);
+  values->_base._self = wrap->_base._self;
+  values->_base._name = wrap->_base._name;
+  values->_keys._len_func = wrap->_keys._len_func;
+  values->_keys._getitem_func = wrap->_keys._getitem_func;
+  values->_getitem_func = wrap->_getitem_func;
+  values->_setitem_func = nullptr;
+  return (PyObject *)values;
+}
+
+/**
+ * Implementation of property.items(...) that returns an iterable yielding a
+ * `(key, value)` tuple for every item.
+ */
+static PyObject *Dtool_MappingWrapper_items(PyObject *self, PyObject *) {
+  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)self;
+  nassertr(wrap, nullptr);
+  nassertr(wrap->_getitem_func, nullptr);
+
+  if (wrap->_keys._len_func == nullptr || wrap->_keys._getitem_func == nullptr) {
+    return Dtool_Raise_TypeError("property does not support items()");
+  }
+
+  Dtool_MappingWrapper *items = (Dtool_MappingWrapper *)PyObject_MALLOC(sizeof(Dtool_MappingWrapper));
+  if (items == nullptr) {
+    return PyErr_NoMemory();
+  }
+
+  // If the collections.abc module is loaded, register this as a subclass.
+  static bool registered = false;
+  if (!registered) {
+    registered = true;
+    _register_collection((PyTypeObject *)&Dtool_MappingWrapper_Items_Type, "MappingView");
+  }
+
+  PyObject_INIT(items, &Dtool_MappingWrapper_Items_Type);
+  Py_XINCREF(wrap->_base._self);
+  items->_base._self = wrap->_base._self;
+  items->_base._name = wrap->_base._name;
+  items->_keys._len_func = wrap->_keys._len_func;
+  items->_keys._getitem_func = wrap->_keys._getitem_func;
+  items->_getitem_func = wrap->_getitem_func;
+  items->_setitem_func = nullptr;
+  return (PyObject *)items;
+}
+
+/**
+ * Implementation of `property[key] = value`
+ */
+static int Dtool_MutableMappingWrapper_setitem(PyObject *self, PyObject *key, PyObject *value) {
+  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)self;
+  nassertr(wrap->_setitem_func != nullptr, -1);
+  return wrap->_setitem_func(wrap->_base._self, key, value);
+}
+
+/**
+ * Implementation of property.pop(key[,def=None]) which is the same as get()
+ * except that it also removes the element from the mapping.
+ */
+static PyObject *Dtool_MutableMappingWrapper_pop(PyObject *self, PyObject *args) {
+  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)self;
+  nassertr(wrap, nullptr);
+  if (wrap->_getitem_func == nullptr || wrap->_setitem_func == nullptr) {
+    return Dtool_Raise_TypeError("property does not support pop()");
+  }
+
+  Py_ssize_t size = PyTuple_GET_SIZE(args);
+  if (size != 1 && size != 2) {
+    return PyErr_Format(PyExc_TypeError, "%s.pop() takes 1 or 2 arguments", wrap->_base._name);
+  }
+  PyObject *defvalue = Py_None;
+  if (size >= 2) {
+    defvalue = PyTuple_GET_ITEM(args, 1);
+  }
+
+  PyObject *key = PyTuple_GET_ITEM(args, 0);
+  PyObject *value = wrap->_getitem_func(wrap->_base._self, key);
+  if (value != nullptr) {
+    // OK, now set unset this value.
+    if (wrap->_setitem_func(wrap->_base._self, key, nullptr) == 0) {
+      return value;
+    } else {
+      Py_DECREF(value);
+      return nullptr;
+    }
+  } else if (_PyErr_OCCURRED() == PyExc_KeyError) {
+    PyErr_Restore(nullptr, nullptr, nullptr);
+    Py_INCREF(defvalue);
+    return defvalue;
+  } else {
+    return nullptr;
+  }
+}
+
+/**
+ * Implementation of property.popitem() which returns and removes an arbitrary
+ * (key, value) pair from the mapping.  Useful for destructive iteration.
+ */
+static PyObject *Dtool_MutableMappingWrapper_popitem(PyObject *self, PyObject *) {
+  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)self;
+  nassertr(wrap, nullptr);
+  if (wrap->_getitem_func == nullptr || wrap->_setitem_func == nullptr ||
+      wrap->_keys._len_func == nullptr || wrap->_keys._getitem_func == nullptr) {
+    return Dtool_Raise_TypeError("property does not support popitem()");
+  }
+
+  Py_ssize_t length = wrap->_keys._len_func(wrap->_base._self);
+  if (length < 1) {
+    return PyErr_Format(PyExc_KeyError, "%s is empty", wrap->_base._name);
+  }
+
+  PyObject *key = wrap->_keys._getitem_func(wrap->_base._self, length - 1);
+  if (key != nullptr) {
+    PyObject *value = wrap->_getitem_func(wrap->_base._self, key);
+    if (value != nullptr) {
+      // OK, now set unset this value.
+      if (wrap->_setitem_func(wrap->_base._self, key, nullptr) == 0) {
+        PyObject *item = PyTuple_New(2);
+        PyTuple_SET_ITEM(item, 0, key);
+        PyTuple_SET_ITEM(item, 1, value);
+        return item;
+      }
+      Py_DECREF(value);
+    }
+  }
+  return nullptr;
+}
+
+/*
+ * Implementation of property.clear() which removes all elements in the
+ * mapping.
+ */
+static PyObject *Dtool_MutableMappingWrapper_clear(PyObject *self, PyObject *) {
+  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)self;
+  nassertr(wrap, nullptr);
+  Py_ssize_t index = 0;
+  if (wrap->_keys._len_func != nullptr && wrap->_keys._getitem_func != nullptr &&
+      wrap->_setitem_func != nullptr) {
+    index = wrap->_keys._len_func(wrap->_base._self);
+  } else {
+    return Dtool_Raise_TypeError("property does not support clear()");
+  }
+
+  // Iterate through the items, invoking the delete function for each.  We do
+  // this in reverse order, which may be more efficient.
+  while (index > 0) {
+    --index;
+    PyObject *key = wrap->_keys._getitem_func(wrap->_base._self, index);
+    if (key != nullptr) {
+      int result = wrap->_setitem_func(wrap->_base._self, key, nullptr);
+      Py_DECREF(key);
+      if (result != 0) {
+        return nullptr;
+      }
+    }
+  }
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+/**
+ * Implementation of property.setdefault(key[,def=None]) which is the same as
+ * get() except that it also writes the default value back to the mapping if
+ * the key was not found is missing.
+ */
+static PyObject *Dtool_MutableMappingWrapper_setdefault(PyObject *self, PyObject *args) {
+  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)self;
+  nassertr(wrap, nullptr);
+
+  if (wrap->_getitem_func == nullptr || wrap->_setitem_func == nullptr) {
+    return Dtool_Raise_TypeError("property does not support setdefault()");
+  }
+
+  Py_ssize_t size = PyTuple_GET_SIZE(args);
+  if (size != 1 && size != 2) {
+    return PyErr_Format(PyExc_TypeError, "%s.setdefault() takes 1 or 2 arguments", wrap->_base._name);
+  }
+  PyObject *defvalue = Py_None;
+  if (size >= 2) {
+    defvalue = PyTuple_GET_ITEM(args, 1);
+  }
+  PyObject *key = PyTuple_GET_ITEM(args, 0);
+  PyObject *value = wrap->_getitem_func(wrap->_base._self, key);
+  if (value != nullptr) {
+    return value;
+  } else if (_PyErr_OCCURRED() == PyExc_KeyError) {
+    PyErr_Restore(nullptr, nullptr, nullptr);
+    if (wrap->_setitem_func(wrap->_base._self, key, defvalue) == 0) {
+      Py_INCREF(defvalue);
+      return defvalue;
+    }
+  }
+  return nullptr;
+}
+
+/**
+ * Implementation of property.update(...) which sets multiple values in one
+ * go.  It accepts either a single dictionary or keyword arguments, not both.
+ */
+static PyObject *Dtool_MutableMappingWrapper_update(PyObject *self, PyObject *args, PyObject *kwargs) {
+  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)self;
+  nassertr(wrap, nullptr);
+
+  if (wrap->_getitem_func == nullptr || wrap->_setitem_func == nullptr) {
+    return Dtool_Raise_TypeError("property does not support update()");
+  }
+
+  // We accept either a dict argument or keyword arguments, but not both.
+  PyObject *dict;
+  switch (PyTuple_GET_SIZE(args)) {
+  case 0:
+    if (kwargs == nullptr) {
+      // This is legal.
+      Py_INCREF(Py_None);
+      return Py_None;
+    }
+    dict = kwargs;
+    break;
+  case 1:
+    if (PyDict_Check(PyTuple_GET_ITEM(args, 0)) && (kwargs == nullptr || Py_SIZE(kwargs) == 0)) {
+      dict = PyTuple_GET_ITEM(args, 0);
+      break;
+    }
+    // Fall through
+  default:
+    return PyErr_Format(PyExc_TypeError, "%s.update() takes either a dict argument or keyword arguments", wrap->_base._name);
+  }
+
+  PyObject *key, *value;
+  Py_ssize_t pos = 0;
+  while (PyDict_Next(dict, &pos, &key, &value)) {
+    if (wrap->_setitem_func(wrap->_base._self, key, value) != 0) {
+      return nullptr;
+    }
+  }
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+/**
+ * This variant defines only a sequence interface.
+ */
+static PySequenceMethods Dtool_SequenceWrapper_SequenceMethods = {
+  Dtool_SequenceWrapper_length,
+  0, // sq_concat
+  0, // sq_repeat
+  Dtool_SequenceWrapper_getitem,
+  0, // sq_slice
+  0, // sq_ass_item
+  0, // sq_ass_slice
+  Dtool_SequenceWrapper_contains,
+  0, // sq_inplace_concat
+  0, // sq_inplace_repeat
+};
+
+static PyMethodDef Dtool_SequenceWrapper_Methods[] = {
+  {"index", &Dtool_SequenceWrapper_index, METH_O, nullptr},
+  {"count", &Dtool_SequenceWrapper_count, METH_O, nullptr},
+  {nullptr, nullptr, 0, nullptr}
+};
+
+PyTypeObject Dtool_SequenceWrapper_Type = {
+  PyVarObject_HEAD_INIT(nullptr, 0)
+  "sequence wrapper",
+  sizeof(Dtool_SequenceWrapper),
+  0, // tp_itemsize
+  Dtool_WrapperBase_dealloc,
+  0, // tp_print
+  0, // tp_getattr
+  0, // tp_setattr
+  0, // tp_compare
+  Dtool_SequenceWrapper_repr,
+  0, // tp_as_number
+  &Dtool_SequenceWrapper_SequenceMethods,
+  0, // tp_as_mapping
+  0, // tp_hash
+  0, // tp_call
+  0, // tp_str
+  PyObject_GenericGetAttr,
+  PyObject_GenericSetAttr,
+  0, // tp_as_buffer
+  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES,
+  0, // tp_doc
+  0, // tp_traverse
+  0, // tp_clear
+  0, // tp_richcompare
+  0, // tp_weaklistoffset
+  PySeqIter_New,
+  0, // tp_iternext
+  Dtool_SequenceWrapper_Methods,
+  0, // tp_members
+  0, // tp_getset
+  0, // tp_base
+  0, // tp_dict
+  0, // tp_descr_get
+  0, // tp_descr_set
+  0, // tp_dictoffset
+  0, // tp_init
+  PyType_GenericAlloc,
+  0, // tp_new
+  PyObject_Del,
+  0, // tp_is_gc
+  0, // tp_bases
+  0, // tp_mro
+  0, // tp_cache
+  0, // tp_subclasses
+  0, // tp_weaklist
+  0, // tp_del
+};
+
+/**
+ * This is a variant on SequenceWrapper that also has an insert() method.
+ */
+static PySequenceMethods Dtool_MutableSequenceWrapper_SequenceMethods = {
+  Dtool_SequenceWrapper_length,
+  0, // sq_concat
+  0, // sq_repeat
+  Dtool_SequenceWrapper_getitem,
+  0, // sq_slice
+  Dtool_MutableSequenceWrapper_setitem,
+  0, // sq_ass_slice
+  Dtool_SequenceWrapper_contains,
+  Dtool_MutableSequenceWrapper_extend,
+  0, // sq_inplace_repeat
+};
+
+static PyMethodDef Dtool_MutableSequenceWrapper_Methods[] = {
+  {"index", &Dtool_SequenceWrapper_index, METH_O, nullptr},
+  {"count", &Dtool_SequenceWrapper_count, METH_O, nullptr},
+  {"clear", &Dtool_MutableSequenceWrapper_clear, METH_NOARGS, nullptr},
+  {"pop", &Dtool_MutableSequenceWrapper_pop, METH_VARARGS, nullptr},
+  {"remove", &Dtool_MutableSequenceWrapper_remove, METH_O, nullptr},
+  {"append", &Dtool_MutableSequenceWrapper_append, METH_O, nullptr},
+  {"insert", &Dtool_MutableSequenceWrapper_insert, METH_VARARGS, nullptr},
+  {"extend", &Dtool_MutableSequenceWrapper_extend, METH_O, nullptr},
+  {nullptr, nullptr, 0, nullptr}
+};
+
+PyTypeObject Dtool_MutableSequenceWrapper_Type = {
+  PyVarObject_HEAD_INIT(nullptr, 0)
+  "sequence wrapper",
+  sizeof(Dtool_MutableSequenceWrapper),
+  0, // tp_itemsize
+  Dtool_WrapperBase_dealloc,
+  0, // tp_print
+  0, // tp_getattr
+  0, // tp_setattr
+  0, // tp_compare
+  Dtool_SequenceWrapper_repr,
+  0, // tp_as_number
+  &Dtool_MutableSequenceWrapper_SequenceMethods,
+  0, // tp_as_mapping
+  0, // tp_hash
+  0, // tp_call
+  0, // tp_str
+  PyObject_GenericGetAttr,
+  PyObject_GenericSetAttr,
+  0, // tp_as_buffer
+  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES,
+  0, // tp_doc
+  0, // tp_traverse
+  0, // tp_clear
+  0, // tp_richcompare
+  0, // tp_weaklistoffset
+  PySeqIter_New,
+  0, // tp_iternext
+  Dtool_MutableSequenceWrapper_Methods,
+  0, // tp_members
+  0, // tp_getset
+  0, // tp_base
+  0, // tp_dict
+  0, // tp_descr_get
+  0, // tp_descr_set
+  0, // tp_dictoffset
+  0, // tp_init
+  PyType_GenericAlloc,
+  0, // tp_new
+  PyObject_Del,
+  0, // tp_is_gc
+  0, // tp_bases
+  0, // tp_mro
+  0, // tp_cache
+  0, // tp_subclasses
+  0, // tp_weaklist
+  0, // tp_del
+};
+
+/**
+ * This variant defines only a mapping interface.
+ */
+static PySequenceMethods Dtool_MappingWrapper_SequenceMethods = {
+  Dtool_SequenceWrapper_length,
+  0, // sq_concat
+  0, // sq_repeat
+  0, // sq_item
+  0, // sq_slice
+  0, // sq_ass_item
+  0, // sq_ass_slice
+  Dtool_MappingWrapper_contains,
+  0, // sq_inplace_concat
+  0, // sq_inplace_repeat
+};
+
+static PyMappingMethods Dtool_MappingWrapper_MappingMethods = {
+  Dtool_SequenceWrapper_length,
+  Dtool_MappingWrapper_getitem,
+  0, // mp_ass_subscript
+};
+
+static PyMethodDef Dtool_MappingWrapper_Methods[] = {
+  {"get", &Dtool_MappingWrapper_get, METH_VARARGS, nullptr},
+  {"keys", &Dtool_MappingWrapper_keys, METH_NOARGS, nullptr},
+  {"values", &Dtool_MappingWrapper_values, METH_NOARGS, nullptr},
+  {"items", &Dtool_MappingWrapper_items, METH_NOARGS, nullptr},
+  {nullptr, nullptr, 0, nullptr}
+};
+
+PyTypeObject Dtool_MappingWrapper_Type = {
+  PyVarObject_HEAD_INIT(nullptr, 0)
+  "mapping wrapper",
+  sizeof(Dtool_MappingWrapper),
+  0, // tp_itemsize
+  Dtool_WrapperBase_dealloc,
+  0, // tp_print
+  0, // tp_getattr
+  0, // tp_setattr
+  0, // tp_compare
+  Dtool_WrapperBase_repr,
+  0, // tp_as_number
+  &Dtool_MappingWrapper_SequenceMethods,
+  &Dtool_MappingWrapper_MappingMethods,
+  0, // tp_hash
+  0, // tp_call
+  0, // tp_str
+  PyObject_GenericGetAttr,
+  PyObject_GenericSetAttr,
+  0, // tp_as_buffer
+  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES,
+  0, // tp_doc
+  0, // tp_traverse
+  0, // tp_clear
+  0, // tp_richcompare
+  0, // tp_weaklistoffset
+  Dtool_MappingWrapper_iter,
+  0, // tp_iternext
+  Dtool_MappingWrapper_Methods,
+  0, // tp_members
+  0, // tp_getset
+  0, // tp_base
+  0, // tp_dict
+  0, // tp_descr_get
+  0, // tp_descr_set
+  0, // tp_dictoffset
+  0, // tp_init
+  PyType_GenericAlloc,
+  0, // tp_new
+  PyObject_Del,
+  0, // tp_is_gc
+  0, // tp_bases
+  0, // tp_mro
+  0, // tp_cache
+  0, // tp_subclasses
+  0, // tp_weaklist
+  0, // tp_del
+};
+
+/**
+ * This variant defines only a mutable mapping interface.
+ */
+static PyMappingMethods Dtool_MutableMappingWrapper_MappingMethods = {
+  Dtool_SequenceWrapper_length,
+  Dtool_MappingWrapper_getitem,
+  Dtool_MutableMappingWrapper_setitem,
+};
+
+static PyMethodDef Dtool_MutableMappingWrapper_Methods[] = {
+  {"get", &Dtool_MappingWrapper_get, METH_VARARGS, nullptr},
+  {"pop", &Dtool_MutableMappingWrapper_pop, METH_VARARGS, nullptr},
+  {"popitem", &Dtool_MutableMappingWrapper_popitem, METH_NOARGS, nullptr},
+  {"clear", &Dtool_MutableMappingWrapper_clear, METH_VARARGS, nullptr},
+  {"setdefault", &Dtool_MutableMappingWrapper_setdefault, METH_VARARGS, nullptr},
+  {"update", (PyCFunction) &Dtool_MutableMappingWrapper_update, METH_VARARGS | METH_KEYWORDS, nullptr},
+  {"keys", &Dtool_MappingWrapper_keys, METH_NOARGS, nullptr},
+  {"values", &Dtool_MappingWrapper_values, METH_NOARGS, nullptr},
+  {"items", &Dtool_MappingWrapper_items, METH_NOARGS, nullptr},
+  {nullptr, nullptr, 0, nullptr}
+};
+
+PyTypeObject Dtool_MutableMappingWrapper_Type = {
+  PyVarObject_HEAD_INIT(nullptr, 0)
+  "mapping wrapper",
+  sizeof(Dtool_MappingWrapper),
+  0, // tp_itemsize
+  Dtool_WrapperBase_dealloc,
+  0, // tp_print
+  0, // tp_getattr
+  0, // tp_setattr
+  0, // tp_compare
+  Dtool_WrapperBase_repr,
+  0, // tp_as_number
+  &Dtool_MappingWrapper_SequenceMethods,
+  &Dtool_MutableMappingWrapper_MappingMethods,
+  0, // tp_hash
+  0, // tp_call
+  0, // tp_str
+  PyObject_GenericGetAttr,
+  PyObject_GenericSetAttr,
+  0, // tp_as_buffer
+  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES,
+  0, // tp_doc
+  0, // tp_traverse
+  0, // tp_clear
+  0, // tp_richcompare
+  0, // tp_weaklistoffset
+  Dtool_MappingWrapper_iter,
+  0, // tp_iternext
+  Dtool_MutableMappingWrapper_Methods,
+  0, // tp_members
+  0, // tp_getset
+  0, // tp_base
+  0, // tp_dict
+  0, // tp_descr_get
+  0, // tp_descr_set
+  0, // tp_dictoffset
+  0, // tp_init
+  PyType_GenericAlloc,
+  0, // tp_new
+  PyObject_Del,
+  0, // tp_is_gc
+  0, // tp_bases
+  0, // tp_mro
+  0, // tp_cache
+  0, // tp_subclasses
+  0, // tp_weaklist
+  0, // tp_del
+};
+
+/**
+ * This is returned by mapping.items().
+ */
+static PyObject *Dtool_MappingWrapper_Items_repr(PyObject *self) {
+  Dtool_WrapperBase *wrap = (Dtool_WrapperBase *)self;
+  nassertr(wrap, nullptr);
+
+  PyObject *repr = PyObject_Repr(wrap->_self);
+  PyObject *result;
+#if PY_MAJOR_VERSION >= 3
+  result = PyUnicode_FromFormat("<%s.items() of %s>", wrap->_name, PyUnicode_AsUTF8(repr));
+#else
+  result = PyString_FromFormat("<%s.items() of %s>", wrap->_name, PyString_AS_STRING(repr));
+#endif
+  Py_DECREF(repr);
+  return result;
+}
+
+static PyObject *Dtool_MappingWrapper_Items_getitem(PyObject *self, Py_ssize_t index) {
+  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)self;
+  nassertr(wrap, nullptr);
+  nassertr(wrap->_keys._getitem_func, nullptr);
+
+  PyObject *key = wrap->_keys._getitem_func(wrap->_base._self, index);
+  if (key != nullptr) {
+    PyObject *value = wrap->_getitem_func(wrap->_base._self, key);
+    if (value != nullptr) {
+      // PyTuple_SET_ITEM steals the reference.
+      PyObject *item = PyTuple_New(2);
+      PyTuple_SET_ITEM(item, 0, key);
+      PyTuple_SET_ITEM(item, 1, value);
+      return item;
+    } else {
+      Py_DECREF(key);
+    }
+  }
+  return nullptr;
+}
+
+static PySequenceMethods Dtool_MappingWrapper_Items_SequenceMethods = {
+  Dtool_SequenceWrapper_length,
+  0, // sq_concat
+  0, // sq_repeat
+  Dtool_MappingWrapper_Items_getitem,
+  0, // sq_slice
+  0, // sq_ass_item
+  0, // sq_ass_slice
+  Dtool_MappingWrapper_contains,
+  0, // sq_inplace_concat
+  0, // sq_inplace_repeat
+};
+
+PyTypeObject Dtool_MappingWrapper_Items_Type = {
+  PyVarObject_HEAD_INIT(nullptr, 0)
+  "sequence wrapper",
+  sizeof(Dtool_MappingWrapper),
+  0, // tp_itemsize
+  Dtool_WrapperBase_dealloc,
+  0, // tp_print
+  0, // tp_getattr
+  0, // tp_setattr
+  0, // tp_compare
+  Dtool_MappingWrapper_Items_repr,
+  0, // tp_as_number
+  &Dtool_MappingWrapper_Items_SequenceMethods,
+  0, // tp_as_mapping
+  0, // tp_hash
+  0, // tp_call
+  0, // tp_str
+  PyObject_GenericGetAttr,
+  PyObject_GenericSetAttr,
+  0, // tp_as_buffer
+  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES,
+  0, // tp_doc
+  0, // tp_traverse
+  0, // tp_clear
+  0, // tp_richcompare
+  0, // tp_weaklistoffset
+  PySeqIter_New,
+  0, // tp_iternext
+  0, // tp_methods
+  0, // tp_members
+  0, // tp_getset
+  0, // tp_base
+  0, // tp_dict
+  0, // tp_descr_get
+  0, // tp_descr_set
+  0, // tp_dictoffset
+  0, // tp_init
+  PyType_GenericAlloc,
+  0, // tp_new
+  PyObject_Del,
+  0, // tp_is_gc
+  0, // tp_bases
+  0, // tp_mro
+  0, // tp_cache
+  0, // tp_subclasses
+  0, // tp_weaklist
+  0, // tp_del
+};
+
+/**
+ * This is returned by mapping.keys().
+ */
+static PyObject *Dtool_MappingWrapper_Keys_repr(PyObject *self) {
+  Dtool_WrapperBase *wrap = (Dtool_WrapperBase *)self;
+  nassertr(wrap, nullptr);
+
+  PyObject *repr = PyObject_Repr(wrap->_self);
+  PyObject *result;
+#if PY_MAJOR_VERSION >= 3
+  result = PyUnicode_FromFormat("<%s.keys() of %s>", wrap->_name, PyUnicode_AsUTF8(repr));
+#else
+  result = PyString_FromFormat("<%s.keys() of %s>", wrap->_name, PyString_AS_STRING(repr));
+#endif
+  Py_DECREF(repr);
+  return result;
+}
+
+static PySequenceMethods Dtool_MappingWrapper_Keys_SequenceMethods = {
+  Dtool_SequenceWrapper_length,
+  0, // sq_concat
+  0, // sq_repeat
+  Dtool_MappingWrapper_Items_getitem,
+  0, // sq_slice
+  0, // sq_ass_item
+  0, // sq_ass_slice
+  Dtool_MappingWrapper_contains,
+  0, // sq_inplace_concat
+  0, // sq_inplace_repeat
+};
+
+PyTypeObject Dtool_MappingWrapper_Keys_Type = {
+  PyVarObject_HEAD_INIT(nullptr, 0)
+  "sequence wrapper",
+  sizeof(Dtool_SequenceWrapper),
+  0, // tp_itemsize
+  Dtool_WrapperBase_dealloc,
+  0, // tp_print
+  0, // tp_getattr
+  0, // tp_setattr
+  0, // tp_compare
+  Dtool_MappingWrapper_Keys_repr,
+  0, // tp_as_number
+  &Dtool_SequenceWrapper_SequenceMethods,
+  0, // tp_as_mapping
+  0, // tp_hash
+  0, // tp_call
+  0, // tp_str
+  PyObject_GenericGetAttr,
+  PyObject_GenericSetAttr,
+  0, // tp_as_buffer
+  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES,
+  0, // tp_doc
+  0, // tp_traverse
+  0, // tp_clear
+  0, // tp_richcompare
+  0, // tp_weaklistoffset
+  PySeqIter_New,
+  0, // tp_iternext
+  0, // tp_methods
+  0, // tp_members
+  0, // tp_getset
+  0, // tp_base
+  0, // tp_dict
+  0, // tp_descr_get
+  0, // tp_descr_set
+  0, // tp_dictoffset
+  0, // tp_init
+  PyType_GenericAlloc,
+  0, // tp_new
+  PyObject_Del,
+  0, // tp_is_gc
+  0, // tp_bases
+  0, // tp_mro
+  0, // tp_cache
+  0, // tp_subclasses
+  0, // tp_weaklist
+  0, // tp_del
+};
+
+/**
+ * This is returned by mapping.values().
+ */
+static PyObject *Dtool_MappingWrapper_Values_repr(PyObject *self) {
+  Dtool_WrapperBase *wrap = (Dtool_WrapperBase *)self;
+  nassertr(wrap, nullptr);
+
+  PyObject *repr = PyObject_Repr(wrap->_self);
+  PyObject *result;
+#if PY_MAJOR_VERSION >= 3
+  result = PyUnicode_FromFormat("<%s.values() of %s>", wrap->_name, PyUnicode_AsUTF8(repr));
+#else
+  result = PyString_FromFormat("<%s.values() of %s>", wrap->_name, PyString_AS_STRING(repr));
+#endif
+  Py_DECREF(repr);
+  return result;
+}
+
+static PyObject *Dtool_MappingWrapper_Values_getitem(PyObject *self, Py_ssize_t index) {
+  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)self;
+  nassertr(wrap, nullptr);
+  nassertr(wrap->_keys._getitem_func, nullptr);
+
+  PyObject *key = wrap->_keys._getitem_func(wrap->_base._self, index);
+  if (key != nullptr) {
+    PyObject *value = wrap->_getitem_func(wrap->_base._self, key);
+    Py_DECREF(key);
+    return value;
+  }
+  return nullptr;
+}
+
+static PySequenceMethods Dtool_MappingWrapper_Values_SequenceMethods = {
+  Dtool_SequenceWrapper_length,
+  0, // sq_concat
+  0, // sq_repeat
+  Dtool_MappingWrapper_Values_getitem,
+  0, // sq_slice
+  0, // sq_ass_item
+  0, // sq_ass_slice
+  Dtool_MappingWrapper_contains,
+  0, // sq_inplace_concat
+  0, // sq_inplace_repeat
+};
+
+PyTypeObject Dtool_MappingWrapper_Values_Type = {
+  PyVarObject_HEAD_INIT(nullptr, 0)
+  "sequence wrapper",
+  sizeof(Dtool_MappingWrapper),
+  0, // tp_itemsize
+  Dtool_WrapperBase_dealloc,
+  0, // tp_print
+  0, // tp_getattr
+  0, // tp_setattr
+  0, // tp_compare
+  Dtool_MappingWrapper_Values_repr,
+  0, // tp_as_number
+  &Dtool_MappingWrapper_Values_SequenceMethods,
+  0, // tp_as_mapping
+  0, // tp_hash
+  0, // tp_call
+  0, // tp_str
+  PyObject_GenericGetAttr,
+  PyObject_GenericSetAttr,
+  0, // tp_as_buffer
+  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES,
+  0, // tp_doc
+  0, // tp_traverse
+  0, // tp_clear
+  0, // tp_richcompare
+  0, // tp_weaklistoffset
+  PySeqIter_New,
+  0, // tp_iternext
+  0, // tp_methods
+  0, // tp_members
+  0, // tp_getset
+  0, // tp_base
+  0, // tp_dict
+  0, // tp_descr_get
+  0, // tp_descr_set
+  0, // tp_dictoffset
+  0, // tp_init
+  PyType_GenericAlloc,
+  0, // tp_new
+  PyObject_Del,
+  0, // tp_is_gc
+  0, // tp_bases
+  0, // tp_mro
+  0, // tp_cache
+  0, // tp_subclasses
+  0, // tp_weaklist
+  0, // tp_del
+};
+
+/**
+ * This variant defines only a generator interface.
+ */
+static PyObject *Dtool_GeneratorWrapper_iternext(PyObject *self) {
+  Dtool_GeneratorWrapper *wrap = (Dtool_GeneratorWrapper *)self;
+  nassertr(wrap, nullptr);
+  nassertr(wrap->_iternext_func, nullptr);
+  return wrap->_iternext_func(wrap->_base._self);
+}
+
+PyTypeObject Dtool_GeneratorWrapper_Type = {
+  PyVarObject_HEAD_INIT(nullptr, 0)
+  "generator wrapper",
+  sizeof(Dtool_GeneratorWrapper),
+  0, // tp_itemsize
+  Dtool_WrapperBase_dealloc,
+  0, // tp_print
+  0, // tp_getattr
+  0, // tp_setattr
+  0, // tp_compare
+  0, // tp_repr
+  0, // tp_as_number
+  0, // tp_as_sequence
+  0, // tp_as_mapping
+  0, // tp_hash
+  0, // tp_call
+  0, // tp_str
+  PyObject_GenericGetAttr,
+  PyObject_GenericSetAttr,
+  0, // tp_as_buffer
+  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES,
+  0, // tp_doc
+  0, // tp_traverse
+  0, // tp_clear
+  0, // tp_richcompare
+  0, // tp_weaklistoffset
+  PyObject_SelfIter,
+  Dtool_GeneratorWrapper_iternext,
+  0, // tp_methods
+  0, // tp_members
+  0, // tp_getset
+  0, // tp_base
+  0, // tp_dict
+  0, // tp_descr_get
+  0, // tp_descr_set
+  0, // tp_dictoffset
+  0, // tp_init
+  PyType_GenericAlloc,
+  0, // tp_new
+  PyObject_Del,
+  0, // tp_is_gc
+  0, // tp_bases
+  0, // tp_mro
+  0, // tp_cache
+  0, // tp_subclasses
+  0, // tp_weaklist
+  0, // tp_del
+};
+
+/**
+ * This is a variant of the Python getset mechanism that permits static
+ * properties.
+ */
+static void
+Dtool_StaticProperty_dealloc(PyDescrObject *descr) {
+  _PyObject_GC_UNTRACK(descr);
+  Py_XDECREF(descr->d_type);
+  Py_XDECREF(descr->d_name);
+//#if PY_MAJOR_VERSION >= 3
+//  Py_XDECREF(descr->d_qualname);
+//#endif
+  PyObject_GC_Del(descr);
+}
+
+static PyObject *
+Dtool_StaticProperty_repr(PyDescrObject *descr, const char *format) {
+#if PY_MAJOR_VERSION >= 3
+  return PyUnicode_FromFormat("<attribute '%s' of '%s'>",
+                              PyUnicode_AsUTF8(descr->d_name),
+                              descr->d_type->tp_name);
+#else
+  return PyString_FromFormat("<attribute '%s' of '%s'>",
+                             PyString_AS_STRING(descr->d_name),
+                             descr->d_type->tp_name);
+#endif
+}
+
+static int
+Dtool_StaticProperty_traverse(PyObject *self, visitproc visit, void *arg) {
+  PyDescrObject *descr = (PyDescrObject *)self;
+  Py_VISIT(descr->d_type);
+  return 0;
+}
+
+static PyObject *
+Dtool_StaticProperty_get(PyGetSetDescrObject *descr, PyObject *obj, PyObject *type) {
+  if (descr->d_getset->get != nullptr) {
+    return descr->d_getset->get(obj, descr->d_getset->closure);
+  } else {
+    return PyErr_Format(PyExc_AttributeError,
+                        "attribute '%s' of type '%.100s' is not readable",
+#if PY_MAJOR_VERSION >= 3
+                        PyUnicode_AsUTF8(((PyDescrObject *)descr)->d_name),
+#else
+                        PyString_AS_STRING(((PyDescrObject *)descr)->d_name),
+#endif
+                        ((PyDescrObject *)descr)->d_type->tp_name);
+  }
+}
+
+static int
+Dtool_StaticProperty_set(PyGetSetDescrObject *descr, PyObject *obj, PyObject *value) {
+  if (descr->d_getset->set != nullptr) {
+    return descr->d_getset->set(obj, value, descr->d_getset->closure);
+  } else {
+    PyErr_Format(PyExc_AttributeError,
+                 "attribute '%s' of type '%.100s' is not writable",
+#if PY_MAJOR_VERSION >= 3
+                 PyUnicode_AsUTF8(((PyDescrObject *)descr)->d_name),
+#else
+                 PyString_AS_STRING(((PyDescrObject *)descr)->d_name),
+#endif
+                 ((PyDescrObject *)descr)->d_type->tp_name);
+    return -1;
+  }
+}
+
+PyTypeObject Dtool_StaticProperty_Type = {
+  PyVarObject_HEAD_INIT(&PyType_Type, 0)
+  "getset_descriptor",
+  sizeof(PyGetSetDescrObject),
+  0, // tp_itemsize
+  (destructor)Dtool_StaticProperty_dealloc,
+  0, // tp_print
+  0, // tp_getattr
+  0, // tp_setattr
+  0, // tp_reserved
+  (reprfunc)Dtool_StaticProperty_repr,
+  0, // tp_as_number
+  0, // tp_as_sequence
+  0, // tp_as_mapping
+  0, // tp_hash
+  0, // tp_call
+  0, // tp_str
+  PyObject_GenericGetAttr,
+  0, // tp_setattro
+  0, // tp_as_buffer
+  Py_TPFLAGS_DEFAULT,
+  0, // tp_doc
+  Dtool_StaticProperty_traverse,
+  0, // tp_clear
+  0, // tp_richcompare
+  0, // tp_weaklistoffset
+  0, // tp_iter
+  0, // tp_iternext
+  0, // tp_methods
+  0, // tp_members
+  0, // tp_getset
+  0, // tp_base
+  0, // tp_dict
+  (descrgetfunc)Dtool_StaticProperty_get,
+  (descrsetfunc)Dtool_StaticProperty_set,
+  0, // tp_dictoffset
+  0, // tp_init
+  0, // tp_alloc
+  0, // tp_new
+  0, // tp_del
+  0, // tp_is_gc
+  0, // tp_bases
+  0, // tp_mro
+  0, // tp_cache
+  0, // tp_subclasses
+  0, // tp_weaklist
+  0, // tp_del
+};
+
+/**
+ * This wraps around a property that exposes a sequence interface.
+ */
+Dtool_SequenceWrapper *Dtool_NewSequenceWrapper(PyObject *self, const char *name) {
+  Dtool_SequenceWrapper *wrap = (Dtool_SequenceWrapper *)PyObject_MALLOC(sizeof(Dtool_SequenceWrapper));
+  if (wrap == nullptr) {
+    return (Dtool_SequenceWrapper *)PyErr_NoMemory();
+  }
+
+  // If the collections.abc module is loaded, register this as a subclass.
+  static bool registered = false;
+  if (!registered) {
+    registered = true;
+    _register_collection((PyTypeObject *)&Dtool_MutableSequenceWrapper_Type, "Sequence");
+  }
+
+  PyObject_INIT(wrap, &Dtool_SequenceWrapper_Type);
+  Py_XINCREF(self);
+  wrap->_base._self = self;
+  wrap->_base._name = name;
+  wrap->_len_func = nullptr;
+  wrap->_getitem_func = nullptr;
+  return wrap;
+}
+
+/**
+ * This wraps around a property that exposes a mutable sequence interface.
+ */
+Dtool_MutableSequenceWrapper *Dtool_NewMutableSequenceWrapper(PyObject *self, const char *name) {
+  Dtool_MutableSequenceWrapper *wrap = (Dtool_MutableSequenceWrapper *)PyObject_MALLOC(sizeof(Dtool_MutableSequenceWrapper));
+  if (wrap == nullptr) {
+    return (Dtool_MutableSequenceWrapper *)PyErr_NoMemory();
+  }
+
+  // If the collections.abc module is loaded, register this as a subclass.
+  static bool registered = false;
+  if (!registered) {
+    registered = true;
+    _register_collection((PyTypeObject *)&Dtool_MutableSequenceWrapper_Type, "MutableSequence");
+  }
+
+  PyObject_INIT(wrap, &Dtool_MutableSequenceWrapper_Type);
+  Py_XINCREF(self);
+  wrap->_base._self = self;
+  wrap->_base._name = name;
+  wrap->_len_func = nullptr;
+  wrap->_getitem_func = nullptr;
+  wrap->_setitem_func = nullptr;
+  wrap->_insert_func = nullptr;
+  return wrap;
+}
+
+/**
+ * This wraps around a mapping interface, with getitem function.
+ */
+Dtool_MappingWrapper *Dtool_NewMappingWrapper(PyObject *self, const char *name) {
+  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)PyObject_MALLOC(sizeof(Dtool_MappingWrapper));
+  if (wrap == nullptr) {
+    return (Dtool_MappingWrapper *)PyErr_NoMemory();
+  }
+
+  // If the collections.abc module is loaded, register this as a subclass.
+  static bool registered = false;
+  if (!registered) {
+    registered = true;
+    _register_collection((PyTypeObject *)&Dtool_MappingWrapper_Type, "Mapping");
+  }
+
+  PyObject_INIT(wrap, &Dtool_MappingWrapper_Type);
+  Py_XINCREF(self);
+  wrap->_base._self = self;
+  wrap->_base._name = name;
+  wrap->_keys._len_func = nullptr;
+  wrap->_keys._getitem_func = nullptr;
+  wrap->_getitem_func = nullptr;
+  wrap->_setitem_func = nullptr;
+  return wrap;
+}
+
+/**
+ * This wraps around a mapping interface, with getitem/setitem functions.
+ */
+Dtool_MappingWrapper *Dtool_NewMutableMappingWrapper(PyObject *self, const char *name) {
+  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)PyObject_MALLOC(sizeof(Dtool_MappingWrapper));
+  if (wrap == nullptr) {
+    return (Dtool_MappingWrapper *)PyErr_NoMemory();
+  }
+
+  // If the collections.abc module is loaded, register this as a subclass.
+  static bool registered = false;
+  if (!registered) {
+    registered = true;
+    _register_collection((PyTypeObject *)&Dtool_MutableMappingWrapper_Type, "MutableMapping");
+  }
+
+  PyObject_INIT(wrap, &Dtool_MutableMappingWrapper_Type);
+  Py_XINCREF(self);
+  wrap->_base._self = self;
+  wrap->_base._name = name;
+  wrap->_keys._len_func = nullptr;
+  wrap->_keys._getitem_func = nullptr;
+  wrap->_getitem_func = nullptr;
+  wrap->_setitem_func = nullptr;
+  return wrap;
+}
+
+/**
+ * This is a variant of the Python getset mechanism that permits static
+ * properties.
+ */
+PyObject *
+Dtool_NewStaticProperty(PyTypeObject *type, const PyGetSetDef *getset) {
+  PyGetSetDescrObject *descr;
+  descr = (PyGetSetDescrObject *)PyType_GenericAlloc(&Dtool_StaticProperty_Type, 0);
+  if (descr != nullptr) {
+    Py_XINCREF(type);
+    descr->d_getset = (PyGetSetDef *)getset;
+#if PY_MAJOR_VERSION >= 3
+    descr->d_common.d_type = type;
+    descr->d_common.d_name = PyUnicode_InternFromString(getset->name);
+#if PY_VERSION_HEX >= 0x03030000
+    descr->d_common.d_qualname = nullptr;
+#endif
+#else
+    descr->d_type = type;
+    descr->d_name = PyString_InternFromString(getset->name);
+#endif
+  }
+  return (PyObject *)descr;
+}
+
+#endif  // HAVE_PYTHON

+ 78 - 0
dtool/src/interrogatedb/py_wrappers.h

@@ -0,0 +1,78 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file py_wrappers.h
+ * @author rdb
+ * @date 2017-11-26
+ */
+
+#ifndef PY_WRAPPERS_H
+#define PY_WRAPPERS_H
+
+#include "py_panda.h"
+
+#ifdef HAVE_PYTHON
+
+/**
+ * These classes are returned from properties that require a subscript
+ * interface, ie. something.children[i] = 3.
+ */
+struct Dtool_WrapperBase {
+  PyObject_HEAD;
+  PyObject *_self;
+  const char *_name;
+};
+
+struct Dtool_SequenceWrapper {
+  Dtool_WrapperBase _base;
+  lenfunc _len_func;
+  ssizeargfunc _getitem_func;
+};
+
+struct Dtool_MutableSequenceWrapper {
+  Dtool_WrapperBase _base;
+  lenfunc _len_func;
+  ssizeargfunc _getitem_func;
+  ssizeobjargproc _setitem_func;
+  PyObject *(*_insert_func)(PyObject *, size_t, PyObject *);
+};
+
+struct Dtool_MappingWrapper {
+  union {
+    Dtool_WrapperBase _base;
+    Dtool_SequenceWrapper _keys;
+  };
+  binaryfunc _getitem_func;
+  objobjargproc _setitem_func;
+};
+
+struct Dtool_GeneratorWrapper {
+  Dtool_WrapperBase _base;
+  iternextfunc _iternext_func;
+};
+
+EXPCL_INTERROGATEDB extern PyTypeObject Dtool_SequenceWrapper_Type;
+EXPCL_INTERROGATEDB extern PyTypeObject Dtool_MutableSequenceWrapper_Type;
+EXPCL_INTERROGATEDB extern PyTypeObject Dtool_MappingWrapper_Type;
+EXPCL_INTERROGATEDB extern PyTypeObject Dtool_MutableMappingWrapper_Type;
+EXPCL_INTERROGATEDB extern PyTypeObject Dtool_MappingWrapper_Items_Type;
+EXPCL_INTERROGATEDB extern PyTypeObject Dtool_MappingWrapper_Keys_Type;
+EXPCL_INTERROGATEDB extern PyTypeObject Dtool_MappingWrapper_Values_Type;
+EXPCL_INTERROGATEDB extern PyTypeObject Dtool_GeneratorWrapper_Type;
+EXPCL_INTERROGATEDB extern PyTypeObject Dtool_StaticProperty_Type;
+
+EXPCL_INTERROGATEDB Dtool_SequenceWrapper *Dtool_NewSequenceWrapper(PyObject *self, const char *name);
+EXPCL_INTERROGATEDB Dtool_MutableSequenceWrapper *Dtool_NewMutableSequenceWrapper(PyObject *self, const char *name);
+EXPCL_INTERROGATEDB Dtool_MappingWrapper *Dtool_NewMappingWrapper(PyObject *self, const char *name);
+EXPCL_INTERROGATEDB Dtool_MappingWrapper *Dtool_NewMutableMappingWrapper(PyObject *self, const char *name);
+EXPCL_INTERROGATEDB PyObject *Dtool_NewGenerator(PyObject *self, const char *name, iternextfunc func);
+EXPCL_INTERROGATEDB PyObject *Dtool_NewStaticProperty(PyTypeObject *obj, const PyGetSetDef *getset);
+
+#endif  // HAVE_PYTHON
+
+#endif  // PY_WRAPPERS_H

+ 9 - 0
dtool/src/parser-inc/iostream

@@ -88,6 +88,9 @@ __published:
   streampos tellp();
   void seekp(streampos pos);
   void seekp(streamoff off, ios_base::seekdir dir);
+
+protected:
+  ostream(ostream &&);
 };
 class istream : virtual public ios {
 __published:
@@ -97,12 +100,18 @@ __published:
   streampos tellg();
   void seekg(streampos pos);
   void seekg(streamoff off, ios_base::seekdir dir);
+
+protected:
+  istream(istream &&);
 };
 class iostream : public istream, public ostream {
 __published:
   iostream(const iostream&) = delete;
 
   void flush();
+
+protected:
+  iostream(iostream &&);
 };
 
 class ofstream : public ostream {

+ 1 - 1
dtool/src/prc/configVariable.h

@@ -35,7 +35,7 @@ protected:
                         const string &description, int flags);
 
 PUBLISHED:
-  INLINE ConfigVariable(const string &name);
+  INLINE explicit ConfigVariable(const string &name);
   INLINE ~ConfigVariable();
 
   INLINE const string &get_string_value() const;

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików