Procházet zdrojové kódy

Merge branch 'master' of github.com:panda3d/panda3d into deploy-ng

rdb před 8 roky
rodič
revize
c911ba8059
100 změnil soubory, kde provedl 2798 přidání a 1024 odebrání
  1. 3 1
      direct/src/interval/Interval.py
  2. 26 12
      direct/src/showbase/Loader.py
  3. 11 1
      direct/src/showbase/Messenger.py
  4. 53 45
      direct/src/showbase/ShowBase.py
  5. 5 1
      direct/src/task/Task.py
  6. 1 1
      dtool/src/dtoolutil/filename_ext.cxx
  7. 8 0
      dtool/src/interrogate/functionRemap.cxx
  8. 27 29
      dtool/src/interrogate/interfaceMakerPythonNative.cxx
  9. 1 0
      dtool/src/interrogatedb/p3interrogatedb_composite2.cxx
  10. 54 0
      dtool/src/interrogatedb/py_compat.cxx
  11. 175 0
      dtool/src/interrogatedb/py_compat.h
  12. 41 7
      dtool/src/interrogatedb/py_panda.I
  13. 33 91
      dtool/src/interrogatedb/py_panda.cxx
  14. 23 136
      dtool/src/interrogatedb/py_panda.h
  15. 4 0
      dtool/src/interrogatedb/py_wrappers.cxx
  16. 5 1
      dtool/src/interrogatedb/py_wrappers.h
  17. 1 1
      makepanda/installer.nsi
  18. 4 2
      makepanda/makepanda.py
  19. 0 1
      makepanda/makewheel.py
  20. 7 17
      panda/src/audio/audioLoadRequest.I
  21. 1 2
      panda/src/audio/audioLoadRequest.cxx
  22. 0 5
      panda/src/audio/audioLoadRequest.h
  23. 3 3
      panda/src/cocoadisplay/cocoaGraphicsBuffer.I
  24. 61 0
      panda/src/cocoadisplay/cocoaGraphicsBuffer.h
  25. 165 0
      panda/src/cocoadisplay/cocoaGraphicsBuffer.mm
  26. 0 8
      panda/src/cocoadisplay/cocoaGraphicsPipe.I
  27. 2 8
      panda/src/cocoadisplay/cocoaGraphicsPipe.h
  28. 23 112
      panda/src/cocoadisplay/cocoaGraphicsPipe.mm
  29. 18 0
      panda/src/cocoadisplay/cocoaGraphicsStateGuardian.I
  30. 4 0
      panda/src/cocoadisplay/cocoaGraphicsStateGuardian.h
  31. 16 1
      panda/src/cocoadisplay/cocoaGraphicsStateGuardian.mm
  32. 32 12
      panda/src/cocoadisplay/cocoaGraphicsWindow.mm
  33. 2 0
      panda/src/cocoadisplay/config_cocoadisplay.mm
  34. 1 0
      panda/src/cocoadisplay/p3cocoadisplay_composite1.mm
  35. 10 4
      panda/src/display/graphicsOutput.I
  36. 16 5
      panda/src/display/graphicsOutput.cxx
  37. 3 2
      panda/src/display/graphicsOutput.h
  38. 10 9
      panda/src/display/graphicsStateGuardian.cxx
  39. 1 1
      panda/src/display/graphicsStateGuardian.h
  40. 200 0
      panda/src/event/asyncFuture.I
  41. 376 0
      panda/src/event/asyncFuture.cxx
  42. 199 0
      panda/src/event/asyncFuture.h
  43. 318 0
      panda/src/event/asyncFuture_ext.cxx
  44. 12 6
      panda/src/event/asyncFuture_ext.h
  45. 0 9
      panda/src/event/asyncTask.I
  46. 65 16
      panda/src/event/asyncTask.cxx
  47. 10 15
      panda/src/event/asyncTask.h
  48. 17 29
      panda/src/event/asyncTaskChain.cxx
  49. 2 1
      panda/src/event/asyncTaskChain.h
  50. 19 19
      panda/src/event/asyncTaskCollection.cxx
  51. 5 5
      panda/src/event/asyncTaskCollection.h
  52. 1 1
      panda/src/event/asyncTaskManager.I
  53. 6 14
      panda/src/event/asyncTaskManager.cxx
  54. 4 3
      panda/src/event/asyncTaskManager.h
  55. 0 75
      panda/src/event/asyncTask_ext.cxx
  56. 3 0
      panda/src/event/config_event.cxx
  57. 32 0
      panda/src/event/eventHandler.cxx
  58. 7 1
      panda/src/event/eventHandler.h
  59. 0 7
      panda/src/event/eventParameter.I
  60. 2 1
      panda/src/event/eventParameter.h
  61. 1 0
      panda/src/event/p3event_composite1.cxx
  62. 3 8
      panda/src/event/pythonTask.I
  63. 54 84
      panda/src/event/pythonTask.cxx
  64. 11 7
      panda/src/event/pythonTask.h
  65. 22 19
      panda/src/express/pointerToArray_ext.I
  66. 31 14
      panda/src/glstuff/glGraphicsBuffer_src.cxx
  67. 0 2
      panda/src/glstuff/glGraphicsBuffer_src.h
  68. 21 4
      panda/src/glstuff/glGraphicsStateGuardian_src.cxx
  69. 4 3
      panda/src/gobj/animateVerticesRequest.I
  70. 0 1
      panda/src/gobj/animateVerticesRequest.cxx
  71. 1 2
      panda/src/gobj/animateVerticesRequest.h
  72. 1 0
      panda/src/gobj/config_gobj.cxx
  73. 147 7
      panda/src/gobj/preparedGraphicsObjects.cxx
  74. 54 2
      panda/src/gobj/preparedGraphicsObjects.h
  75. 3 2
      panda/src/gobj/shader.cxx
  76. 2 1
      panda/src/gobj/shader.h
  77. 59 4
      panda/src/gobj/texture.I
  78. 108 33
      panda/src/gobj/texture.cxx
  79. 10 5
      panda/src/gobj/texture.h
  80. 55 2
      panda/src/gobj/texturePeeker.cxx
  81. 2 0
      panda/src/gobj/texturePeeker.h
  82. 4 3
      panda/src/gobj/textureReloadRequest.I
  83. 0 1
      panda/src/gobj/textureReloadRequest.cxx
  84. 2 1
      panda/src/gobj/textureReloadRequest.h
  85. 33 16
      panda/src/gobj/texture_ext.cxx
  86. 2 0
      panda/src/pgraph/cullResult.cxx
  87. 1 1
      panda/src/pgraph/cullTraverser.cxx
  88. 5 4
      panda/src/pgraph/geomNode.cxx
  89. 1 0
      panda/src/pgraph/loader.I
  90. 8 20
      panda/src/pgraph/modelFlattenRequest.I
  91. 2 2
      panda/src/pgraph/modelFlattenRequest.cxx
  92. 0 5
      panda/src/pgraph/modelFlattenRequest.h
  93. 11 18
      panda/src/pgraph/modelLoadRequest.I
  94. 3 4
      panda/src/pgraph/modelLoadRequest.cxx
  95. 0 5
      panda/src/pgraph/modelLoadRequest.h
  96. 5 13
      panda/src/pgraph/modelSaveRequest.I
  97. 0 2
      panda/src/pgraph/modelSaveRequest.cxx
  98. 0 4
      panda/src/pgraph/modelSaveRequest.h
  99. 1 2
      panda/src/pgraph/nodePathCollection_ext.cxx
  100. 3 3
      panda/src/pgraph/nodePath_ext.cxx

+ 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

+ 26 - 12
direct/src/showbase/Loader.py

@@ -50,10 +50,10 @@ class Loader(DirectObject):
         def cancel(self):
             "Cancels the request.  Callback won't be called."
             if self._loader:
-                self._loader = None
                 for request in self.requests:
                     self._loader.loader.remove(request)
                     del self._loader._requests[request]
+                self._loader = None
                 self.requests = None
                 self.requestList = None
 
@@ -66,7 +66,9 @@ class Loader(DirectObject):
             return not self.requests
 
         def result(self):
-            assert not self.requests, "Result is not ready."
+            "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:
@@ -132,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
@@ -169,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
@@ -224,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 = []
@@ -362,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
@@ -397,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 = []
@@ -1059,7 +1069,7 @@ class Loader(DirectObject):
             return
 
         cb, i = self._requests[request]
-        if cb.cancelled():
+        if cb.cancelled() or request.cancelled():
             # Shouldn't be here.
             del self._requests[request]
             return
@@ -1068,7 +1078,11 @@ class Loader(DirectObject):
         if not cb.requests:
             del self._requests[request]
 
-        cb.gotObject(i, request.result() or None)
+        result = request.result()
+        if isinstance(result, PandaNode):
+            result = NodePath(result)
+
+        cb.gotObject(i, result)
 
     load_model = loadModel
     unload_model = unloadModel

+ 11 - 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

+ 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")

+ 5 - 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
@@ -84,6 +86,8 @@ Task.DtoolClassDict['exit'] = exit
 pause = AsyncTaskPause
 Task.DtoolClassDict['pause'] = staticmethod(pause)
 
+gather = Task.gather
+
 def sequence(*taskList):
     seq = AsyncTaskSequence('sequence')
     for task in taskList:

+ 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;
   }
 

+ 8 - 0
dtool/src/interrogate/functionRemap.cxx

@@ -797,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;
     }
   }
 
@@ -909,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;

+ 27 - 29
dtool/src/interrogate/interfaceMakerPythonNative.cxx

@@ -1094,14 +1094,14 @@ write_class_details(ostream &out, Object *obj) {
   // Write support methods to cast from and to pointers of this type.
   {
     out << "static void *Dtool_UpcastInterface_" << ClassName << "(PyObject *self, Dtool_PyTypedObject *requested_type) {\n";
-    out << "  Dtool_PyTypedObject *SelfType = ((Dtool_PyInstDef *)self)->_My_Type;\n";
-    out << "  if (SelfType != Dtool_Ptr_" << ClassName << ") {\n";
+    out << "  Dtool_PyTypedObject *type = DtoolInstance_TYPE(self);\n";
+    out << "  if (type != &Dtool_" << ClassName << ") {\n";
     out << "    printf(\"" << ClassName << " ** Bad Source Type-- Requesting Conversion from %s to %s\\n\", Py_TYPE(self)->tp_name, requested_type->_PyType.tp_name); fflush(NULL);\n";;
     out << "    return NULL;\n";
     out << "  }\n";
     out << "\n";
-    out << "  " << cClassName << " *local_this = (" << cClassName << " *)((Dtool_PyInstDef *)self)->_ptr_to_object;\n";
-    out << "  if (requested_type == Dtool_Ptr_" << ClassName << ") {\n";
+    out << "  " << cClassName << " *local_this = (" << cClassName << " *)DtoolInstance_VOID_PTR(self);\n";
+    out << "  if (requested_type == &Dtool_" << ClassName << ") {\n";
     out << "    return local_this;\n";
     out << "  }\n";
 
@@ -2220,13 +2220,13 @@ write_module_class(ostream &out, Object *obj) {
           // provide a writable buffer or a readonly buffer.
           const string const_this = "(const " + cClassName + " *)local_this";
           if (remap_const != NULL && remap_nonconst != NULL) {
-            out << "  if (!((Dtool_PyInstDef *)self)->_is_const) {\n";
+            out << "  if (!DtoolInstance_IS_CONST(self)) {\n";
             out << "    return " << remap_nonconst->call_function(out, 4, false, "local_this", params_nonconst) << ";\n";
             out << "  } else {\n";
             out << "    return " << remap_const->call_function(out, 4, false, const_this, params_const) << ";\n";
             out << "  }\n";
           } else if (remap_nonconst != NULL) {
-            out << "  if (!((Dtool_PyInstDef *)self)->_is_const) {\n";
+            out << "  if (!DtoolInstance_IS_CONST(self)) {\n";
             out << "    return " << remap_nonconst->call_function(out, 4, false, "local_this", params_nonconst) << ";\n";
             out << "  } else {\n";
             out << "    Dtool_Raise_TypeError(\"Cannot call " << ClassName << ".__getbuffer__() on a const object.\");\n";
@@ -2285,7 +2285,7 @@ write_module_class(ostream &out, Object *obj) {
           string return_expr;
           const string const_this = "(const " + cClassName + " *)local_this";
           if (remap_const != NULL && remap_nonconst != NULL) {
-            out << "  if (!((Dtool_PyInstDef *)self)->_is_const) {\n";
+            out << "  if (!DtoolInstance_IS_CONST(self)) {\n";
             return_expr = remap_nonconst->call_function(out, 4, false, "local_this", params_nonconst);
             if (!return_expr.empty()) {
               out << "    " << return_expr << ";\n";
@@ -3078,7 +3078,7 @@ write_module_class(ostream &out, Object *obj) {
 
     out << "    Dtool_" << ClassName << "._PyType.tp_bases = PyTuple_Pack(" << bases.size() << baseargs << ");\n";
   } else {
-    out << "    Dtool_" << ClassName << "._PyType.tp_base = (PyTypeObject *)Dtool_Ptr_DTOOL_SUPER_BASE;\n";
+    out << "    Dtool_" << ClassName << "._PyType.tp_base = (PyTypeObject *)&Dtool_DTOOL_SUPER_BASE;\n";
   }
 
   int num_nested = obj->_itype.number_of_nested_types();
@@ -3515,8 +3515,9 @@ write_function_for_name(ostream &out, Object *obj,
       out << "  if (!Dtool_Call_ExtractThisPointer_NonConst(self, Dtool_" << ClassName << ", "
           << "(void **)&local_this, \"" << classNameFromCppName(cClassName, false)
           << "." << methodNameFromCppName(remap, cClassName, false) << "\")) {\n";
+
     } else {
-      out << "  if (!Dtool_Call_ExtractThisPointer(self, Dtool_" << ClassName << ", (void **)&local_this)) {\n";
+      out << "  if (!DtoolInstance_GetPointer(self, local_this, Dtool_" << ClassName << ")) {\n";
     }
 
     error_return(out, 4, return_flags);
@@ -3616,7 +3617,7 @@ write_function_for_name(ostream &out, Object *obj,
       if (strip_keyword_args) {
         // None of the remaps take any keyword arguments, so let's check that
         // we take none.  This saves some checks later on.
-        indent(out, 4) << "if (kwds == NULL || ((PyDictObject *)kwds)->ma_used == 0) {\n";
+        indent(out, 4) << "if (kwds == NULL || PyDict_GET_SIZE(kwds) == 0) {\n";
         if (min_args == 1 && min_args == 1) {
           indent(out, 4) << "  PyObject *arg = PyTuple_GET_ITEM(args, 0);\n";
           write_function_forset(out, mii->second, min_args, max_args, expected_params, 6,
@@ -3890,11 +3891,10 @@ write_coerce_constructor(ostream &out, Object *obj, bool is_const) {
     // Note: this relies on the PT() being initialized to NULL.  This is
     // currently the case in all invocations, but this may not be true in the
     // future.
-    out << "  DTOOL_Call_ExtractThisPointerForType(args, &Dtool_" << ClassName << ", (void**)&coerced.cheat());\n";
-    out << "  if (coerced != NULL) {\n";
+    out << "  if (DtoolInstance_GetPointer(args, coerced.cheat(), Dtool_" << ClassName << ")) {\n";
     out << "    // The argument is already of matching type, no need to coerce.\n";
     if (!is_const) {
-      out << "    if (!((Dtool_PyInstDef *)args)->_is_const) {\n";
+      out << "    if (!DtoolInstance_IS_CONST(args)) {\n";
       out << "      // A non-const instance is required, which this is.\n";
       out << "      coerced->ref();\n";
       out << "      return true;\n";
@@ -3909,9 +3909,8 @@ write_coerce_constructor(ostream &out, Object *obj, bool is_const) {
     out << cClassName << " *Dtool_Coerce_" << ClassName << "(PyObject *args, " << cClassName << " &coerced) {\n";
 
     out << "  " << cClassName << " *local_this;\n";
-    out << "  DTOOL_Call_ExtractThisPointerForType(args, &Dtool_" << ClassName << ", (void**)&local_this);\n";
-    out << "  if (local_this != NULL) {\n";
-    out << "    if (((Dtool_PyInstDef *)args)->_is_const) {\n";
+    out << "  if (DtoolInstance_GetPointer(args, local_this, Dtool_" << ClassName << ")) {\n";
+    out << "    if (DtoolInstance_IS_CONST(args)) {\n";
     out << "      // This is a const object.  Make a copy.\n";
     out << "      coerced = *(const " << cClassName << " *)local_this;\n";
     out << "      return &coerced;\n";
@@ -4340,7 +4339,7 @@ write_function_forset(ostream &out,
     if (all_nonconst) {
       // Yes, they do.  Check that the parameter has the required constness.
       indent(out, indent_level)
-        << "if (!((Dtool_PyInstDef *)self)->_is_const) {\n";
+        << "if (!DtoolInstance_IS_CONST(self)) {\n";
       indent_level += 2;
       verify_const = false;
     }
@@ -4406,7 +4405,7 @@ write_function_forset(ostream &out,
       if (verify_const && (remap->_has_this && !remap->_const_method)) {
         // If it's a non-const method, we only allow a non-const this.
         indent(out, indent_level)
-          << "if (!((Dtool_PyInstDef *)self)->_is_const) {\n";
+          << "if (!DtoolInstance_IS_CONST(self)) {\n";
       } else {
         indent(out, indent_level)
           << "{\n";
@@ -4441,7 +4440,7 @@ write_function_forset(ostream &out,
 
         if (verify_const && (remap->_has_this && !remap->_const_method)) {
           indent(out, indent_level)
-            << "if (!((Dtool_PyInstDef *)self)->_is_const) {\n";
+            << "if (!DtoolInstance_IS_CONST(self)) {\n";
         } else {
           indent(out, indent_level)
             << "{\n";
@@ -5525,14 +5524,13 @@ write_function_instance(ostream &out, FunctionRemap *remap,
           // This function does the same thing in this case and is slightly
           // simpler.  But maybe we should just reorganize these functions
           // entirely?
-          extra_convert << ";\n";
-          if (is_optional) {
-            extra_convert << "  ";
-          }
-          extra_convert
-            << "DTOOL_Call_ExtractThisPointerForType(" << param_name
-            << ", Dtool_Ptr_" << make_safe_name(class_name)
-            << ", (void **)&" << param_name << "_this);\n";
+          extra_convert << " = NULL;\n";
+          int indent_level = is_optional ? 2 : 0;
+          indent(extra_convert, indent_level)
+            << "DtoolInstance_GetPointer(" << param_name
+            << ", " << param_name << "_this"
+            << ", *Dtool_Ptr_" << make_safe_name(class_name)
+            << ");\n";
         } else {
           extra_convert << boolalpha
             << " = (" << class_name << " *)"
@@ -6796,7 +6794,7 @@ write_getset(ostream &out, Object *obj, Property *property) {
     out << "  if (wrap != NULL) {\n"
            "    wrap->_getitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Mapping_Getitem;\n";
     if (!property->_setter_remaps.empty()) {
-      out << "    if (!((Dtool_PyInstDef *)self)->_is_const) {\n";
+      out << "    if (!DtoolInstance_IS_CONST(self)) {\n";
       out << "      wrap->_setitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Mapping_Setitem;\n";
       out << "    }\n";
     }
@@ -6828,7 +6826,7 @@ write_getset(ostream &out, Object *obj, Property *property) {
         "    wrap->_len_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Len;\n"
         "    wrap->_getitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Sequence_Getitem;\n";
       if (!property->_setter_remaps.empty()) {
-        out << "    if (!((Dtool_PyInstDef *)self)->_is_const) {\n";
+        out << "    if (!DtoolInstance_IS_CONST(self)) {\n";
         out << "      wrap->_setitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Sequence_Setitem;\n";
         if (property->_inserter != nullptr) {
           out << "      wrap->_insert_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Sequence_insert;\n";

+ 1 - 0
dtool/src/interrogatedb/p3interrogatedb_composite2.cxx

@@ -5,4 +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

+ 41 - 7
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);
 }
 
 /**

+ 33 - 91
dtool/src/interrogatedb/py_panda.cxx

@@ -17,8 +17,6 @@
 
 #ifdef HAVE_PYTHON
 
-PyTupleObject Dtool_EmptyTuple;
-
 PyMemberDef standard_type_members[] = {
   {(char *)"this", (sizeof(void*) == sizeof(int)) ? T_UINT : T_ULONGLONG, offsetof(Dtool_PyInstDef, _ptr_to_object), READONLY, (char *)"C++ 'this' pointer, if any"},
   {(char *)"this_ownership", T_BOOL, offsetof(Dtool_PyInstDef, _memory_rules), READONLY, (char *)"C++ 'this' ownership rules"},
@@ -33,67 +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
-
-/**
- * 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;
   }
 }
 
@@ -103,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;
 }
 
@@ -123,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.",
@@ -136,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;
 }
 
@@ -166,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;
       }
 
@@ -187,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;
     }
   }
 
@@ -195,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;
 }
 
 /**
@@ -646,8 +588,9 @@ PyObject *Dtool_PyModuleInitHelper(LibraryDef *defs[], const char *modulename) {
       return Dtool_Raise_TypeError("PyType_Ready(Dtool_StaticProperty_Type)");
     }
 
-    // Initialize the "empty tuple".
-    (void)PyObject_INIT_VAR((PyObject *)&Dtool_EmptyTuple, &PyTuple_Type, 0);
+#ifdef Py_TRACE_REFS
+    _Py_AddToAllObjects((PyObject *)&Dtool_EmptyTuple, 0);
+#endif
 
     // Initialize the base class of everything.
     Dtool_PyModuleClassInit_DTOOL_SUPER_BASE(NULL);
@@ -749,7 +692,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;
 
@@ -794,9 +737,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;
 }
@@ -969,14 +911,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
@@ -997,7 +939,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;
   }
@@ -1015,12 +957,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)) {
@@ -1047,7 +989,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) {

+ 23 - 136
dtool/src/interrogatedb/py_panda.h

@@ -20,131 +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
-
-extern EXPCL_INTERROGATEDB PyTupleObject Dtool_EmptyTuple;
-
-#ifndef _PyObject_CallNoArg
-#define _PyObject_CallNoArg(func) PyObject_Call((func), (PyObject *)&Dtool_EmptyTuple, NULL)
-#endif
-
 using namespace std;
 
 // this is tempory .. untill this is glued better into the panda build system
@@ -243,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"; \
@@ -255,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);\
@@ -265,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);\
@@ -279,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
@@ -308,20 +201,14 @@ 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() (UNLIKELY(_PyErr_OCCURRED() != NULL))
+#define Dtool_CheckErrorOccurred() (UNLIKELY(_PyErr_OCCURRED() != nullptr))
 #else
 #define Dtool_CheckErrorOccurred() (UNLIKELY(_Dtool_CheckErrorOccurred()))
 #endif
@@ -348,8 +235,8 @@ 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)

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

@@ -13,6 +13,8 @@
 
 #include "py_wrappers.h"
 
+#ifdef HAVE_PYTHON
+
 #if PY_VERSION_HEX >= 0x03040000
 #define _COLLECTIONS_ABC "_collections_abc"
 #elif PY_VERSION_HEX >= 0x03030000
@@ -1669,3 +1671,5 @@ Dtool_NewStaticProperty(PyTypeObject *type, const PyGetSetDef *getset) {
   }
   return (PyObject *)descr;
 }
+
+#endif  // HAVE_PYTHON

+ 5 - 1
dtool/src/interrogatedb/py_wrappers.h

@@ -16,6 +16,8 @@
 
 #include "py_panda.h"
 
+#ifdef HAVE_PYTHON
+
 /**
  * These classes are returned from properties that require a subscript
  * interface, ie. something.children[i] = 3.
@@ -71,4 +73,6 @@ EXPCL_INTERROGATEDB Dtool_MappingWrapper *Dtool_NewMutableMappingWrapper(PyObjec
 EXPCL_INTERROGATEDB PyObject *Dtool_NewGenerator(PyObject *self, const char *name, iternextfunc func);
 EXPCL_INTERROGATEDB PyObject *Dtool_NewStaticProperty(PyTypeObject *obj, const PyGetSetDef *getset);
 
-#endif
+#endif  // HAVE_PYTHON
+
+#endif  // PY_WRAPPERS_H

+ 1 - 1
makepanda/installer.nsi

@@ -586,6 +586,7 @@ Section "3ds Max plug-ins" SecMaxPlugins
     SetOutPath $INSTDIR\plugins
     File /nonfatal /r "${BUILT}\plugins\*.dle"
     File /nonfatal /r "${BUILT}\plugins\*.dlo"
+    File /nonfatal /r "${BUILT}\plugins\*.ms"
     File "${SOURCE}\doc\INSTALLING-PLUGINS.TXT"
 SectionEnd
 !endif
@@ -601,7 +602,6 @@ Section "Maya plug-ins" SecMayaPlugins
     SetOutPath $INSTDIR\plugins
     File /nonfatal /r "${BUILT}\plugins\*.mll"
     File /nonfatal /r "${BUILT}\plugins\*.mel"
-    File /nonfatal /r "${BUILT}\plugins\*.ms"
     File "${SOURCE}\doc\INSTALLING-PLUGINS.TXT"
 SectionEnd
 !endif

+ 4 - 2
makepanda/makepanda.py

@@ -3729,7 +3729,7 @@ if (not RUNTIME):
   TargetAdd('p3event_composite2.obj', opts=OPTS, input='p3event_composite2.cxx')
 
   OPTS=['DIR:panda/src/event', 'PYTHON']
-  TargetAdd('p3event_asyncTask_ext.obj', opts=OPTS, input='asyncTask_ext.cxx')
+  TargetAdd('p3event_asyncFuture_ext.obj', opts=OPTS, input='asyncFuture_ext.cxx')
   TargetAdd('p3event_pythonTask.obj', opts=OPTS, input='pythonTask.cxx')
   IGATEFILES=GetDirectoryContents('panda/src/event', ["*.h", "*_composite*.cxx"])
   TargetAdd('libp3event.in', opts=OPTS, input=IGATEFILES)
@@ -4323,7 +4323,7 @@ if (not RUNTIME):
   TargetAdd('core.pyd', input='p3pipeline_pythonThread.obj')
   TargetAdd('core.pyd', input='p3putil_ext_composite.obj')
   TargetAdd('core.pyd', input='p3pnmimage_pfmFile_ext.obj')
-  TargetAdd('core.pyd', input='p3event_asyncTask_ext.obj')
+  TargetAdd('core.pyd', input='p3event_asyncFuture_ext.obj')
   TargetAdd('core.pyd', input='p3event_pythonTask.obj')
   TargetAdd('core.pyd', input='p3gobj_ext_composite.obj')
   TargetAdd('core.pyd', input='p3pgraph_ext_composite.obj')
@@ -6467,6 +6467,8 @@ for VER in MAYAVERSIONS:
         continue
     elif GetTarget() == 'darwin' and int(VNUM) >= 2009:
       ARCH_OPTS = ['NOARCH:PPC']
+    elif GetTarget() == 'darwin':
+      ARCH_OPTS = ['NOARCH:X86_64']
     else:
       ARCH_OPTS = []
 

+ 0 - 1
makepanda/makewheel.py

@@ -344,7 +344,6 @@ class WheelFile(object):
                 # Otherwise, just copy it over.
                 temp.write(open(source_path, 'rb').read())
 
-            temp.write(open(source_path, 'rb').read())
             os.fchmod(temp.fileno(), os.fstat(temp.fileno()).st_mode | 0o111)
             temp.close()
 

+ 7 - 17
panda/src/audio/audioLoadRequest.I

@@ -20,8 +20,7 @@ AudioLoadRequest(AudioManager *audio_manager, const string &filename,
                  bool positional) :
   _audio_manager(audio_manager),
   _filename(filename),
-  _positional(positional),
-  _is_ready(false)
+  _positional(positional)
 {
 }
 
@@ -55,31 +54,22 @@ get_positional() const {
  * Returns true if this request has completed, false if it is still pending.
  * When this returns true, you may retrieve the sound loaded by calling
  * get_sound().
+ * Equivalent to `req.done() and not req.cancelled()`.
+ * @see done()
  */
 INLINE bool AudioLoadRequest::
 is_ready() const {
-  return _is_ready;
+  return (FutureState)AtomicAdjust::get(_future_state) == FS_finished;
 }
 
 /**
  * Returns the sound that was loaded asynchronously, if any, or nullptr if
- * there was an error.  It is an error to call this unless is_ready() returns
+ * there was an error.  It is an error to call this unless done() returns
  * true.
  * @deprecated Use result() instead.
  */
 INLINE AudioSound *AudioLoadRequest::
 get_sound() const {
-  nassertr(_is_ready, NULL);
-  return _sound;
-}
-
-/**
- * Returns the sound that was loaded asynchronously, if any, or nullptr if
- * there was an error.  It is an error to call this unless is_ready() returns
- * true.
- */
-INLINE AudioSound *AudioLoadRequest::
-result() const {
-  nassertr(_is_ready, nullptr);
-  return _sound;
+  nassertr_always(done(), nullptr);
+  return (AudioSound *)_result;
 }

+ 1 - 2
panda/src/audio/audioLoadRequest.cxx

@@ -21,8 +21,7 @@ TypeHandle AudioLoadRequest::_type_handle;
  */
 AsyncTask::DoneStatus AudioLoadRequest::
 do_task() {
-  _sound = _audio_manager->get_sound(_filename, _positional);
-  _is_ready = true;
+  set_result(_audio_manager->get_sound(_filename, _positional));
 
   // Don't continue the task; we're done.
   return DS_done;

+ 0 - 5
panda/src/audio/audioLoadRequest.h

@@ -43,8 +43,6 @@ PUBLISHED:
   INLINE bool is_ready() const;
   INLINE AudioSound *get_sound() const;
 
-  INLINE AudioSound *result() const;
-
 protected:
   virtual DoneStatus do_task();
 
@@ -53,9 +51,6 @@ private:
   string _filename;
   bool _positional;
 
-  bool _is_ready;
-  PT(AudioSound) _sound;
-
 public:
   static TypeHandle get_class_type() {
     return _type_handle;

+ 3 - 3
panda/src/pipeline/asyncTaskBase.I → panda/src/cocoadisplay/cocoaGraphicsBuffer.I

@@ -6,7 +6,7 @@
  * license.  You should have received a copy of this license along
  * with this source code in a file named "LICENSE."
  *
- * @file asyncTaskBase.I
- * @author drose
- * @date 2010-02-09
+ * @file cocoaGraphicsBuffer.I
+ * @author rdb
+ * @date 2017-12-19
  */

+ 61 - 0
panda/src/cocoadisplay/cocoaGraphicsBuffer.h

@@ -0,0 +1,61 @@
+/**
+ * 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 cocoaGraphicsBuffer.h
+ * @author rdb
+ * @date 2017-12-19
+ */
+
+#ifndef COCOAGRAPHICSBUFFER_H
+#define COCOAGRAPHICSBUFFER_H
+
+#include "pandabase.h"
+#include "glgsg.h"
+
+/**
+ * This is a light wrapper around GLGraphicsBuffer (ie. FBOs) to interface
+ * with Cocoa contexts, so that it can be used without a host window.
+ */
+class CocoaGraphicsBuffer : public GLGraphicsBuffer {
+public:
+  CocoaGraphicsBuffer(GraphicsEngine *engine, GraphicsPipe *pipe,
+                      const string &name,
+                      const FrameBufferProperties &fb_prop,
+                      const WindowProperties &win_prop,
+                      int flags,
+                      GraphicsStateGuardian *gsg,
+                      GraphicsOutput *host);
+
+  virtual bool begin_frame(FrameMode mode, Thread *current_thread);
+  virtual void end_frame(FrameMode mode, Thread *current_thread);
+
+protected:
+  virtual void close_buffer();
+  virtual bool open_buffer();
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    GLGraphicsBuffer::init_type();
+    register_type(_type_handle, "CocoaGraphicsBuffer",
+                  GLGraphicsBuffer::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+};
+
+#include "cocoaGraphicsBuffer.I"
+
+#endif

+ 165 - 0
panda/src/cocoadisplay/cocoaGraphicsBuffer.mm

@@ -0,0 +1,165 @@
+/**
+ * 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 cocoaGraphicsBuffer.mm
+ * @author rdb
+ * @date 2017-12-19
+ */
+
+#include "cocoaGraphicsBuffer.h"
+#include "cocoaGraphicsStateGuardian.h"
+#include "config_cocoadisplay.h"
+#include "cocoaGraphicsPipe.h"
+
+#import <OpenGL/OpenGL.h>
+
+TypeHandle CocoaGraphicsBuffer::_type_handle;
+
+/**
+ *
+ */
+CocoaGraphicsBuffer::
+CocoaGraphicsBuffer(GraphicsEngine *engine, GraphicsPipe *pipe,
+                    const string &name,
+                    const FrameBufferProperties &fb_prop,
+                    const WindowProperties &win_prop,
+                    int flags,
+                    GraphicsStateGuardian *gsg,
+                    GraphicsOutput *host) : // Ignore the host.
+  GLGraphicsBuffer(engine, pipe, name, fb_prop, win_prop, flags, gsg, nullptr)
+{
+}
+
+/**
+ * This function will be called within the draw thread before beginning
+ * rendering for a given frame.  It should do whatever setup is required, and
+ * return true if the frame should be rendered, or false if it should be
+ * skipped.
+ */
+bool CocoaGraphicsBuffer::
+begin_frame(FrameMode mode, Thread *current_thread) {
+  if (_gsg == nullptr) {
+    return false;
+  }
+
+  CocoaGraphicsStateGuardian *cocoagsg;
+  DCAST_INTO_R(cocoagsg, _gsg, false);
+  nassertr(cocoagsg->_context != nil, false);
+
+  // Lock the context and make it current.
+  {
+    PStatTimer timer(_make_current_pcollector, current_thread);
+    cocoagsg->lock_context();
+    [cocoagsg->_context makeCurrentContext];
+  }
+
+  return GLGraphicsBuffer::begin_frame(mode, current_thread);
+}
+
+/**
+ * This function will be called within the draw thread after rendering is
+ * completed for a given frame.  It should do whatever finalization is
+ * required.
+ */
+void CocoaGraphicsBuffer::
+end_frame(FrameMode mode, Thread *current_thread) {
+  nassertv(_gsg != nullptr);
+
+  GLGraphicsBuffer::end_frame(mode, current_thread);
+
+  // Release the context.
+  CocoaGraphicsStateGuardian *cocoagsg;
+  DCAST_INTO_V(cocoagsg, _gsg);
+  cocoagsg->unlock_context();
+}
+
+/**
+ * Opens the buffer right now.  Called from the window thread.  Returns true
+ * if the buffer is successfully opened, or false if there was a problem.
+ */
+bool CocoaGraphicsBuffer::
+open_buffer() {
+  CocoaGraphicsPipe *cocoa_pipe;
+  DCAST_INTO_R(cocoa_pipe, _pipe, false);
+
+  // GSG CreationInitialization
+  CocoaGraphicsStateGuardian *cocoagsg;
+  if (_gsg == nullptr) {
+    // There is no old gsg.  Create a new one.
+    cocoagsg = new CocoaGraphicsStateGuardian(_engine, _pipe, nullptr);
+    cocoagsg->choose_pixel_format(_fb_properties, cocoa_pipe->get_display_id(), false);
+    _gsg = cocoagsg;
+  } else {
+    // If the old gsg has the wrong pixel format, create a new one that shares
+    // with the old gsg.
+    DCAST_INTO_R(cocoagsg, _gsg, false);
+    if (!cocoagsg->get_fb_properties().subsumes(_fb_properties)) {
+      cocoagsg = new CocoaGraphicsStateGuardian(_engine, _pipe, cocoagsg);
+      cocoagsg->choose_pixel_format(_fb_properties, cocoa_pipe->get_display_id(), false);
+      _gsg = cocoagsg;
+    }
+  }
+
+  FrameBufferProperties desired_props(_fb_properties);
+
+  // Lock the context, so we can safely operate on it.
+  cocoagsg->lock_context();
+
+  // Make the context current and initialize what we need.
+  [cocoagsg->_context makeCurrentContext];
+  [cocoagsg->_context update];
+  cocoagsg->reset_if_new();
+
+  // These properties are determined by choose_pixel_format.
+  _fb_properties.set_force_hardware(cocoagsg->_fbprops.get_force_hardware());
+  _fb_properties.set_force_software(cocoagsg->_fbprops.get_force_software());
+
+  bool success = GLGraphicsBuffer::open_buffer();
+  if (success) {
+    rebuild_bitplanes();
+    if (_needs_rebuild) {
+      // If it still needs rebuild, then something must have gone wrong.
+      success = false;
+    }
+  }
+
+  if (success && !_fb_properties.verify_hardware_software
+      (desired_props, cocoagsg->get_gl_renderer())) {
+    GLGraphicsBuffer::close_buffer();
+    success = false;
+  }
+
+  // Release the context.
+  cocoagsg->unlock_context();
+
+  if (!success) {
+    return false;
+  }
+
+  return true;
+}
+
+/**
+ * Closes the buffer right now.  Called from the window thread.
+ */
+void CocoaGraphicsBuffer::
+close_buffer() {
+  if (_gsg != nullptr) {
+    CocoaGraphicsStateGuardian *cocoagsg;
+    cocoagsg = DCAST(CocoaGraphicsStateGuardian, _gsg);
+
+    if (cocoagsg != nullptr && cocoagsg->_context != nil) {
+      cocoagsg->lock_context();
+      GLGraphicsBuffer::close_buffer();
+      cocoagsg->unlock_context();
+    }
+    _gsg.clear();
+  } else {
+    GLGraphicsBuffer::close_buffer();
+  }
+}

+ 0 - 8
panda/src/cocoadisplay/cocoaGraphicsPipe.I

@@ -18,11 +18,3 @@ INLINE CGDirectDisplayID CocoaGraphicsPipe::
 get_display_id() const {
   return _display;
 }
-
-/**
- * Returns the Cocoa NSScreen pointer associated with this graphics pipe.
- */
-INLINE NSScreen *CocoaGraphicsPipe::
-get_nsscreen() const {
-  return _screen;
-}

+ 2 - 8
panda/src/cocoadisplay/cocoaGraphicsPipe.h

@@ -35,13 +35,10 @@ class FrameBufferProperties;
  */
 class CocoaGraphicsPipe : public GraphicsPipe {
 public:
-  CocoaGraphicsPipe();
-  CocoaGraphicsPipe(CGDirectDisplayID display);
-  CocoaGraphicsPipe(NSScreen *screen);
+  CocoaGraphicsPipe(CGDirectDisplayID display = CGMainDisplayID());
   virtual ~CocoaGraphicsPipe();
 
   INLINE CGDirectDisplayID get_display_id() const;
-  INLINE NSScreen *get_nsscreen() const;
 
   virtual string get_interface_name() const;
   static PT(GraphicsPipe) pipe_constructor();
@@ -64,11 +61,8 @@ protected:
 private:
   void load_display_information();
 
-  // _display and _screen refer to the same thing, NSScreen being the tiny
-  // Cocoa wrapper around the Quartz display ID.  NSScreen isn't generally
-  // useful, but we need it when creating the window.
+  // This is the Quartz display identifier.
   CGDirectDisplayID _display;
-  NSScreen *_screen;
 
   friend class CocoaGraphicsWindow;
 

+ 23 - 112
panda/src/cocoadisplay/cocoaGraphicsPipe.mm

@@ -12,7 +12,7 @@
  */
 
 #include "cocoaGraphicsPipe.h"
-// #include "cocoaGraphicsBuffer.h"
+#include "cocoaGraphicsBuffer.h"
 #include "cocoaGraphicsWindow.h"
 #include "cocoaGraphicsStateGuardian.h"
 #include "cocoaPandaApp.h"
@@ -30,104 +30,32 @@
 
 TypeHandle CocoaGraphicsPipe::_type_handle;
 
-static void init_app() {
-  if (NSApp == nil) {
-    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
-    [CocoaPandaApp sharedApplication];
-
-#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1060
-    [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
-#endif
-    [NSApp finishLaunching];
-    [NSApp activateIgnoringOtherApps:YES];
-
-    // Put Cocoa into thread-safe mode by spawning a thread which immediately
-    // exits.
-    NSThread* thread = [[NSThread alloc] init];
-    [thread start];
-    [thread autorelease];
-  }
-}
-
 /**
- * Uses the main screen (the one the user is most likely to be working in at
- * the moment).
+ * Takes a CoreGraphics display ID, which defaults to the main display.
  */
 CocoaGraphicsPipe::
-CocoaGraphicsPipe() {
+CocoaGraphicsPipe(CGDirectDisplayID display) : _display(display) {
   _supported_types = OT_window | OT_buffer | OT_texture_buffer;
   _is_valid = true;
 
-  init_app();
-
-  _screen = [NSScreen mainScreen];
-  NSNumber *num = [[_screen deviceDescription] objectForKey: @"NSScreenNumber"];
-  _display = (CGDirectDisplayID) [num longValue];
-
-  _display_width = CGDisplayPixelsWide(_display);
-  _display_height = CGDisplayPixelsHigh(_display);
-  load_display_information();
-
-  cocoadisplay_cat.debug()
-    << "Creating CocoaGraphicsPipe for main screen "
-    << _screen << " with display ID " << _display << "\n";
-}
-
-/**
- * Takes a CoreGraphics display ID.
- */
-CocoaGraphicsPipe::
-CocoaGraphicsPipe(CGDirectDisplayID display) {
-  _supported_types = OT_window | OT_buffer | OT_texture_buffer;
-  _is_valid = true;
-  _display = display;
-
-  init_app();
-
-  // Iterate over the screens to find the one with our display ID.
-  NSEnumerator *e = [[NSScreen screens] objectEnumerator];
-  while (NSScreen *screen = (NSScreen *) [e nextObject]) {
-    NSNumber *num = [[screen deviceDescription] objectForKey: @"NSScreenNumber"];
-    if (display == (CGDirectDisplayID) [num longValue]) {
-      _screen = screen;
-      break;
-    }
-  }
-
-  _display_width = CGDisplayPixelsWide(_display);
-  _display_height = CGDisplayPixelsHigh(_display);
-  load_display_information();
+  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
 
-  cocoadisplay_cat.debug()
-    << "Creating CocoaGraphicsPipe for screen "
-    << _screen << " with display ID " << _display << "\n";
-}
+  // Put Cocoa into thread-safe mode by spawning a thread which immediately
+  // exits.
+  NSThread* thread = [[NSThread alloc] init];
+  [thread start];
+  [thread autorelease];
 
-/**
- * Takes an NSScreen pointer.
- */
-CocoaGraphicsPipe::
-CocoaGraphicsPipe(NSScreen *screen) {
-  _supported_types = OT_window | OT_buffer | OT_texture_buffer;
-  _is_valid = true;
-
-  init_app();
-
-  if (screen == nil) {
-    _screen = [NSScreen mainScreen];
-  } else {
-    _screen = screen;
-  }
-  NSNumber *num = [[_screen deviceDescription] objectForKey: @"NSScreenNumber"];
-  _display = (CGDirectDisplayID) [num longValue];
+  // We used to also obtain the corresponding NSScreen here, but this causes
+  // the application icon to start bouncing, which may be undesirable for
+  // apps that will never open a window.
 
   _display_width = CGDisplayPixelsWide(_display);
   _display_height = CGDisplayPixelsHigh(_display);
   load_display_information();
 
   cocoadisplay_cat.debug()
-    << "Creating CocoaGraphicsPipe for screen "
-    << _screen << " with display ID " << _display << "\n";
+    << "Creating CocoaGraphicsPipe for display ID " << _display << "\n";
 }
 
 /**
@@ -308,10 +236,12 @@ make_output(const string &name,
                                    flags, gsg, host);
   }
 
-  // Second thing to try: a GLGraphicsBuffer
+  // Second thing to try: a GLGraphicsBuffer.  This requires a context, so if
+  // we don't have a host window, we instead create a CocoaGraphicsBuffer,
+  // which wraps around GLGraphicsBuffer and manages a context.
 
   if (retry == 1) {
-    if (!gl_support_fbo || host == NULL ||
+    if (!gl_support_fbo ||
         (flags & (BF_require_parasite | BF_require_window)) != 0) {
       return NULL;
     }
@@ -334,33 +264,14 @@ make_output(const string &name,
         precertify = true;
       }
     }
-    return new GLGraphicsBuffer(engine, this, name, fb_prop, win_prop,
-                                flags, gsg, host);
-  }
-/*
-  // Third thing to try: a CocoaGraphicsBuffer
-  if (retry == 2) {
-    if (((flags&BF_require_parasite)!=0)||
-        ((flags&BF_require_window)!=0)||
-        ((flags&BF_resizeable)!=0)||
-        ((flags&BF_size_track_host)!=0)||
-        ((flags&BF_can_bind_layered)!=0)) {
-      return NULL;
-    }
-
-    if (!support_rtt) {
-      if (((flags&BF_rtt_cumulative)!=0)||
-          ((flags&BF_can_bind_every)!=0)) {
-        // If we require Render-to-Texture, but can't be sure we support it,
-        // bail.
-        return NULL;
-      }
+    if (host != NULL) {
+      return new GLGraphicsBuffer(engine, this, name, fb_prop, win_prop,
+                                  flags, gsg, host);
+    } else {
+      return new CocoaGraphicsBuffer(engine, this, name, fb_prop, win_prop,
+                                     flags, gsg, host);
     }
-
-    return new CocoaGraphicsBuffer(engine, this, name, fb_prop, win_prop,
-                                 flags, gsg, host);
   }
-*/
 
   // Nothing else left to try.
   return NULL;

+ 18 - 0
panda/src/cocoadisplay/cocoaGraphicsStateGuardian.I

@@ -19,3 +19,21 @@ INLINE const FrameBufferProperties &CocoaGraphicsStateGuardian::
 get_fb_properties() const {
   return _fbprops;
 }
+
+/**
+ * Locks the context.
+ */
+INLINE void CocoaGraphicsStateGuardian::
+lock_context() {
+  nassertv(_context != nil);
+  CGLLockContext((CGLContextObj) [_context CGLContextObj]);
+}
+
+/**
+ * Unlocks the context.
+ */
+INLINE void CocoaGraphicsStateGuardian::
+unlock_context() {
+  nassertv(_context != nil);
+  CGLUnlockContext((CGLContextObj) [_context CGLContextObj]);
+}

+ 4 - 0
panda/src/cocoadisplay/cocoaGraphicsStateGuardian.h

@@ -19,6 +19,7 @@
 #include "glgsg.h"
 
 #import <AppKit/NSOpenGL.h>
+#import <OpenGL/OpenGL.h>
 
 /**
  * A tiny specialization on GLGraphicsStateGuardian to add some Cocoa-specific
@@ -38,6 +39,9 @@ public:
 
   virtual ~CocoaGraphicsStateGuardian();
 
+  INLINE void lock_context();
+  INLINE void unlock_context();
+
   NSOpenGLContext *_share_context;
   NSOpenGLContext *_context;
   FrameBufferProperties _fbprops;

+ 16 - 1
panda/src/cocoadisplay/cocoaGraphicsStateGuardian.mm

@@ -24,6 +24,10 @@
 #define kCGLRendererIDMatchingMask   0x00FE7F00
 #endif
 
+#ifndef NSAppKitVersionNumber10_7
+#define NSAppKitVersionNumber10_7 1138
+#endif
+
 TypeHandle CocoaGraphicsStateGuardian::_type_handle;
 
 /**
@@ -207,7 +211,8 @@ choose_pixel_format(const FrameBufferProperties &properties,
     attribs.push_back(NSOpenGLPFAAccelerated);
   }
 
-  attribs.push_back(NSOpenGLPFAWindow);
+  // This seems to cause getting a 3.2+ context to fail.
+  //attribs.push_back(NSOpenGLPFAWindow);
 
   if (need_pbuffer) {
     attribs.push_back(NSOpenGLPFAPixelBuffer);
@@ -217,6 +222,16 @@ choose_pixel_format(const FrameBufferProperties &properties,
   attribs.push_back(NSOpenGLPFAScreenMask);
   attribs.push_back(CGDisplayIDToOpenGLDisplayMask(display));
 
+  // Set OpenGL version if a minimum was requested.
+  if (gl_version.size() >= 1 && NSAppKitVersionNumber >= NSAppKitVersionNumber10_7) {
+    //NB. There is also NSOpenGLProfileVersion4_1Core, but this seems to cause
+    // a software implementation to be selected on my mac mini running 10.11.
+    if (gl_version[0] >= 4 || (gl_version.size() >= 2 && gl_version[0] == 3 && gl_version[1] >= 2)) {
+      attribs.push_back((NSOpenGLPixelFormatAttribute)99); // NSOpenGLPFAOpenGLProfile
+      attribs.push_back((NSOpenGLPixelFormatAttribute)0x3200); // NSOpenGLProfileVersion3_2Core
+    }
+  }
+
   // End of the array
   attribs.push_back((NSOpenGLPixelFormatAttribute)0);
 

+ 32 - 12
panda/src/cocoadisplay/cocoaGraphicsWindow.mm

@@ -65,6 +65,16 @@ CocoaGraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe,
   _fullscreen_mode = NULL;
   _windowed_mode = NULL;
 
+  // Now that we know for sure we want a window, we can create the Cocoa app.
+  // This will cause the application icon to appear and start bouncing.
+  if (NSApp == nil) {
+#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1060
+    [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
+#endif
+    [NSApp finishLaunching];
+    [NSApp activateIgnoringOtherApps:YES];
+  }
+
   GraphicsWindowInputDevice device =
     GraphicsWindowInputDevice::pointer_and_keyboard(this, "keyboard_mouse");
   add_input_device(device);
@@ -144,7 +154,7 @@ begin_frame(FrameMode mode, Thread *current_thread) {
   nassertr(_view != nil, false);
 
   // Place a lock on the context.
-  CGLLockContext((CGLContextObj) [cocoagsg->_context CGLContextObj]);
+  cocoagsg->lock_context();
 
   // Set the drawable.
   if (_properties.get_fullscreen()) {
@@ -210,7 +220,7 @@ end_frame(FrameMode mode, Thread *current_thread) {
   CocoaGraphicsStateGuardian *cocoagsg;
   DCAST_INTO_V(cocoagsg, _gsg);
 
-  CGLUnlockContext((CGLContextObj) [cocoagsg->_context CGLContextObj]);
+  cocoagsg->unlock_context();
 
   if (mode == FM_render) {
     // end_render_texture();
@@ -239,7 +249,7 @@ end_flip() {
     CocoaGraphicsStateGuardian *cocoagsg;
     DCAST_INTO_V(cocoagsg, _gsg);
 
-    CGLLockContext((CGLContextObj) [cocoagsg->_context CGLContextObj]);
+    cocoagsg->lock_context();
 
     // Swap the front and back buffer.
     [cocoagsg->_context flushBuffer];
@@ -247,7 +257,7 @@ end_flip() {
     // Flush the window
     [[_view window] flushWindow];
 
-    CGLUnlockContext((CGLContextObj) [cocoagsg->_context CGLContextObj]);
+    cocoagsg->unlock_context();
   }
   GraphicsWindow::end_flip();
 }
@@ -399,6 +409,16 @@ open_window() {
     }
   }
 
+  // Iterate over the screens to find the one with our display ID.
+  NSScreen *screen;
+  NSEnumerator *e = [[NSScreen screens] objectEnumerator];
+  while (screen = (NSScreen *) [e nextObject]) {
+    NSNumber *num = [[screen deviceDescription] objectForKey: @"NSScreenNumber"];
+    if (cocoa_pipe->_display == (CGDirectDisplayID) [num longValue]) {
+      break;
+    }
+  }
+
   // Center the window if coordinates were set to -1 or -2 TODO: perhaps in
   // future, in the case of -1, it should use the origin used in a previous
   // run of Panda
@@ -406,7 +426,7 @@ open_window() {
   if (parent_nsview != NULL) {
     container = [parent_nsview bounds];
   } else {
-    container = [cocoa_pipe->_screen frame];
+    container = [screen frame];
     container.origin = NSMakePoint(0, 0);
   }
   int x = _properties.get_x_origin();
@@ -453,7 +473,7 @@ open_window() {
     _window = [[CocoaPandaWindow alloc]
                initWithContentRect: rect
                styleMask:windowStyle
-               screen:cocoa_pipe->_screen
+               screen:screen
                window:this];
 
     if (_window == nil) {
@@ -464,7 +484,7 @@ open_window() {
   }
 
   // Lock the context, so we can safely operate on it.
-  CGLLockContext((CGLContextObj) [cocoagsg->_context CGLContextObj]);
+  cocoagsg->lock_context();
 
   // Create the NSView to render to.
   NSRect rect = NSMakeRect(0, 0, _properties.get_x_size(), _properties.get_y_size());
@@ -588,7 +608,7 @@ open_window() {
   cocoagsg->reset_if_new();
 
   // Release the context.
-  CGLUnlockContext((CGLContextObj) [cocoagsg->_context CGLContextObj]);
+  cocoagsg->unlock_context();
 
   if (!cocoagsg->is_valid()) {
     close_window();
@@ -637,9 +657,9 @@ close_window() {
     cocoagsg = DCAST(CocoaGraphicsStateGuardian, _gsg);
 
     if (cocoagsg != NULL && cocoagsg->_context != nil) {
-      CGLLockContext((CGLContextObj) [cocoagsg->_context CGLContextObj]);
+      cocoagsg->lock_context();
       [cocoagsg->_context clearDrawable];
-      CGLUnlockContext((CGLContextObj) [cocoagsg->_context CGLContextObj]);
+      cocoagsg->unlock_context();
     }
     _gsg.clear();
   }
@@ -1429,9 +1449,9 @@ handle_close_event() {
     cocoagsg = DCAST(CocoaGraphicsStateGuardian, _gsg);
 
     if (cocoagsg != NULL && cocoagsg->_context != nil) {
-      CGLLockContext((CGLContextObj) [cocoagsg->_context CGLContextObj]);
+      cocoagsg->lock_context();
       [cocoagsg->_context clearDrawable];
-      CGLUnlockContext((CGLContextObj) [cocoagsg->_context CGLContextObj]);
+      cocoagsg->unlock_context();
     }
     _gsg.clear();
   }

+ 2 - 0
panda/src/cocoadisplay/config_cocoadisplay.mm

@@ -12,6 +12,7 @@
  */
 
 #include "config_cocoadisplay.h"
+#include "cocoaGraphicsBuffer.h"
 #include "cocoaGraphicsPipe.h"
 #include "cocoaGraphicsStateGuardian.h"
 #include "cocoaGraphicsWindow.h"
@@ -40,6 +41,7 @@ init_libcocoadisplay() {
   }
   initialized = true;
 
+  CocoaGraphicsBuffer::init_type();
   CocoaGraphicsPipe::init_type();
   CocoaGraphicsStateGuardian::init_type();
   CocoaGraphicsWindow::init_type();

+ 1 - 0
panda/src/cocoadisplay/p3cocoadisplay_composite1.mm

@@ -1,4 +1,5 @@
 #include "config_cocoadisplay.mm"
+#include "cocoaGraphicsBuffer.mm"
 #include "cocoaGraphicsPipe.mm"
 #include "cocoaGraphicsStateGuardian.mm"
 #include "cocoaGraphicsWindow.mm"

+ 10 - 4
panda/src/display/graphicsOutput.I

@@ -489,10 +489,16 @@ get_child_sort() const {
 /**
  * When the GraphicsOutput is in triggered copy mode, this function triggers
  * the copy (at the end of the next frame).
- */
-INLINE void GraphicsOutput::
-trigger_copy()  {
-  _trigger_copy = true;
+ * @returns a future that can be awaited.
+ */
+INLINE AsyncFuture *GraphicsOutput::
+trigger_copy() {
+  AsyncFuture *future = _trigger_copy;
+  if (future == nullptr) {
+    future = new AsyncFuture;
+    _trigger_copy = future;
+  }
+  return future;
 }
 
 /**

+ 16 - 5
panda/src/display/graphicsOutput.cxx

@@ -115,7 +115,6 @@ GraphicsOutput(GraphicsEngine *engine, GraphicsPipe *pipe,
   _sbs_left_dimensions.set(0.0f, 1.0f, 0.0f, 1.0f);
   _sbs_right_dimensions.set(0.0f, 1.0f, 0.0f, 1.0f);
   _delete_flag = false;
-  _trigger_copy = false;
 
   if (_fb_properties.is_single_buffered()) {
     _draw_buffer_type = RenderBuffer::T_front;
@@ -276,12 +275,21 @@ add_render_texture(Texture *tex, RenderTextureMode mode,
 
   // Choose a default bitplane.
   if (plane == RTP_COUNT) {
-    if (tex->get_format() == Texture::F_depth_stencil) {
+    switch (tex->get_format()) {
+    case Texture::F_depth_stencil:
       plane = RTP_depth_stencil;
-    } else if (tex->get_format() == Texture::F_depth_component) {
+      break;
+
+    case Texture::F_depth_component:
+    case Texture::F_depth_component16:
+    case Texture::F_depth_component24:
+    case Texture::F_depth_component32:
       plane = RTP_depth;
-    } else {
+      break;
+
+    default:
       plane = RTP_color;
+      break;
     }
   }
 
@@ -1435,7 +1443,10 @@ copy_to_textures() {
       }
     }
   }
-  _trigger_copy = false;
+  if (_trigger_copy != nullptr) {
+    _trigger_copy->set_result(nullptr);
+    _trigger_copy = nullptr;
+  }
 
   return okflag;
 }

+ 3 - 2
panda/src/display/graphicsOutput.h

@@ -40,6 +40,7 @@
 #include "cycleDataWriter.h"
 #include "pipelineCycler.h"
 #include "updateSeq.h"
+#include "asyncFuture.h"
 
 class PNMImage;
 class GraphicsEngine;
@@ -197,7 +198,7 @@ PUBLISHED:
   INLINE int get_child_sort() const;
   MAKE_PROPERTY(child_sort, get_child_sort, set_child_sort);
 
-  INLINE void trigger_copy();
+  INLINE AsyncFuture *trigger_copy();
 
   INLINE DisplayRegion *make_display_region();
   INLINE DisplayRegion *make_display_region(PN_stdfloat l, PN_stdfloat r, PN_stdfloat b, PN_stdfloat t);
@@ -330,7 +331,7 @@ protected:
   int _target_tex_view;
   DisplayRegion *_prev_page_dr;
   PT(GeomNode) _texture_card;
-  bool _trigger_copy;
+  PT(AsyncFuture) _trigger_copy;
 
   class RenderTexture {
   public:

+ 10 - 9
panda/src/display/graphicsStateGuardian.cxx

@@ -3184,15 +3184,15 @@ get_untextured_state() {
  * Should be called when a texture is encountered that needs to have its RAM
  * image reloaded, and get_incomplete_render() is true.  This will fire off a
  * thread on the current Loader object that will request the texture to load
- * its image.  The image will be available at some point in the future (no
- * event will be generated).
+ * its image.  The image will be available at some point in the future.
+ * @returns a future object that can be used to check its status.
  */
-void GraphicsStateGuardian::
+AsyncFuture *GraphicsStateGuardian::
 async_reload_texture(TextureContext *tc) {
-  nassertv(_loader != (Loader *)NULL);
+  nassertr(_loader != nullptr, nullptr);
 
   int priority = 0;
-  if (_current_display_region != (DisplayRegion *)NULL) {
+  if (_current_display_region != nullptr) {
     priority = _current_display_region->get_texture_reload_priority();
   }
 
@@ -3201,15 +3201,15 @@ async_reload_texture(TextureContext *tc) {
 
   // See if we are already loading this task.
   AsyncTaskCollection orig_tasks = task_mgr->find_tasks(task_name);
-  int num_tasks = orig_tasks.get_num_tasks();
-  for (int ti = 0; ti < num_tasks; ++ti) {
+  size_t num_tasks = orig_tasks.get_num_tasks();
+  for (size_t ti = 0; ti < num_tasks; ++ti) {
     AsyncTask *task = orig_tasks.get_task(ti);
     if (task->is_exact_type(TextureReloadRequest::get_class_type()) &&
-        DCAST(TextureReloadRequest, task)->get_texture() == tc->get_texture()) {
+        ((TextureReloadRequest *)task)->get_texture() == tc->get_texture()) {
       // This texture is already queued to be reloaded.  Don't queue it again,
       // just make sure the priority is updated, and return.
       task->set_priority(max(task->get_priority(), priority));
-      return;
+      return (AsyncFuture *)task;
     }
   }
 
@@ -3220,6 +3220,7 @@ async_reload_texture(TextureContext *tc) {
                              _supports_compressed_texture);
   request->set_priority(priority);
   _loader->load_async(request);
+  return (AsyncFuture *)request.p();
 }
 
 /**

+ 1 - 1
panda/src/display/graphicsStateGuardian.h

@@ -460,7 +460,7 @@ protected:
   static CPT(RenderState) get_unclipped_state();
   static CPT(RenderState) get_untextured_state();
 
-  void async_reload_texture(TextureContext *tc);
+  AsyncFuture *async_reload_texture(TextureContext *tc);
 
 protected:
   PT(SceneSetup) _scene_null;

+ 200 - 0
panda/src/event/asyncFuture.I

@@ -0,0 +1,200 @@
+/**
+ * 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 asyncFuture.I
+ * @author rdb
+ * @date 2017-11-28
+ */
+
+/**
+ * Initializes the future in the pending state.
+ */
+INLINE AsyncFuture::
+AsyncFuture() :
+  _manager(nullptr),
+  _future_state(FS_pending),
+  _result(nullptr) {
+}
+
+/**
+ * Returns true if the future is done or has been cancelled.  It is always
+ * safe to call this.
+ */
+INLINE bool AsyncFuture::
+done() const {
+  return (FutureState)AtomicAdjust::get(_future_state) >= FS_finished;
+}
+
+/**
+ * Returns true if the future was cancelled.  It is always safe to call this.
+ */
+INLINE bool AsyncFuture::
+cancelled() const {
+  return (FutureState)AtomicAdjust::get(_future_state) == FS_cancelled;
+}
+
+/**
+ * Sets the event name that will be triggered when the future finishes.  Will
+ * not be triggered if the future is cancelled, but it will be triggered for
+ * a coroutine task that exits with an exception.
+ */
+INLINE void AsyncFuture::
+set_done_event(const string &done_event) {
+  nassertv(!done());
+  _done_event = done_event;
+}
+
+/**
+ * Returns the event name that will be triggered when the future finishes.
+ * See set_done_event().
+ */
+INLINE const string &AsyncFuture::
+get_done_event() const {
+  return _done_event;
+}
+
+/**
+ * Returns this future's result.  Can only be called if done() returns true.
+ */
+INLINE TypedObject *AsyncFuture::
+get_result() const {
+  // This is thread safe, since _result may no longer be modified after the
+  // state is changed to "done".
+  nassertr_always(done(), nullptr);
+  return _result;
+}
+
+/**
+ * Returns this future's result as a pair of TypedObject, ReferenceCount
+ * pointers.  Can only be called if done() returns true.
+ */
+INLINE void AsyncFuture::
+get_result(TypedObject *&ptr, ReferenceCount *&ref_ptr) const {
+  // This is thread safe, since _result may no longer be modified after the
+  // state is changed to "done".
+  nassertd(done()) {
+    ptr = nullptr;
+    ref_ptr = nullptr;
+  }
+  ptr = _result;
+  ref_ptr = _result_ref.p();
+}
+
+/**
+ * Sets this future's result.  Can only be called if done() returns false.
+ */
+INLINE void AsyncFuture::
+set_result(nullptr_t) {
+  set_result(nullptr, nullptr);
+}
+
+INLINE void AsyncFuture::
+set_result(TypedObject *result) {
+  set_result(result, nullptr);
+}
+
+INLINE void AsyncFuture::
+set_result(TypedReferenceCount *result) {
+  set_result(result, result);
+}
+
+INLINE void AsyncFuture::
+set_result(const EventParameter &result) {
+  set_result(result.get_ptr(), result.get_ptr());
+}
+
+/**
+ * Creates a new future that returns `done()` when all of the contained
+ * futures are done.
+ *
+ * Calling `cancel()` on the returned future will result in all contained
+ * futures that have not yet finished to be cancelled.
+ */
+INLINE AsyncFuture *AsyncFuture::
+gather(Futures futures) {
+  if (futures.empty()) {
+    AsyncFuture *fut = new AsyncFuture;
+    fut->_future_state = (AtomicAdjust::Integer)FS_finished;
+    return fut;
+  } else if (futures.size() == 1) {
+    return futures[0].p();
+  } else {
+    return (AsyncFuture *)new AsyncGatheringFuture(move(futures));
+  }
+}
+
+/**
+ * Tries to atomically lock the future, assuming it is pending.  Returns false
+ * if it is not in the pending state, implying it's either done or about to be
+ * cancelled.
+ */
+INLINE bool AsyncFuture::
+try_lock_pending() {
+  return set_future_state(FS_locked_pending);
+}
+
+/**
+ * Should be called after try_lock_pending() returns true.
+ */
+INLINE void AsyncFuture::
+unlock(FutureState new_state) {
+  nassertv(new_state != FS_locked_pending);
+  FutureState orig_state = (FutureState)AtomicAdjust::set(_future_state, (AtomicAdjust::Integer)new_state);
+  nassertv(orig_state == FS_locked_pending);
+}
+
+/**
+ * Atomically changes the future state from pending to another state.  Returns
+ * true if successful, false if the future was already done.
+ * Note that once a future is in a "done" state (ie. cancelled or finished) it
+ * can never change state again.
+ */
+INLINE bool AsyncFuture::
+set_future_state(FutureState state) {
+  FutureState orig_state = (FutureState)
+    AtomicAdjust::compare_and_exchange(
+      _future_state,
+      (AtomicAdjust::Integer)FS_pending,
+      (AtomicAdjust::Integer)state);
+
+  while (orig_state == FS_locked_pending) {
+    Thread::force_yield();
+    orig_state = (FutureState)AtomicAdjust::compare_and_exchange(
+      _future_state,
+      (AtomicAdjust::Integer)FS_pending,
+      (AtomicAdjust::Integer)state);
+  }
+
+  return orig_state == FS_pending;
+}
+
+/**
+ * Returns the number of futures that were passed to the constructor.
+ */
+INLINE size_t AsyncGatheringFuture::
+get_num_futures() const {
+  return _futures.size();
+}
+
+/**
+ * Returns the nth future that was passed into the constructor.
+ */
+INLINE AsyncFuture *AsyncGatheringFuture::
+get_future(size_t i) const {
+  nassertr(i < _futures.size(), nullptr);
+  return _futures[i].p();
+}
+
+/**
+ * Returns the result of the nth future that was passed into the constructor.
+ */
+INLINE TypedObject *AsyncGatheringFuture::
+get_result(size_t i) const {
+  nassertr(i < _futures.size(), nullptr);
+  return _futures[i]->get_result();
+}

+ 376 - 0
panda/src/event/asyncFuture.cxx

@@ -0,0 +1,376 @@
+/**
+ * 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 asyncFuture.cxx
+ * @author rdb
+ * @date 2017-11-28
+ */
+
+#include "asyncFuture.h"
+#include "asyncTask.h"
+#include "asyncTaskManager.h"
+#include "conditionVarFull.h"
+#include "config_event.h"
+#include "pStatTimer.h"
+#include "throw_event.h"
+
+TypeHandle AsyncFuture::_type_handle;
+TypeHandle AsyncGatheringFuture::_type_handle;
+
+/**
+ * Destroys the future.  Assumes notify_done() has already been called.
+ */
+AsyncFuture::
+~AsyncFuture() {
+  // If this triggers, the future destroyed before it was cancelled, which is
+  // not valid.  Unless we should simply call cancel() here?
+  nassertv(_waiting.empty());
+}
+
+/**
+ * Cancels the future.  Returns true if it was cancelled, or false if the
+ * future was already done.  Either way, done() will return true after this
+ * call returns.
+ *
+ * In the case of a task, this is equivalent to remove().
+ */
+bool AsyncFuture::
+cancel() {
+  if (set_future_state(FS_cancelled)) {
+    // The compare-swap operation succeeded, so schedule the callbacks.
+    notify_done(false);
+    return true;
+  } else {
+    // It's already done.
+    return false;
+  }
+}
+
+/**
+ *
+ */
+void AsyncFuture::
+output(ostream &out) const {
+  out << get_type();
+  FutureState state = (FutureState)AtomicAdjust::get(_future_state);
+  switch (state) {
+  case FS_pending:
+  case FS_locked_pending:
+    out << " (pending)";
+    break;
+  case FS_finished:
+    out << " (finished)";
+    break;
+  case FS_cancelled:
+    out << " (cancelled)";
+    break;
+  default:
+    out << " (**INVALID**)";
+    break;
+  }
+}
+
+/**
+ * Waits until the future is done.
+ */
+void AsyncFuture::
+wait() {
+  if (done()) {
+    return;
+  }
+
+  PStatTimer timer(AsyncTaskChain::_wait_pcollector);
+  if (task_cat.is_debug()) {
+    task_cat.debug()
+      << "Waiting for future " << *this << "\n";
+  }
+
+  // Continue to yield while the future isn't done.  It may be more efficient
+  // to use a condition variable, but let's not add the extra complexity
+  // unless we're sure that we need it.
+  do {
+    Thread::force_yield();
+  } while (!done());
+}
+
+/**
+ * Waits until the future is done, or until the timeout is reached.
+ */
+void AsyncFuture::
+wait(double timeout) {
+  if (done()) {
+    return;
+  }
+
+  PStatTimer timer(AsyncTaskChain::_wait_pcollector);
+  if (task_cat.is_debug()) {
+    task_cat.debug()
+      << "Waiting up to " << timeout << " seconds for future " << *this << "\n";
+  }
+
+  // Continue to yield while the future isn't done.  It may be more efficient
+  // to use a condition variable, but let's not add the extra complexity
+  // unless we're sure that we need it.
+  ClockObject *clock = ClockObject::get_global_clock();
+  double end = clock->get_real_time() + timeout;
+  do {
+    Thread::force_yield();
+  } while (!done() && clock->get_real_time() < end);
+}
+
+/**
+ * Schedules the done callbacks.  Called after the future has just entered the
+ * 'done' state.
+ * @param clean_exit true if finished successfully, false if cancelled.
+ */
+void AsyncFuture::
+notify_done(bool clean_exit) {
+  nassertv(done());
+
+  // This will only be called by the thread that managed to set the
+  // _future_state away from the "pending" state, so this is thread safe.
+
+  Futures::iterator it;
+  for (it = _waiting.begin(); it != _waiting.end(); ++it) {
+    AsyncFuture *fut = *it;
+    if (fut->is_task()) {
+      // It's a task.  Make it active again.
+      wake_task((AsyncTask *)fut);
+    } else {
+      // It's a gathering future.  Decrease the pending count on it, and if
+      // we're the last one, call notify_done() on it.
+      AsyncGatheringFuture *gather = (AsyncGatheringFuture *)fut;
+      if (!AtomicAdjust::dec(gather->_num_pending)) {
+        if (gather->set_future_state(FS_finished)) {
+          gather->notify_done(true);
+        }
+      }
+    }
+  }
+  _waiting.clear();
+
+  // For historical reasons, we don't send the "done event" if the future was
+  // cancelled.
+  if (clean_exit && !_done_event.empty()) {
+    PT_Event event = new Event(_done_event);
+    event->add_parameter(EventParameter(this));
+    throw_event(move(event));
+  }
+}
+
+/**
+ * Sets this future's result.  Can only be done while the future is not done.
+ * Calling this marks the future as done and schedules the done callbacks.
+ *
+ * This variant takes two pointers; the second one is only set if this object
+ * inherits from ReferenceCount, so that a reference can be held.
+ *
+ * Assumes the manager's lock is *not* held.
+ */
+void AsyncFuture::
+set_result(TypedObject *ptr, ReferenceCount *ref_ptr) {
+  // We don't strictly need to lock the future since only one thread is
+  // allowed to call set_result(), but we might as well.
+  FutureState orig_state = (FutureState)AtomicAdjust::
+    compare_and_exchange(_future_state, (AtomicAdjust::Integer)FS_pending,
+                                        (AtomicAdjust::Integer)FS_locked_pending);
+
+  while (orig_state == FS_locked_pending) {
+    Thread::force_yield();
+    orig_state = (FutureState)AtomicAdjust::
+      compare_and_exchange(_future_state, (AtomicAdjust::Integer)FS_pending,
+                                          (AtomicAdjust::Integer)FS_locked_pending);
+  }
+
+  if (orig_state == FS_pending) {
+    _result = ptr;
+    _result_ref = ref_ptr;
+    unlock(FS_finished);
+
+    // OK, now our thread owns the _waiting vector et al.
+    notify_done(true);
+
+  } else if (orig_state == FS_cancelled) {
+    // This was originally illegal, but there is a chance that the future was
+    // cancelled while another thread was setting the result.  So, we drop
+    // this, but we can issue a warning.
+    task_cat.warning()
+      << "Ignoring set_result() called on cancelled " << *this << "\n";
+
+  } else {
+    task_cat.error()
+      << "set_result() was called on finished " << *this << "\n";
+  }
+}
+
+/**
+ * Indicates that the given task is waiting for this future to complete.  When
+ * the future is done, it will reactivate the given task.  If this is called
+ * while the future is already done, schedules the task immediately.
+ * Assumes the manager's lock is not held.
+ * @returns true if the future was pending, false if it was already done.
+ */
+bool AsyncFuture::
+add_waiting_task(AsyncTask *task) {
+  nassertr(task->is_runnable(), false);
+
+  // We have to make sure we're not going to change state while we're in the
+  // process of adding the task.
+  if (try_lock_pending()) {
+    if (_manager == nullptr) {
+      _manager = task->_manager;
+    }
+
+    _waiting.push_back(task);
+
+    // Unlock the state.
+    unlock();
+    nassertr(task->_manager == nullptr || task->_manager == _manager, true);
+    return true;
+  } else {
+    // It's already done.  Wake the task immediately.
+    wake_task(task);
+    return false;
+  }
+}
+
+/**
+ * Reactivates the given task.  Assumes the manager lock is not held.
+ */
+void AsyncFuture::
+wake_task(AsyncTask *task) {
+  cerr << "waking task\n";
+  AsyncTaskManager *manager = task->_manager;
+  if (manager == nullptr) {
+    // If it's an unscheduled task, schedule it on the same manager as the
+    // rest of the waiting tasks.
+    manager = _manager;
+    if (manager == nullptr) {
+      manager = AsyncTaskManager::get_global_ptr();
+    }
+  }
+
+  MutexHolder holder(manager->_lock);
+  switch (task->_state) {
+  case AsyncTask::S_servicing_removed:
+    nassertv(task->_manager == _manager);
+    // Re-adding a self-removed task; this just means clearing the removed
+    // flag.
+    task->_state = AsyncTask::S_servicing;
+    return;
+
+  case AsyncTask::S_inactive:
+    // Schedule it immediately.
+    nassertv(task->_manager == nullptr);
+
+    if (task_cat.is_debug()) {
+      task_cat.debug()
+        << "Adding " << *task << " (woken by future " << *this << ")\n";
+    }
+
+    {
+      manager->_lock.release();
+      task->upon_birth(manager);
+      manager->_lock.acquire();
+      nassertv(task->_manager == nullptr &&
+               task->_state == AsyncTask::S_inactive);
+
+      AsyncTaskChain *chain = manager->do_find_task_chain(task->_chain_name);
+      if (chain == nullptr) {
+        task_cat.warning()
+          << "Creating implicit AsyncTaskChain " << task->_chain_name
+          << " for " << manager->get_type() << " " << manager->get_name() << "\n";
+        chain = manager->do_make_task_chain(task->_chain_name);
+      }
+      chain->do_add(task);
+    }
+    return;
+
+  case AsyncTask::S_awaiting:
+    nassertv(task->_manager == _manager);
+    task->_state = AsyncTask::S_active;
+    task->_chain->_active.push_back(task);
+    --task->_chain->_num_awaiting_tasks;
+    return;
+
+  default:
+    nassertv(false);
+    return;
+  }
+}
+
+/**
+ * @see AsyncFuture::gather
+ */
+AsyncGatheringFuture::
+AsyncGatheringFuture(AsyncFuture::Futures futures) :
+  _futures(move(futures)),
+  _num_pending(0) {
+
+  bool any_pending = false;
+
+  AsyncFuture::Futures::const_iterator it;
+  for (it = _futures.begin(); it != _futures.end(); ++it) {
+    AsyncFuture *fut = *it;
+    // If this returns true, the future is not yet done and we need to
+    // register ourselves with it.  This creates a circular reference, but it
+    // is resolved when the future is completed or cancelled.
+    if (fut->try_lock_pending()) {
+      if (_manager == nullptr) {
+        _manager = fut->_manager;
+      }
+      fut->_waiting.push_back((AsyncFuture *)this);
+      AtomicAdjust::inc(_num_pending);
+      fut->unlock();
+      any_pending = true;
+    }
+  }
+  if (!any_pending) {
+    // Start in the done state if all the futures we were passed are done.
+    // Note that it is only safe to set this member in this manner if indeed
+    // no other future holds a reference to us.
+    _future_state = (AtomicAdjust::Integer)FS_finished;
+  }
+}
+
+/**
+ * Cancels all the futures.  Returns true if any futures were cancelled.
+ * Makes sure that all the futures finish before this one is marked done, in
+ * order to maintain the guarantee that calling result() is safe when done()
+ * returns true.
+ */
+bool AsyncGatheringFuture::
+cancel() {
+  if (!done()) {
+    // Temporarily increase the pending count so that the notify_done()
+    // callbacks won't end up causing it to be set to "finished".
+    AtomicAdjust::inc(_num_pending);
+
+    bool any_cancelled = false;
+    AsyncFuture::Futures::const_iterator it;
+    for (it = _futures.begin(); it != _futures.end(); ++it) {
+      AsyncFuture *fut = *it;
+      if (fut->cancel()) {
+        any_cancelled = true;
+      }
+    }
+
+    // Now change state to "cancelled" and call the notify_done() callbacks.
+    // Don't call notify_done() if another thread has beaten us to it.
+    if (set_future_state(FS_cancelled)) {
+      notify_done(false);
+    }
+
+    // Decreasing the pending count is kind of pointless now, so we do it only
+    // in a debug build.
+    nassertr(!AtomicAdjust::dec(_num_pending), any_cancelled);
+    return any_cancelled;
+  } else {
+    return false;
+  }
+}

+ 199 - 0
panda/src/event/asyncFuture.h

@@ -0,0 +1,199 @@
+/**
+ * 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 asyncFuture.h
+ * @author rdb
+ * @date 2017-11-28
+ */
+
+#ifndef ASYNCFUTURE_H
+#define ASYNCFUTURE_H
+
+#include "pandabase.h"
+#include "typedReferenceCount.h"
+#include "typedWritableReferenceCount.h"
+#include "eventParameter.h"
+#include "atomicAdjust.h"
+
+class AsyncTaskManager;
+class AsyncTask;
+class ConditionVarFull;
+
+/**
+ * This class represents a thread-safe handle to a promised future result of
+ * an asynchronous operation, providing methods to query its status and result
+ * as well as register callbacks for this future's completion.
+ *
+ * An AsyncFuture can be awaited from within a coroutine or task.  It keeps
+ * track of tasks waiting for this future and automatically reactivates them
+ * upon this future's completion.
+ *
+ * A task itself is also a subclass of AsyncFuture.  Other subclasses are
+ * not generally necessary, except to override the function of `cancel()`.
+ *
+ * Until the future is done, it is "owned" by the resolver thread, though it's
+ * still legal for other threads to query its state.  When the resolver thread
+ * resolves this future using `set_result()`, or any thread calls `cancel()`,
+ * it instantly enters the "done" state, after which the result becomes a
+ * read-only field that all threads can access.
+ *
+ * When the future returns true for done(), a thread can use cancelled() to
+ * determine whether the future was cancelled or get_result() to access the
+ * result of the operation.  Not all operations define a meaningful result
+ * value, so some will always return nullptr.
+ *
+ * In Python, the `cancelled()`, `wait()` and `get_result()` methods are
+ * wrapped up into a single `result()` method which waits for the future to
+ * complete before either returning the result or throwing an exception if the
+ * future was cancelled.
+ * However, it is preferable to use the `await` keyword when running from a
+ * coroutine, which only suspends the current task and not the entire thread.
+ *
+ * This API aims to mirror and be compatible with Python's Future class.
+ */
+class EXPCL_PANDA_EVENT AsyncFuture : public TypedReferenceCount {
+PUBLISHED:
+  INLINE AsyncFuture();
+  virtual ~AsyncFuture();
+
+  EXTENSION(static PyObject *__await__(PyObject *self));
+  EXTENSION(static PyObject *__iter__(PyObject *self));
+
+  INLINE bool done() const;
+  INLINE bool cancelled() const;
+  EXTENSION(PyObject *result(PyObject *timeout = Py_None) const);
+
+  virtual bool cancel();
+
+  INLINE void set_done_event(const string &done_event);
+  INLINE const string &get_done_event() const;
+  MAKE_PROPERTY(done_event, get_done_event, set_done_event);
+
+  EXTENSION(PyObject *add_done_callback(PyObject *self, PyObject *fn));
+
+  EXTENSION(static PyObject *gather(PyObject *args));
+
+  virtual void output(ostream &out) const;
+
+  BLOCKING void wait();
+  BLOCKING void wait(double timeout);
+
+  INLINE void set_result(nullptr_t);
+  INLINE void set_result(TypedObject *result);
+  INLINE void set_result(TypedReferenceCount *result);
+  INLINE void set_result(const EventParameter &result);
+
+public:
+  void set_result(TypedObject *ptr, ReferenceCount *ref_ptr);
+
+  INLINE TypedObject *get_result() const;
+  INLINE void get_result(TypedObject *&ptr, ReferenceCount *&ref_ptr) const;
+
+  typedef pvector<PT(AsyncFuture)> Futures;
+  INLINE static AsyncFuture *gather(Futures futures);
+
+  virtual bool is_task() const {return false;}
+
+  void notify_done(bool clean_exit);
+  bool add_waiting_task(AsyncTask *task);
+
+private:
+  void wake_task(AsyncTask *task);
+
+protected:
+  enum FutureState {
+    // Pending states
+    FS_pending,
+    FS_locked_pending,
+
+    // Done states
+    FS_finished,
+    FS_cancelled,
+  };
+  INLINE bool try_lock_pending();
+  INLINE void unlock(FutureState new_state = FS_pending);
+  INLINE bool set_future_state(FutureState state);
+
+  AsyncTaskManager *_manager;
+  TypedObject *_result;
+  PT(ReferenceCount) _result_ref;
+  AtomicAdjust::Integer _future_state;
+
+  string _done_event;
+
+  // Tasks and gathering futures waiting for this one to complete.
+  Futures _waiting;
+
+  friend class AsyncGatheringFuture;
+  friend class AsyncTaskChain;
+  friend class PythonTask;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    TypedReferenceCount::init_type();
+    register_type(_type_handle, "AsyncFuture",
+                  TypedReferenceCount::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+};
+
+INLINE ostream &operator << (ostream &out, const AsyncFuture &fut) {
+  fut.output(out);
+  return out;
+};
+
+/**
+ * Specific future that collects the results of several futures.
+ */
+class EXPCL_PANDA_EVENT AsyncGatheringFuture FINAL : public AsyncFuture {
+private:
+  AsyncGatheringFuture(AsyncFuture::Futures futures);
+
+public:
+  virtual bool cancel() override;
+
+  INLINE size_t get_num_futures() const;
+  INLINE AsyncFuture *get_future(size_t i) const;
+  INLINE TypedObject *get_result(size_t i) const;
+
+private:
+  const Futures _futures;
+  AtomicAdjust::Integer _num_pending;
+
+  friend class AsyncFuture;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    AsyncFuture::init_type();
+    register_type(_type_handle, "AsyncGatheringFuture",
+                  AsyncFuture::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+};
+
+#include "asyncFuture.I"
+
+#endif

+ 318 - 0
panda/src/event/asyncFuture_ext.cxx

@@ -0,0 +1,318 @@
+/**
+ * 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 asyncFuture_ext.h
+ * @author rdb
+ * @date 2017-10-29
+ */
+
+#include "asyncFuture_ext.h"
+#include "asyncTaskSequence.h"
+#include "eventParameter.h"
+#include "paramValue.h"
+#include "pythonTask.h"
+
+#ifdef HAVE_PYTHON
+
+#ifndef CPPPARSER
+extern struct Dtool_PyTypedObject Dtool_AsyncFuture;
+extern struct Dtool_PyTypedObject Dtool_ParamValueBase;
+extern struct Dtool_PyTypedObject Dtool_TypedObject;
+#endif
+
+/**
+ * Get the result of a future, which may be a PythonTask.  Assumes that the
+ * future is already done.
+ */
+static PyObject *get_done_result(const AsyncFuture *future) {
+  if (!future->cancelled()) {
+    if (future->is_of_type(PythonTask::get_class_type())) {
+      // If it's a PythonTask, defer to its get_result(), since it may store
+      // any PyObject value or raise an exception.
+      const PythonTask *task = (const PythonTask *)future;
+      return task->get_result();
+
+    } else if (future->is_of_type(AsyncTaskSequence::get_class_type())) {
+      // If it's an AsyncTaskSequence, get the result for each task.
+      const AsyncTaskSequence *task = (const AsyncTaskSequence *)future;
+      Py_ssize_t num_tasks = (Py_ssize_t)task->get_num_tasks();
+      PyObject *results = PyTuple_New(num_tasks);
+
+      for (Py_ssize_t i = 0; i < num_tasks; ++i) {
+        PyObject *result = get_done_result(task->get_task(i));
+        if (result != nullptr) {
+          // This steals a reference.
+          PyTuple_SET_ITEM(results, i, result);
+        } else {
+          Py_DECREF(results);
+          return nullptr;
+        }
+      }
+      return results;
+
+    } else if (future->is_of_type(AsyncGatheringFuture::get_class_type())) {
+      // If it's an AsyncGatheringFuture, get the result for each future.
+      const AsyncGatheringFuture *gather = (const AsyncGatheringFuture *)future;
+      Py_ssize_t num_futures = (Py_ssize_t)gather->get_num_futures();
+      PyObject *results = PyTuple_New(num_futures);
+
+      for (Py_ssize_t i = 0; i < num_futures; ++i) {
+        PyObject *result = get_done_result(gather->get_future((size_t)i));
+        if (result != nullptr) {
+          // This steals a reference.
+          PyTuple_SET_ITEM(results, i, result);
+        } else {
+          Py_DECREF(results);
+          return nullptr;
+        }
+      }
+      return results;
+
+    } else {
+      // It's any other future.
+      ReferenceCount *ref_ptr;
+      TypedObject *ptr;
+      future->get_result(ptr, ref_ptr);
+
+      if (ptr == nullptr) {
+        Py_INCREF(Py_None);
+        return Py_None;
+      }
+
+      TypeHandle type = ptr->get_type();
+      if (type.is_derived_from(ParamValueBase::get_class_type())) {
+        // If this is a ParamValueBase, return the 'value' property.
+        // EventStoreInt and Double are not exposed to Python for some reason.
+        if (type == EventStoreInt::get_class_type()) {
+          return Dtool_WrapValue(((EventStoreInt *)ptr)->get_value());
+        } else if (type == EventStoreDouble::get_class_type()) {
+          return Dtool_WrapValue(((EventStoreDouble *)ptr)->get_value());
+        }
+
+        ParamValueBase *value = (ParamValueBase *)ptr;
+        PyObject *wrap = DTool_CreatePyInstanceTyped
+          ((void *)value, Dtool_ParamValueBase, false, false, type.get_index());
+        if (wrap != nullptr) {
+          PyObject *value = PyObject_GetAttrString(wrap, "value");
+          if (value != nullptr) {
+            return value;
+          }
+          PyErr_Restore(nullptr, nullptr, nullptr);
+          Py_DECREF(wrap);
+        }
+      }
+
+      if (ref_ptr != nullptr) {
+        ref_ptr->ref();
+      }
+
+      return DTool_CreatePyInstanceTyped
+        ((void *)ptr, Dtool_TypedObject, (ref_ptr != nullptr), false,
+         type.get_index());
+    }
+  } else {
+    // If the future was cancelled, we should raise an exception.
+    static PyObject *exc_type = nullptr;
+    if (exc_type == nullptr) {
+      // Get the CancelledError that asyncio uses, too.
+      PyObject *module = PyImport_ImportModule("concurrent.futures._base");
+      if (module != nullptr) {
+        exc_type = PyObject_GetAttrString(module, "CancelledError");
+        Py_DECREF(module);
+      }
+      // If we can't get that, we should pretend and make our own.
+      if (exc_type == nullptr) {
+        exc_type = PyErr_NewExceptionWithDoc((char*)"concurrent.futures._base.CancelledError",
+                                             (char*)"The Future was cancelled.",
+                                             nullptr, nullptr);
+      }
+    }
+    Py_INCREF(exc_type);
+    PyErr_Restore(exc_type, nullptr, nullptr);
+    return nullptr;
+  }
+}
+
+/**
+ * Yields continuously until the task has finished.
+ */
+static PyObject *gen_next(PyObject *self) {
+  const AsyncFuture *future = nullptr;
+  if (!Dtool_Call_ExtractThisPointer(self, Dtool_AsyncFuture, (void **)&future)) {
+    return nullptr;
+  }
+
+  if (!future->done()) {
+    // Continue awaiting the result.
+    Py_INCREF(self);
+    return self;
+  } else {
+    PyObject *result = get_done_result(future);
+    if (result != nullptr) {
+      Py_INCREF(PyExc_StopIteration);
+      PyErr_Restore(PyExc_StopIteration, result, nullptr);
+    }
+    return nullptr;
+  }
+}
+
+/**
+ * Returns a generator that continuously yields an awaitable until the task
+ * has finished.  This allows syntax like `model = await loader.load...` to be
+ * used in a Python coroutine.
+ */
+PyObject *Extension<AsyncFuture>::
+__await__(PyObject *self) {
+  Dtool_GeneratorWrapper *gen;
+  gen = (Dtool_GeneratorWrapper *)PyType_GenericAlloc(&Dtool_GeneratorWrapper_Type, 0);
+  if (gen != nullptr) {
+    Py_INCREF(self);
+    gen->_base._self = self;
+    gen->_iternext_func = &gen_next;
+  }
+  return (PyObject *)gen;
+}
+
+/**
+ * Returns the result of this future, unless it was cancelled, in which case
+ * it returns CancelledError.
+ * If the future is not yet done, waits until the result is available.  If a
+ * timeout is passed and the future is not done within the given timeout,
+ * raises TimeoutError.
+ */
+PyObject *Extension<AsyncFuture>::
+result(PyObject *timeout) const {
+  if (!_this->done()) {
+    // Not yet done?  Wait until it is done, or until a timeout occurs.  But
+    // first check to make sure we're not trying to deadlock the thread.
+    Thread *current_thread = Thread::get_current_thread();
+    if (_this == (const AsyncFuture *)current_thread->get_current_task()) {
+      PyErr_SetString(PyExc_RuntimeError, "cannot call task.result() from within the task");
+      return nullptr;
+    }
+
+    // Release the GIL for the duration.
+#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
+    PyThreadState *_save;
+    Py_UNBLOCK_THREADS
+#endif
+    if (timeout == Py_None) {
+      _this->wait();
+    } else {
+      PyObject *num = PyNumber_Float(timeout);
+      if (num != nullptr) {
+        _this->wait(PyFloat_AS_DOUBLE(num));
+      } else {
+        return Dtool_Raise_ArgTypeError(timeout, 0, "result", "float");
+      }
+    }
+#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
+    Py_BLOCK_THREADS
+#endif
+
+    if (!_this->done()) {
+      // It timed out.  Raise an exception.
+      static PyObject *exc_type = nullptr;
+      if (exc_type == nullptr) {
+        // Get the TimeoutError that asyncio uses, too.
+        PyObject *module = PyImport_ImportModule("concurrent.futures._base");
+        if (module != nullptr) {
+          exc_type = PyObject_GetAttrString(module, "TimeoutError");
+          Py_DECREF(module);
+        }
+        // If we can't get that, we should pretend and make our own.
+        if (exc_type == nullptr) {
+          exc_type = PyErr_NewExceptionWithDoc((char*)"concurrent.futures._base.TimeoutError",
+                                               (char*)"The operation exceeded the given deadline.",
+                                               nullptr, nullptr);
+        }
+      }
+      Py_INCREF(exc_type);
+      PyErr_Restore(exc_type, nullptr, nullptr);
+      return nullptr;
+    }
+  }
+
+  return get_done_result(_this);
+}
+
+/**
+ * Schedules the given function to be run as soon as the future is complete.
+ * This is also called if the future is cancelled.
+ * If the future is already done, the callback is scheduled right away.
+ */
+PyObject *Extension<AsyncFuture>::
+add_done_callback(PyObject *self, PyObject *fn) {
+  if (!PyCallable_Check(fn)) {
+    return Dtool_Raise_ArgTypeError(fn, 0, "add_done_callback", "callable");
+  }
+
+  PythonTask *task = new PythonTask(fn);
+  Py_DECREF(task->_args);
+  task->_args = PyTuple_Pack(1, self);
+  task->_append_task = false;
+  task->_ignore_return = true;
+
+  // If this is an AsyncTask, make sure it is scheduled on the same chain.
+  if (_this->is_task()) {
+    AsyncTask *this_task = (AsyncTask *)_this;
+    task->set_task_chain(this_task->get_task_chain());
+  }
+
+  _this->add_waiting_task(task);
+
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+/**
+ * Creates a new future that returns `done()` when all of the contained
+ * futures are done.
+ *
+ * Calling `cancel()` on the returned future will result in all contained
+ * futures that have not yet finished to be cancelled.
+ */
+PyObject *Extension<AsyncFuture>::
+gather(PyObject *args) {
+  if (!PyTuple_Check(args)) {
+    return Dtool_Raise_TypeError("args is not a tuple");
+  }
+
+  Py_ssize_t size = Py_SIZE(args);
+  AsyncFuture::Futures futures;
+  futures.reserve(size);
+
+  for (Py_ssize_t i = 0; i < size; ++i) {
+    PyObject *item = PyTuple_GET_ITEM(args, i);
+    if (DtoolInstance_Check(item)) {
+      AsyncFuture *fut = (AsyncFuture *)DtoolInstance_UPCAST(item, Dtool_AsyncFuture);
+      if (fut != nullptr) {
+        futures.push_back(fut);
+        continue;
+      }
+#if PY_VERSION_HEX >= 0x03050000
+    } else if (PyCoro_CheckExact(item)) {
+      // We allow passing in a coroutine instead of a future.  This causes it
+      // to be scheduled as a task.
+      futures.push_back(new PythonTask(item));
+      continue;
+#endif
+    }
+    return Dtool_Raise_ArgTypeError(item, i, "gather", "coroutine, task or future");
+  }
+
+  AsyncFuture *future = AsyncFuture::gather(move(futures));
+  if (future != nullptr) {
+    future->ref();
+    return DTool_CreatePyInstanceTyped((void *)future, Dtool_AsyncFuture, true, false, future->get_type_index());
+  } else {
+    return PyErr_NoMemory();
+  }
+}
+
+#endif

+ 12 - 6
panda/src/event/asyncTask_ext.h → panda/src/event/asyncFuture_ext.h

@@ -6,13 +6,13 @@
  * license.  You should have received a copy of this license along
  * with this source code in a file named "LICENSE."
  *
- * @file asyncTask_ext.h
+ * @file asyncFuture_ext.h
  * @author rdb
  * @date 2017-10-29
  */
 
-#ifndef ASYNCTASK_EXT_H
-#define ASYNCTASK_EXT_H
+#ifndef ASYNCFUTURE_EXT_H
+#define ASYNCFUTURE_EXT_H
 
 #include "extension.h"
 #include "py_panda.h"
@@ -21,15 +21,21 @@
 #ifdef HAVE_PYTHON
 
 /**
- * Extension class for AsyncTask
+ * Extension class for AsyncFuture
  */
 template<>
-class Extension<AsyncTask> : public ExtensionBase<AsyncTask> {
+class Extension<AsyncFuture> : public ExtensionBase<AsyncFuture> {
 public:
   static PyObject *__await__(PyObject *self);
   static PyObject *__iter__(PyObject *self) { return __await__(self); }
+
+  PyObject *result(PyObject *timeout = Py_None) const;
+
+  PyObject *add_done_callback(PyObject *self, PyObject *fn);
+
+  static PyObject *gather(PyObject *args);
 };
 
 #endif  // HAVE_PYTHON
 
-#endif  // ASYNCTASK_EXT_H
+#endif  // ASYNCFUTURE_EXT_H

+ 0 - 9
panda/src/event/asyncTask.I

@@ -181,15 +181,6 @@ set_done_event(const string &done_event) {
   _done_event = done_event;
 }
 
-/**
- * Returns the event name that will be triggered when the task finishes.  See
- * set_done_event().
- */
-INLINE const string &AsyncTask::
-get_done_event() const {
-  return _done_event;
-}
-
 /**
  * Returns the amount of time elapsed during the task's previous run cycle, in
  * seconds.

+ 65 - 16
panda/src/event/asyncTask.cxx

@@ -35,7 +35,6 @@ AsyncTask(const string &name) :
   _priority(0),
   _state(S_inactive),
   _servicing_thread(NULL),
-  _manager(NULL),
   _chain(NULL),
   _start_time(0.0),
   _start_frame(0),
@@ -68,11 +67,27 @@ AsyncTask::
  * S_inactive (or possible S_servicing_removed).  This is a no-op if the state
  * is already S_inactive.
  */
-void AsyncTask::
+bool AsyncTask::
 remove() {
-  if (_manager != (AsyncTaskManager *)NULL) {
-    _manager->remove(this);
+  AsyncTaskManager *manager = _manager;
+  if (manager != nullptr) {
+    nassertr(_chain->_manager == manager, false);
+    if (task_cat.is_debug()) {
+      task_cat.debug()
+        << "Removing " << *this << "\n";
+    }
+    MutexHolder holder(manager->_lock);
+    if (_chain->do_remove(this, true)) {
+      return true;
+    } else {
+      if (task_cat.is_debug()) {
+        task_cat.debug()
+          << "  (unable to remove " << *this << ")\n";
+      }
+      return false;
+    }
   }
+  return false;
 }
 
 /**
@@ -379,11 +394,26 @@ jump_to_task_chain(AsyncTaskManager *manager) {
  */
 AsyncTask::DoneStatus AsyncTask::
 unlock_and_do_task() {
-  nassertr(_manager != (AsyncTaskManager *)NULL, DS_done);
+  nassertr(_manager != nullptr, DS_done);
   PT(ClockObject) clock = _manager->get_clock();
 
+  // Indicate that this task is now the current task running on the thread.
   Thread *current_thread = Thread::get_current_thread();
-  record_task(current_thread);
+  nassertr(current_thread->_current_task == nullptr, DS_interrupt);
+
+  void *ptr = AtomicAdjust::compare_and_exchange_ptr
+    ((void * TVOLATILE &)current_thread->_current_task,
+     (void *)nullptr, (void *)this);
+
+  // If the return value is other than nullptr, someone else must have
+  // assigned the task first, in another thread.  That shouldn't be possible.
+
+  // But different versions of gcc appear to have problems compiling these
+  // assertions correctly.
+#ifndef __GNUC__
+  nassertr(ptr == nullptr, DS_interrupt);
+  nassertr(current_thread->_current_task == this, DS_interrupt);
+#endif  // __GNUC__
 
   // It's important to release the lock while the task is being serviced.
   _manager->_lock.release();
@@ -403,11 +433,36 @@ unlock_and_do_task() {
 
   _chain->_time_in_frame += _dt;
 
-  clear_task(current_thread);
+  // Now indicate that this is no longer the current task.
+  nassertr(current_thread->_current_task == this, status);
+
+  ptr = AtomicAdjust::compare_and_exchange_ptr
+    ((void * TVOLATILE &)current_thread->_current_task,
+     (void *)this, (void *)nullptr);
+
+  // If the return value is other than this, someone else must have assigned
+  // the task first, in another thread.  That shouldn't be possible.
+
+  // But different versions of gcc appear to have problems compiling these
+  // assertions correctly.
+#ifndef __GNUC__
+  nassertr(ptr == this, DS_interrupt);
+  nassertr(current_thread->_current_task == nullptr, DS_interrupt);
+#endif  // __GNUC__
 
   return status;
 }
 
+/**
+ * Cancels this task.  This is equivalent to remove().
+ */
+bool AsyncTask::
+cancel() {
+  bool result = remove();
+  nassertr(done(), false);
+  return result;
+}
+
 /**
  * Override this function to return true if the task can be successfully
  * executed, false if it cannot.  Mainly intended as a sanity check when
@@ -477,22 +532,16 @@ upon_birth(AsyncTaskManager *manager) {
  * task has been removed because it exited normally (returning DS_done), or
  * false if it was removed for some other reason (e.g.
  * AsyncTaskManager::remove()).  By the time this method is called, _manager
- * has been cleared, so the parameter manager indicates the original
+ * may have been cleared, so the parameter manager indicates the original
  * AsyncTaskManager that owned this task.
  *
- * The normal behavior is to throw the done_event only if clean_exit is true.
- *
  * This function is called with the lock *not* held.
  */
 void AsyncTask::
 upon_death(AsyncTaskManager *manager, bool clean_exit) {
-  if (clean_exit && !_done_event.empty()) {
-    PT_Event event = new Event(_done_event);
-    event->add_parameter(EventParameter(this));
-    throw_event(event);
-  }
+  //NB. done_event is now being thrown in AsyncFuture::notify_done().
 
-  // Also throw a generic remove event for the manager.
+  // Throw a generic remove event for the manager.
   if (manager != (AsyncTaskManager *)NULL) {
     string remove_name = manager->get_name() + "-removeTask";
     PT_Event event = new Event(remove_name);

+ 10 - 15
panda/src/event/asyncTask.h

@@ -15,8 +15,8 @@
 #define ASYNCTASK_H
 
 #include "pandabase.h"
-
-#include "asyncTaskBase.h"
+#include "asyncFuture.h"
+#include "namable.h"
 #include "pmutex.h"
 #include "conditionVar.h"
 #include "pStatCollector.h"
@@ -29,7 +29,7 @@ class AsyncTaskChain;
  * Normally, you would subclass from this class, and override do_task(), to
  * define the functionality you wish to have the task perform.
  */
-class EXPCL_PANDA_EVENT AsyncTask : public AsyncTaskBase {
+class EXPCL_PANDA_EVENT AsyncTask : public AsyncFuture, public Namable {
 public:
   AsyncTask(const string &name = string());
   ALLOC_DELETED_CHAIN(AsyncTask);
@@ -62,7 +62,7 @@ PUBLISHED:
   INLINE bool is_alive() const;
   INLINE AsyncTaskManager *get_manager() const;
 
-  void remove();
+  bool remove();
 
   INLINE void set_delay(double delay);
   INLINE void clear_delay();
@@ -92,7 +92,6 @@ PUBLISHED:
   INLINE int get_priority() const;
 
   INLINE void set_done_event(const string &done_event);
-  INLINE const string &get_done_event() const;
 
   INLINE double get_dt() const;
   INLINE double get_max_dt() const;
@@ -100,13 +99,13 @@ PUBLISHED:
 
   virtual void output(ostream &out) const;
 
-  EXTENSION(static PyObject *__await__(PyObject *self));
-  EXTENSION(static PyObject *__iter__(PyObject *self));
-
 protected:
   void jump_to_task_chain(AsyncTaskManager *manager);
   DoneStatus unlock_and_do_task();
 
+  virtual bool cancel() FINAL;
+  virtual bool is_task() const FINAL {return true;}
+
   virtual bool is_runnable();
   virtual DoneStatus do_task();
   virtual void upon_birth(AsyncTaskManager *manager);
@@ -120,11 +119,9 @@ protected:
   double _wake_time;
   int _sort;
   int _priority;
-  string _done_event;
 
   State _state;
   Thread *_servicing_thread;
-  AsyncTaskManager *_manager;
   AsyncTaskChain *_chain;
 
   double _start_time;
@@ -135,9 +132,6 @@ protected:
   double _total_dt;
   int _num_frames;
 
-  // Tasks waiting for this one to complete.
-  pvector<PT(AsyncTask)> _waiting_tasks;
-
   static AtomicAdjust::Integer _next_task_id;
 
   static PStatCollector _show_code_pcollector;
@@ -150,9 +144,9 @@ public:
     return _type_handle;
   }
   static void init_type() {
-    AsyncTaskBase::init_type();
+    AsyncFuture::init_type();
     register_type(_type_handle, "AsyncTask",
-                  AsyncTaskBase::get_class_type());
+                  AsyncFuture::get_class_type());
   }
   virtual TypeHandle get_type() const {
     return get_class_type();
@@ -162,6 +156,7 @@ public:
 private:
   static TypeHandle _type_handle;
 
+  friend class AsyncFuture;
   friend class AsyncTaskManager;
   friend class AsyncTaskChain;
   friend class AsyncTaskSequence;

+ 17 - 29
panda/src/event/asyncTaskChain.cxx

@@ -456,24 +456,21 @@ do_add(AsyncTask *task) {
 /**
  * Removes the indicated task from this chain.  Returns true if removed, false
  * otherwise.  Assumes the lock is already held.  The task->upon_death()
- * method is *not* called.
+ * method is called with clean_exit=false if upon_death is given.
  */
 bool AsyncTaskChain::
-do_remove(AsyncTask *task) {
-  bool removed = false;
-
+do_remove(AsyncTask *task, bool upon_death) {
   nassertr(task->_chain == this, false);
 
   switch (task->_state) {
   case AsyncTask::S_servicing:
-    // This task is being serviced.
+    // This task is being serviced.  upon_death will be called afterwards.
     task->_state = AsyncTask::S_servicing_removed;
-    removed = true;
-    break;
+    return true;
 
   case AsyncTask::S_servicing_removed:
-    // Being serviced, though it will be removed later.
-    break;
+    // Being serviced, though it is already marked to be removed afterwards.
+    return false;
 
   case AsyncTask::S_sleeping:
     // Sleeping, easy.
@@ -482,10 +479,9 @@ do_remove(AsyncTask *task) {
       nassertr(index != -1, false);
       _sleeping.erase(_sleeping.begin() + index);
       make_heap(_sleeping.begin(), _sleeping.end(), AsyncTaskSortWakeTime());
-      removed = true;
-      cleanup_task(task, false, false);
+      cleanup_task(task, upon_death, false);
     }
-    break;
+    return true;
 
   case AsyncTask::S_active:
     {
@@ -503,15 +499,15 @@ do_remove(AsyncTask *task) {
           nassertr(index != -1, false);
         }
       }
-      removed = true;
-      cleanup_task(task, false, false);
+      cleanup_task(task, upon_death, false);
+      return true;
     }
 
   default:
     break;
   }
 
-  return removed;
+  return false;
 }
 
 /**
@@ -776,27 +772,19 @@ cleanup_task(AsyncTask *task, bool upon_death, bool clean_exit) {
   PT(AsyncTask) hold_task = task;
 
   task->_state = AsyncTask::S_inactive;
-  task->_chain = NULL;
-  task->_manager = NULL;
+  task->_chain = nullptr;
   --_num_tasks;
   --(_manager->_num_tasks);
 
   _manager->remove_task_by_name(task);
 
-  // Activate the tasks that were waiting for this one to finish.
-  if (upon_death) {
-    pvector<PT(AsyncTask)>::iterator it;
-    for (it = task->_waiting_tasks.begin(); it != task->_waiting_tasks.end(); ++it) {
-      AsyncTask *task = *it;
-      // Note that this task may not be on the same task chain.
-      nassertd(task->_manager == _manager) continue;
-      task->_state = AsyncTask::S_active;
-      task->_chain->_active.push_back(task);
-      --task->_chain->_num_awaiting_tasks;
-    }
-    task->_waiting_tasks.clear();
+  if (upon_death && task->set_future_state(clean_exit ? AsyncFuture::FS_finished
+                                                      : AsyncFuture::FS_cancelled)) {
+    task->notify_done(clean_exit);
   }
 
+  task->_manager = nullptr;
+
   if (upon_death) {
     _manager->_lock.release();
     task->upon_death(_manager, clean_exit);

+ 2 - 1
panda/src/event/asyncTaskChain.h

@@ -96,7 +96,7 @@ protected:
   typedef pvector< PT(AsyncTask) > TaskHeap;
 
   void do_add(AsyncTask *task);
-  bool do_remove(AsyncTask *task);
+  bool do_remove(AsyncTask *task, bool upon_death=false);
   void do_wait_for_tasks();
   void do_cleanup();
 
@@ -206,6 +206,7 @@ public:
 private:
   static TypeHandle _type_handle;
 
+  friend class AsyncFuture;
   friend class AsyncTaskChainThread;
   friend class AsyncTask;
   friend class AsyncTaskManager;

+ 19 - 19
panda/src/event/asyncTaskCollection.cxx

@@ -63,14 +63,15 @@ add_task(AsyncTask *task) {
  */
 bool AsyncTaskCollection::
 remove_task(AsyncTask *task) {
-  int task_index = -1;
-  for (int i = 0; task_index == -1 && i < (int)_tasks.size(); i++) {
+  size_t task_index = (size_t)-1;
+  for (size_t i = 0; i < _tasks.size(); ++i) {
     if (_tasks[i] == task) {
       task_index = i;
+      break;
     }
   }
 
-  if (task_index == -1) {
+  if (task_index == (size_t)-1) {
     // The indicated task was not a member of the collection.
     return false;
   }
@@ -129,12 +130,12 @@ void AsyncTaskCollection::
 remove_duplicate_tasks() {
   AsyncTasks new_tasks;
 
-  int num_tasks = get_num_tasks();
-  for (int i = 0; i < num_tasks; i++) {
+  size_t num_tasks = get_num_tasks();
+  for (size_t i = 0; i < num_tasks; i++) {
     PT(AsyncTask) task = get_task(i);
     bool duplicated = false;
 
-    for (int j = 0; j < i && !duplicated; j++) {
+    for (size_t j = 0; j < i && !duplicated; j++) {
       duplicated = (task == get_task(j));
     }
 
@@ -152,7 +153,7 @@ remove_duplicate_tasks() {
  */
 bool AsyncTaskCollection::
 has_task(AsyncTask *task) const {
-  for (int i = 0; i < get_num_tasks(); i++) {
+  for (size_t i = 0; i < get_num_tasks(); i++) {
     if (task == get_task(i)) {
       return true;
     }
@@ -174,8 +175,8 @@ clear() {
  */
 AsyncTask *AsyncTaskCollection::
 find_task(const string &name) const {
-  int num_tasks = get_num_tasks();
-  for (int i = 0; i < num_tasks; i++) {
+  size_t num_tasks = get_num_tasks();
+  for (size_t i = 0; i < num_tasks; ++i) {
     AsyncTask *task = get_task(i);
     if (task->get_name() == name) {
       return task;
@@ -187,7 +188,7 @@ find_task(const string &name) const {
 /**
  * Returns the number of AsyncTasks in the collection.
  */
-int AsyncTaskCollection::
+size_t AsyncTaskCollection::
 get_num_tasks() const {
   return _tasks.size();
 }
@@ -196,8 +197,8 @@ get_num_tasks() const {
  * Returns the nth AsyncTask in the collection.
  */
 AsyncTask *AsyncTaskCollection::
-get_task(int index) const {
-  nassertr(index >= 0 && index < (int)_tasks.size(), NULL);
+get_task(size_t index) const {
+  nassertr(index < _tasks.size(), nullptr);
 
   return _tasks[index];
 }
@@ -206,7 +207,7 @@ get_task(int index) const {
  * Removes the nth AsyncTask from the collection.
  */
 void AsyncTaskCollection::
-remove_task(int index) {
+remove_task(size_t index) {
   // If the pointer to our internal array is shared by any other
   // AsyncTaskCollections, we have to copy the array now so we won't
   // inadvertently modify any of our brethren AsyncTaskCollection objects.
@@ -217,7 +218,7 @@ remove_task(int index) {
     _tasks.v() = old_tasks.v();
   }
 
-  nassertv(index >= 0 && index < (int)_tasks.size());
+  nassertv(index < _tasks.size());
   _tasks.erase(_tasks.begin() + index);
 }
 
@@ -226,9 +227,8 @@ remove_task(int index) {
  * get_task(), but it may be a more convenient way to access it.
  */
 AsyncTask *AsyncTaskCollection::
-operator [] (int index) const {
-  nassertr(index >= 0 && index < (int)_tasks.size(), NULL);
-
+operator [] (size_t index) const {
+  nassertr(index < _tasks.size(), nullptr);
   return _tasks[index];
 }
 
@@ -236,7 +236,7 @@ operator [] (int index) const {
  * Returns the number of tasks in the collection.  This is the same thing as
  * get_num_tasks().
  */
-int AsyncTaskCollection::
+size_t AsyncTaskCollection::
 size() const {
   return _tasks.size();
 }
@@ -260,7 +260,7 @@ output(ostream &out) const {
  */
 void AsyncTaskCollection::
 write(ostream &out, int indent_level) const {
-  for (int i = 0; i < get_num_tasks(); i++) {
+  for (size_t i = 0; i < get_num_tasks(); i++) {
     indent(out, indent_level) << *get_task(i) << "\n";
   }
 }

+ 5 - 5
panda/src/event/asyncTaskCollection.h

@@ -41,12 +41,12 @@ PUBLISHED:
 
   AsyncTask *find_task(const string &name) const;
 
-  int get_num_tasks() const;
-  AsyncTask *get_task(int index) const;
+  size_t get_num_tasks() const;
+  AsyncTask *get_task(size_t index) const;
   MAKE_SEQ(get_tasks, get_num_tasks, get_task);
-  void remove_task(int index);
-  AsyncTask *operator [] (int index) const;
-  int size() const;
+  void remove_task(size_t index);
+  AsyncTask *operator [] (size_t index) const;
+  size_t size() const;
   INLINE void operator += (const AsyncTaskCollection &other);
   INLINE AsyncTaskCollection operator + (const AsyncTaskCollection &other) const;
 

+ 1 - 1
panda/src/event/asyncTaskManager.I

@@ -36,7 +36,7 @@ get_clock() {
  * Returns the number of tasks that are currently active or sleeping within
  * the task manager.
  */
-INLINE int AsyncTaskManager::
+INLINE size_t AsyncTaskManager::
 get_num_tasks() const {
   MutexHolder holder(_lock);
   return _num_tasks;

+ 6 - 14
panda/src/event/asyncTaskManager.cxx

@@ -307,25 +307,20 @@ find_tasks_matching(const GlobPattern &pattern) const {
  */
 bool AsyncTaskManager::
 remove(AsyncTask *task) {
-  // We pass this up to the multi-task remove() flavor.  Do we care about the
-  // tiny cost of creating an AsyncTaskCollection here?  Probably not.
-  AsyncTaskCollection tasks;
-  tasks.add_task(task);
-  return remove(tasks) != 0;
+  return task->remove();
 }
 
 /**
  * Removes all of the tasks in the AsyncTaskCollection.  Returns the number of
  * tasks removed.
  */
-int AsyncTaskManager::
+size_t AsyncTaskManager::
 remove(const AsyncTaskCollection &tasks) {
   MutexHolder holder(_lock);
-  int num_removed = 0;
+  size_t num_removed = 0;
 
-  int num_tasks = tasks.get_num_tasks();
-  int i;
-  for (i = 0; i < num_tasks; ++i) {
+  size_t num_tasks = tasks.get_num_tasks();
+  for (size_t i = 0; i < num_tasks; ++i) {
     PT(AsyncTask) task = tasks.get_task(i);
 
     if (task->_manager != this) {
@@ -337,10 +332,7 @@ remove(const AsyncTaskCollection &tasks) {
         task_cat.debug()
           << "Removing " << *task << "\n";
       }
-      if (task->_chain->do_remove(task)) {
-        _lock.release();
-        task->upon_death(this, false);
-        _lock.acquire();
+      if (task->_chain->do_remove(task, true)) {
         ++num_removed;
       } else {
         if (task_cat.is_debug()) {

+ 4 - 3
panda/src/event/asyncTaskManager.h

@@ -71,13 +71,13 @@ PUBLISHED:
   AsyncTaskCollection find_tasks_matching(const GlobPattern &pattern) const;
 
   bool remove(AsyncTask *task);
-  int remove(const AsyncTaskCollection &tasks);
+  size_t remove(const AsyncTaskCollection &tasks);
 
   BLOCKING void wait_for_tasks();
   BLOCKING void stop_threads();
   void start_threads();
 
-  INLINE int get_num_tasks() const;
+  INLINE size_t get_num_tasks() const;
 
   AsyncTaskCollection get_tasks() const;
   AsyncTaskCollection get_active_tasks() const;
@@ -126,7 +126,7 @@ protected:
   typedef ov_set<PT(AsyncTaskChain), IndirectCompareNames<AsyncTaskChain> > TaskChains;
   TaskChains _task_chains;
 
-  int _num_tasks;
+  size_t _num_tasks;
   TasksByName _tasks_by_name;
   PT(ClockObject) _clock;
 
@@ -151,6 +151,7 @@ public:
 private:
   static TypeHandle _type_handle;
 
+  friend class AsyncFuture;
   friend class AsyncTaskChain;
   friend class AsyncTaskChain::AsyncTaskChainThread;
   friend class AsyncTask;

+ 0 - 75
panda/src/event/asyncTask_ext.cxx

@@ -1,75 +0,0 @@
-/**
- * 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 asyncTask_ext.h
- * @author rdb
- * @date 2017-10-29
- */
-
-#include "asyncTask_ext.h"
-#include "nodePath.h"
-
-#ifdef HAVE_PYTHON
-
-#ifndef CPPPARSER
-extern struct Dtool_PyTypedObject Dtool_AsyncTask;
-#endif
-
-/**
- * Yields continuously until the task has finished.
- */
-static PyObject *gen_next(PyObject *self) {
-  const AsyncTask *request = nullptr;
-  if (!Dtool_Call_ExtractThisPointer(self, Dtool_AsyncTask, (void **)&request)) {
-    return nullptr;
-  }
-
-  if (request->is_alive()) {
-    // Continue awaiting the result.
-    Py_INCREF(self);
-    return self;
-  } else {
-    // It's done.  Do we have a method like result(), eg. in the case of a
-    // ModelLoadRequest?  In that case we pass that value into the exception.
-    PyObject *method = PyObject_GetAttrString(self, "result");
-    PyObject *result = nullptr;
-    if (method != nullptr) {
-      if (PyCallable_Check(method)) {
-        result = _PyObject_CallNoArg(method);
-        Py_DECREF(method);
-        if (result == nullptr) {
-          // An exception happened.  Pass it on.
-          return nullptr;
-        }
-      }
-      Py_DECREF(method);
-    }
-    Py_INCREF(PyExc_StopIteration);
-    PyErr_Restore(PyExc_StopIteration, result, nullptr);
-    return nullptr;
-  }
-}
-
-/**
- * Returns a generator that continuously yields an awaitable until the task
- * has finished.  This allows syntax like `model = await loader.load...` to be
- * used in a Python coroutine.
- */
-PyObject *Extension<AsyncTask>::
-__await__(PyObject *self) {
-  Dtool_GeneratorWrapper *gen;
-  gen = (Dtool_GeneratorWrapper *)PyType_GenericAlloc(&Dtool_GeneratorWrapper_Type, 0);
-  if (gen != nullptr) {
-    Py_INCREF(self);
-    gen->_base._self = self;
-    gen->_iternext_func = &gen_next;
-  }
-  return (PyObject *)gen;
-}
-
-#endif

+ 3 - 0
panda/src/event/config_event.cxx

@@ -12,6 +12,7 @@
  */
 
 #include "config_event.h"
+#include "asyncFuture.h"
 #include "asyncTask.h"
 #include "asyncTaskChain.h"
 #include "asyncTaskManager.h"
@@ -31,6 +32,8 @@ NotifyCategoryDef(event, "");
 NotifyCategoryDef(task, "");
 
 ConfigureFn(config_event) {
+  AsyncFuture::init_type();
+  AsyncGatheringFuture::init_type();
   AsyncTask::init_type();
   AsyncTaskChain::init_type();
   AsyncTaskManager::init_type();

+ 32 - 0
panda/src/event/eventHandler.cxx

@@ -27,6 +27,26 @@ EventHandler::
 EventHandler(EventQueue *ev_queue) : _queue(*ev_queue) {
 }
 
+/**
+ * Returns a pending future that will be marked as done when the event is next
+ * fired.
+ */
+AsyncFuture *EventHandler::
+get_future(const string &event_name) {
+  Futures::iterator fi;
+  fi = _futures.find(event_name);
+
+  // If we already have a future, but someone cancelled it, we need to create
+  // a new future instead.
+  if (fi != _futures.end() && !fi->second->cancelled()) {
+    return fi->second;
+  } else {
+    AsyncFuture *fut = new AsyncFuture;
+    _futures[event_name] = fut;
+    return fut;
+  }
+}
+
 /**
  * The main processing loop of the EventHandler.  This function must be called
  * periodically to service events.  Walks through each pending event and calls
@@ -81,6 +101,18 @@ dispatch_event(const Event *event) {
       ((*cfi).first)(event, (*cfi).second);
     }
   }
+
+  // Finally, check for futures that need to be triggered.
+  Futures::iterator fi;
+  fi = _futures.find(event->get_name());
+
+  if (fi != _futures.end()) {
+    AsyncFuture *fut = (*fi).second;
+    if (!fut->done()) {
+      fut->set_result((TypedReferenceCount *)event);
+    }
+    _futures.erase(fi);
+  }
 }
 
 

+ 7 - 1
panda/src/event/eventHandler.h

@@ -18,6 +18,7 @@
 
 #include "event.h"
 #include "pt_Event.h"
+#include "asyncFuture.h"
 
 #include "pset.h"
 #include "pmap.h"
@@ -41,10 +42,13 @@ public:
 
 PUBLISHED:
   explicit EventHandler(EventQueue *ev_queue);
+  ~EventHandler() {}
+
+  AsyncFuture *get_future(const string &event_name);
 
   void process_events();
 
-  virtual void dispatch_event(const Event *);
+  virtual void dispatch_event(const Event *event);
 
   void write(ostream &out) const;
 
@@ -74,9 +78,11 @@ protected:
   typedef pair<EventCallbackFunction*, void*> CallbackFunction;
   typedef pset<CallbackFunction> CallbackFunctions;
   typedef pmap<string, CallbackFunctions> CallbackHooks;
+  typedef pmap<string, PT(AsyncFuture)> Futures;
 
   Hooks _hooks;
   CallbackHooks _cbhooks;
+  Futures _futures;
   EventQueue &_queue;
 
   static EventHandler *_global_event_handler;

+ 0 - 7
panda/src/event/eventParameter.I

@@ -11,13 +11,6 @@
  * @date 1999-02-08
  */
 
-/**
- * Defines an EventParameter that stores nothing: the "empty" parameter.
- */
-INLINE EventParameter::
-EventParameter() {
-}
-
 /**
  * Defines an EventParameter that stores a pointer to any kind of
  * TypedWritableReferenceCount object.  This is the most general constructor.

+ 2 - 1
panda/src/event/eventParameter.h

@@ -34,7 +34,8 @@
  */
 class EXPCL_PANDA_EVENT EventParameter {
 PUBLISHED:
-  INLINE EventParameter();
+  INLINE EventParameter() DEFAULT_CTOR;
+  INLINE EventParameter(nullptr_t) {};
   INLINE EventParameter(const TypedWritableReferenceCount *ptr);
   INLINE EventParameter(const TypedReferenceCount *ptr);
   INLINE EventParameter(int value);

+ 1 - 0
panda/src/event/p3event_composite1.cxx

@@ -1,3 +1,4 @@
+#include "asyncFuture.cxx"
 #include "asyncTask.cxx"
 #include "asyncTaskChain.cxx"
 #include "asyncTaskCollection.cxx"

+ 3 - 8
panda/src/event/pythonTask.I

@@ -45,17 +45,12 @@ get_owner() const {
  */
 INLINE void PythonTask::
 set_result(PyObject *result) {
+  // Note that we don't call notify_done() here since the done status will be
+  // automatically notified upon the task's completion.
   nassertv(is_alive());
+  nassertv(!done());
   nassertv(_exception == nullptr);
   Py_INCREF(result);
   Py_XDECREF(_exc_value);
   _exc_value = result;
 }
-
-/**
- * Same as __await__, for backward compatibility with the old coroutine way.
- */
-INLINE PyObject *PythonTask::
-__iter__(PyObject *self) {
-  return __await__(self);
-}

+ 54 - 84
panda/src/event/pythonTask.cxx

@@ -25,7 +25,7 @@ TypeHandle PythonTask::_type_handle;
 
 #ifndef CPPPARSER
 extern struct Dtool_PyTypedObject Dtool_TypedReferenceCount;
-extern struct Dtool_PyTypedObject Dtool_AsyncTask;
+extern struct Dtool_PyTypedObject Dtool_AsyncFuture;
 extern struct Dtool_PyTypedObject Dtool_PythonTask;
 #endif
 
@@ -45,6 +45,7 @@ PythonTask(PyObject *func_or_coro, const string &name) :
   _exc_traceback(nullptr),
   _generator(nullptr),
   _future_done(nullptr),
+  _ignore_return(false),
   _retrieved_exception(false) {
 
   nassertv(func_or_coro != nullptr);
@@ -169,9 +170,7 @@ get_args() {
     }
 
     this->ref();
-    PyObject *self =
-      DTool_CreatePyInstanceTyped(this, Dtool_TypedReferenceCount,
-                                  true, false, get_type_index());
+    PyObject *self = DTool_CreatePyInstance(this, Dtool_PythonTask, true, false);
     PyTuple_SET_ITEM(with_task, num_args, self);
     return with_task;
 
@@ -238,8 +237,8 @@ set_owner(PyObject *owner) {
  * exception occurred within this task, it is raised instead.
  */
 PyObject *PythonTask::
-result() const {
-  nassertr(!is_alive(), nullptr);
+get_result() const {
+  nassertr(done(), nullptr);
 
   if (_exception == nullptr) {
     // The result of the call is stored in _exc_value.
@@ -273,49 +272,6 @@ exception() const {
   }
 }*/
 
-/**
- * Returns an iterator that continuously yields an awaitable until the task
- * has finished.
- */
-PyObject *PythonTask::
-__await__(PyObject *self) {
-  Dtool_GeneratorWrapper *gen;
-  gen = (Dtool_GeneratorWrapper *)PyType_GenericAlloc(&Dtool_GeneratorWrapper_Type, 0);
-  if (gen != nullptr) {
-    Py_INCREF(self);
-    gen->_base._self = self;
-    gen->_iternext_func = &gen_next;
-  }
-  return (PyObject *)gen;
-}
-
-/**
- * Yields continuously until a task has finished.
- */
-PyObject *PythonTask::
-gen_next(PyObject *self) {
-  const PythonTask *task = nullptr;
-  if (!Dtool_Call_ExtractThisPointer(self, Dtool_PythonTask, (void **)&task)) {
-    return nullptr;
-  }
-
-  if (task->is_alive()) {
-    Py_INCREF(self);
-    return self;
-  } else if (task->_exception != nullptr) {
-    task->_retrieved_exception = true;
-    Py_INCREF(task->_exception);
-    Py_INCREF(task->_exc_value);
-    Py_INCREF(task->_exc_traceback);
-    PyErr_Restore(task->_exception, task->_exc_value, task->_exc_traceback);
-    return nullptr;
-  } else {
-    // The result of the call is stored in _exc_value.
-    PyErr_SetObject(PyExc_StopIteration, task->_exc_value);
-    return nullptr;
-  }
-}
-
 /**
  * Maps from an expression like "task.attr_name = v". This is customized here
  * so we can support some traditional task interfaces that supported directly
@@ -620,36 +576,39 @@ do_python_task() {
           }
         }
 
-        // Tell the task chain we want to kill ourselves.  It doesn't really
-        // matter what we return if we set S_servicing_removed.  If we don't
-        // set it, however, it will think this was a clean exit.
-        _manager->_lock.acquire();
-        _state = S_servicing_removed;
-        _manager->_lock.release();
-        return DS_interrupt;
+        // Tell the task chain we want to kill ourselves.  We indicate this is
+        // a "clean exit" because we still want to run the done callbacks on
+        // exception.
+        return DS_done;
       }
 
-    } else if (DtoolCanThisBeAPandaInstance(result)) {
-      // We are waiting for a task to finish.
-      void *ptr = ((Dtool_PyInstDef *)result)->_My_Type->_Dtool_UpcastInterface(result, &Dtool_AsyncTask);
-      if (ptr != nullptr) {
+    } else if (DtoolInstance_Check(result)) {
+      // We are waiting for an AsyncFuture (eg. other task) to finish.
+      AsyncFuture *fut = (AsyncFuture *)DtoolInstance_UPCAST(result, Dtool_AsyncFuture);
+      if (fut != nullptr) {
         // Suspend execution of this task until this other task has completed.
-        AsyncTask *task = (AsyncTask *)ptr;
-        AsyncTaskManager *manager = task->_manager;
-        nassertr(manager != nullptr, DS_interrupt);
-        nassertr(manager == _manager, DS_interrupt);
-        manager->_lock.acquire();
-        if (task != (AsyncTask *)this) {
-          if (task->is_alive()) {
+        if (fut != (AsyncFuture *)this && !fut->done()) {
+          if (fut->is_task()) {
+            // This is actually a task, do we need to schedule it with the
+            // manager?  This allows doing something like
+            //   await Task.pause(1.0)
+            // directly instead of having to do:
+            //   await taskMgr.add(Task.pause(1.0))
+            AsyncTask *task = (AsyncTask *)fut;
+            _manager->add(task);
+          }
+          if (fut->add_waiting_task(this)) {
             if (task_cat.is_debug()) {
               task_cat.debug()
-                << *this << " is now awaiting <" << *task << ">.\n";
+                << *this << " is now awaiting <" << *fut << ">.\n";
             }
-            task->_waiting_tasks.push_back(this);
           } else {
             // The task is already done.  Continue at next opportunity.
+            if (task_cat.is_debug()) {
+              task_cat.debug()
+                << *this << " would await <" << *fut << ">, were it not already done.\n";
+            }
             Py_DECREF(result);
-            manager->_lock.release();
             return DS_cont;
           }
         } else {
@@ -658,14 +617,12 @@ do_python_task() {
           task_cat.error()
             << *this << " cannot await itself\n";
         }
-        task->_manager->_lock.release();
         Py_DECREF(result);
         return DS_await;
       }
-
     } else {
-      // We are waiting for a future to finish.  We currently implement this
-      // by simply checking every frame whether the future is done.
+      // We are waiting for a non-Panda future to finish.  We currently
+      // implement this by checking every frame whether the future is done.
       PyObject *check = PyObject_GetAttrString(result, "_asyncio_future_blocking");
       if (check != nullptr && check != Py_None) {
         Py_DECREF(check);
@@ -680,7 +637,7 @@ do_python_task() {
         if (task_cat.is_debug()) {
           PyObject *str = PyObject_ASCII(result);
           task_cat.debug()
-            << *this << " is now awaiting " << PyUnicode_AsUTF8(str) << ".\n";
+            << *this << " is now polling " << PyUnicode_AsUTF8(str) << ".done()\n";
           Py_DECREF(str);
         }
 #endif
@@ -707,7 +664,7 @@ do_python_task() {
     return DS_interrupt;
   }
 
-  if (result == Py_None) {
+  if (result == Py_None || _ignore_return) {
     Py_DECREF(result);
     return DS_done;
   }
@@ -746,6 +703,24 @@ do_python_task() {
     }
   }
 
+  // This is unfortunate, but some are returning task.done, which nowadays
+  // conflicts with the AsyncFuture method.  Check if that is being returned.
+  PyMethodDef *meth = nullptr;
+  if (PyCFunction_Check(result)) {
+    meth = ((PyCFunctionObject *)result)->m_ml;
+#if PY_MAJOR_VERSION >= 3
+  } else if (Py_TYPE(result) == &PyMethodDescr_Type) {
+#else
+  } else if (strcmp(Py_TYPE(result)->tp_name, "method_descriptor") == 0) {
+#endif
+    meth = ((PyMethodDescrObject *)result)->d_method;
+  }
+
+  if (meth != nullptr && strcmp(meth->ml_name, "done") == 0) {
+    Py_DECREF(result);
+    return DS_done;
+  }
+
   ostringstream strm;
 #if PY_MAJOR_VERSION >= 3
   PyObject *str = PyObject_ASCII(result);
@@ -886,15 +861,10 @@ void PythonTask::
 call_function(PyObject *function) {
   if (function != Py_None) {
     this->ref();
-    PyObject *self =
-      DTool_CreatePyInstanceTyped(this, Dtool_TypedReferenceCount,
-                                  true, false, get_type_index());
-    PyObject *args = Py_BuildValue("(O)", self);
-    Py_DECREF(self);
-
-    PyObject *result = PyObject_CallObject(function, args);
+    PyObject *self = DTool_CreatePyInstance(this, Dtool_PythonTask, true, false);
+    PyObject *result = PyObject_CallFunctionObjArgs(function, self, nullptr);
     Py_XDECREF(result);
-    Py_DECREF(args);
+    Py_DECREF(self);
   }
 }
 

+ 11 - 7
panda/src/event/pythonTask.h

@@ -20,12 +20,13 @@
 
 #ifdef HAVE_PYTHON
 #include "py_panda.h"
+#include "extension.h"
 
 /**
  * This class exists to allow association of a Python function or coroutine
  * with the AsyncTaskManager.
  */
-class PythonTask : public AsyncTask {
+class PythonTask FINAL : public AsyncTask {
 PUBLISHED:
   PythonTask(PyObject *function = Py_None, const string &name = string());
   virtual ~PythonTask();
@@ -44,12 +45,14 @@ PUBLISHED:
   INLINE PyObject *get_owner() const;
 
   INLINE void set_result(PyObject *result);
-  PyObject *result() const;
-  //PyObject *exception() const;
 
-  static PyObject *__await__(PyObject *self);
-  INLINE static PyObject *__iter__(PyObject *self);
+public:
+  // This is exposed only for the result() function in asyncFuture_ext.cxx
+  // to use, which is why it is not published.
+  PyObject *get_result() const;
+  //PyObject *exception() const;
 
+PUBLISHED:
   int __setattr__(PyObject *self, PyObject *attr, PyObject *v);
   int __delattr__(PyObject *self, PyObject *attr);
   PyObject *__getattr__(PyObject *attr) const;
@@ -101,8 +104,6 @@ protected:
   virtual void upon_death(AsyncTaskManager *manager, bool clean_exit);
 
 private:
-  static PyObject *gen_next(PyObject *self);
-
   void register_to_owner();
   void unregister_from_owner();
   void call_owner_method(const char *method_name);
@@ -122,9 +123,12 @@ private:
   PyObject *_future_done;
 
   bool _append_task;
+  bool _ignore_return;
   bool _registered_to_owner;
   mutable bool _retrieved_exception;
 
+  friend class Extension<AsyncFuture>;
+
 public:
   static TypeHandle get_class_type() {
     return _type_handle;

+ 22 - 19
panda/src/express/pointerToArray_ext.I

@@ -97,7 +97,7 @@ __init__(PyObject *self, PyObject *source) {
 
   // Now construct the internal list by copying the elements one-at-a-time
   // from Python.
-  PyObject *dict = ((Dtool_PyInstDef *)self)->_My_Type->_PyType.tp_dict;
+  PyObject *dict = DtoolInstance_TYPE(self)->_PyType.tp_dict;
   PyObject *push_back = PyDict_GetItemString(dict, "push_back");
   if (push_back == NULL) {
     PyErr_BadArgument();
@@ -105,7 +105,7 @@ __init__(PyObject *self, PyObject *source) {
   }
 
   // We need to initialize the this pointer before we can call push_back.
-  ((Dtool_PyInstDef *)self)->_ptr_to_object = (void *)this->_this;
+  DtoolInstance_INIT_PTR(self, this->_this);
 
   Py_ssize_t size = PySequence_Size(source);
   this->_this->reserve(size);
@@ -203,34 +203,37 @@ set_data(PyObject *data) {
     }
 
     PyBuffer_Release(&view);
-  } else {
-    Dtool_Raise_TypeError("PointerToArray.set_data() requires a buffer object");
+    return;
   }
-#else
-  // In Python 2.5 we didn't have the new buffer protocol, only str.
-  if (PyString_CheckExact(data)) {
-    int size = PyString_Size(data);
-    if (size % sizeof(Element) != 0) {
+#endif
+
+  // In Python 2, there was also an older buffer protocol, supported by eg.
+  // str and array objects.
+#if PY_MAJOR_VERSION < 3
+  // The old, deprecated buffer interface, as used by eg. the array module.
+  const void *buffer;
+  Py_ssize_t buffer_len;
+  if (!PyUnicode_CheckExact(data) &&
+      PyObject_AsReadBuffer(data, &buffer, &buffer_len) == 0) {
+    if (buffer_len % sizeof(Element) != 0) {
       PyErr_Format(PyExc_ValueError,
-                   "str object is not a multiple of %zu bytes",
+                   "byte buffer is not a multiple of %zu bytes",
                    sizeof(Element));
       return;
     }
 
-    int num_elements = size / sizeof(Element);
-    this->_this->insert(this->_this->begin(), num_elements, Element());
-
-    // Hope there aren't any constructors or destructors involved here.
-    if (size != 0) {
-      const char *ptr = PyString_AsString(data);
-      memcpy(this->_this->p(), ptr, size);
+    if (buffer_len > 0) {
+      this->_this->resize(buffer_len / sizeof(Element));
+      memcpy(this->_this->p(), buffer, buffer_len);
     } else {
       this->_this->clear();
     }
-  } else {
-    Dtool_Raise_TypeError("PointerToArray.set_data() requires a str");
+
+    return;
   }
 #endif
+
+  Dtool_Raise_TypeError("PointerToArray.set_data() requires a buffer object");
 }
 
 /**

+ 31 - 14
panda/src/glstuff/glGraphicsBuffer_src.cxx

@@ -213,12 +213,20 @@ begin_frame(FrameMode mode, Thread *current_thread) {
     return false;
   }
 
-  if (!_host->begin_frame(FM_parasite, current_thread)) {
-    if (GLCAT.is_debug()) {
-      GLCAT.debug()
-        << get_name() << "'s host is not ready\n";
+  if (_host != nullptr) {
+    if (!_host->begin_frame(FM_parasite, current_thread)) {
+      if (GLCAT.is_debug()) {
+        GLCAT.debug()
+          << get_name() << "'s host is not ready\n";
+      }
+      return false;
+    }
+  } else {
+    // We don't have a host window, which is possible for CocoaGraphicsBuffer.
+    _gsg->set_current_properties(&get_fb_properties());
+    if (!_gsg->begin_frame(current_thread)) {
+      return false;
     }
-    return false;
   }
 
   // Figure out the desired size of the  buffer.
@@ -235,7 +243,7 @@ begin_frame(FrameMode mode, Thread *current_thread) {
       }
     }
     if (_creation_flags & GraphicsPipe::BF_size_track_host) {
-      if (_host->get_size() != _size) {
+      if (_host != nullptr && _host->get_size() != _size) {
         // We also need to rebuild if we need to change size.
         _needs_rebuild = true;
       }
@@ -356,7 +364,7 @@ rebuild_bitplanes() {
 
   // Calculate bitplane size.  This can be larger than the buffer.
   if (_creation_flags & GraphicsPipe::BF_size_track_host) {
-    if (_host->get_size() != _size) {
+    if (_host != nullptr && _host->get_size() != _size) {
       set_size_and_recalc(_host->get_x_size(),
                           _host->get_y_size());
     }
@@ -1253,7 +1261,11 @@ end_frame(FrameMode mode, Thread *current_thread) {
     generate_mipmaps();
   }
 
-  _host->end_frame(FM_parasite, current_thread);
+  if (_host != nullptr) {
+    _host->end_frame(FM_parasite, current_thread);
+  } else {
+    glgsg->end_frame(current_thread);
+  }
 
   if (mode == FM_render) {
     trigger_flip();
@@ -1315,8 +1327,11 @@ bool CLP(GraphicsBuffer)::
 open_buffer() {
   report_my_gl_errors();
 
-  // Double check that we have a host
-  nassertr(_host != 0, false);
+  // Double check that we have a valid gsg
+  nassertr(_gsg != nullptr, false);
+  if (!_gsg->is_valid()) {
+    return false;
+  }
 
   // Count total color buffers.
   int totalcolor =
@@ -1491,8 +1506,10 @@ open_buffer() {
   _fb_properties.set_back_buffers(0);
   _fb_properties.set_indexed_color(0);
   _fb_properties.set_rgb_color(1);
-  _fb_properties.set_force_hardware(_host->get_fb_properties().get_force_hardware());
-  _fb_properties.set_force_software(_host->get_fb_properties().get_force_software());
+  if (_host != nullptr) {
+    _fb_properties.set_force_hardware(_host->get_fb_properties().get_force_hardware());
+    _fb_properties.set_force_software(_host->get_fb_properties().get_force_software());
+  }
 
   _is_valid = true;
   _needs_rebuild = true;
@@ -1508,7 +1525,7 @@ open_buffer() {
  */
 GraphicsOutput *CLP(GraphicsBuffer)::
 get_host() {
-  return _host;
+  return (_host != nullptr) ? _host : this;
 }
 
 /**
@@ -1699,7 +1716,7 @@ report_my_errors(int line, const char *file) {
  */
 void CLP(GraphicsBuffer)::
 check_host_valid() {
-  if ((_host == 0)||(!_host->is_valid())) {
+  if (_host != nullptr && !_host->is_valid()) {
     _rb_data_size_bytes = 0;
     if (_rb_context != NULL) {
       // We must delete this object first, because when the GSG destructs, so

+ 0 - 2
panda/src/glstuff/glGraphicsBuffer_src.h

@@ -86,8 +86,6 @@ protected:
 
   void report_my_errors(int line, const char *file);
 
-private:
-
   void bind_slot(int layer, bool rb_resize, Texture **attach,
                  RenderTexturePlane plane, GLenum attachpoint);
   void bind_slot_multisample(bool rb_resize, Texture **attach,

+ 21 - 4
panda/src/glstuff/glGraphicsStateGuardian_src.cxx

@@ -155,7 +155,11 @@ null_glBlendColor(GLclampf, GLclampf, GLclampf, GLclampf) {
 // drawing GUIs and such.
 static const string default_vshader =
 #ifndef OPENGLES
+#ifdef __APPLE__ // Apple's GL 3.2 contexts require at least GLSL 1.50.
+  "#version 150\n"
+#else
   "#version 130\n"
+#endif
   "in vec4 p3d_Vertex;\n"
   "in vec4 p3d_Color;\n"
   "in vec2 p3d_MultiTexCoord0;\n"
@@ -179,7 +183,11 @@ static const string default_vshader =
 
 static const string default_fshader =
 #ifndef OPENGLES
+#ifdef __APPLE__  // Apple's GL 3.2 contexts require at least GLSL 1.50.
+  "#version 150\n"
+#else
   "#version 130\n"
+#endif
   "in vec2 texcoord;\n"
   "in vec4 color;\n"
   "out vec4 p3d_FragColor;\n"
@@ -6455,6 +6463,15 @@ framebuffer_copy_to_ram(Texture *tex, int view, int z,
     }
     break;
 
+  case Texture::F_depth_component16:
+    component_type = Texture::T_unsigned_short;
+    break;
+
+  case Texture::F_depth_component24:
+  case Texture::F_depth_component32:
+    component_type = Texture::T_float;
+    break;
+
   default:
     if (_current_properties->get_srgb_color()) {
       if (_current_properties->get_alpha_bits()) {
@@ -12314,20 +12331,20 @@ upload_texture_image(CLP(TextureContext) *gtc, bool needs_reload,
               if (_supports_clear_texture) {
                 // We can do that with the convenient glClearTexImage
                 // function.
-                string clear_data = tex->get_clear_data();
+                vector_uchar clear_data = tex->get_clear_data();
 
                 _glClearTexImage(gtc->_index, n - mipmap_bias, external_format,
-                                 component_type, (void *)clear_data.data());
+                                 component_type, (void *)&clear_data[0]);
                 continue;
               }
             } else {
               if (_supports_clear_buffer) {
                 // For buffer textures we need to clear the underlying
                 // storage.
-                string clear_data = tex->get_clear_data();
+                vector_uchar clear_data = tex->get_clear_data();
 
                 _glClearBufferData(GL_TEXTURE_BUFFER, internal_format, external_format,
-                                   component_type, (const void *)clear_data.data());
+                                   component_type, (const void *)&clear_data[0]);
                 continue;
               }
             }

+ 4 - 3
panda/src/gobj/animateVerticesRequest.I

@@ -16,15 +16,16 @@
  */
 INLINE AnimateVerticesRequest::
 AnimateVerticesRequest(GeomVertexData *geom_vertex_data) :
-  _geom_vertex_data(geom_vertex_data),
-  _is_ready(false)
+  _geom_vertex_data(geom_vertex_data)
 {
 }
 
 /**
  * Returns true if this request has completed, false if it is still pending.
+ * Equivalent to `req.done() and not req.cancelled()`.
+ * @see done()
  */
 INLINE bool AnimateVerticesRequest::
 is_ready() const {
-  return _is_ready;
+  return (FutureState)AtomicAdjust::get(_future_state) == FS_finished;
 }

+ 0 - 1
panda/src/gobj/animateVerticesRequest.cxx

@@ -26,7 +26,6 @@ do_task() {
   // There is no need to store or return a result.  The GeomVertexData caches
   // the result and it will be used later in the rendering process.
   _geom_vertex_data->animate_vertices(true, current_thread);
-  _is_ready = true;
 
   // Don't continue the task; we're done.
   return AsyncTask::DS_done;

+ 1 - 2
panda/src/gobj/animateVerticesRequest.h

@@ -40,11 +40,10 @@ PUBLISHED:
   INLINE bool is_ready() const;
 
 protected:
-    virtual AsyncTask::DoneStatus do_task();
+  virtual AsyncTask::DoneStatus do_task();
 
 private:
   PT(GeomVertexData) _geom_vertex_data;
-  bool _is_ready;
 
 public:
   static TypeHandle get_class_type() {

+ 1 - 0
panda/src/gobj/config_gobj.cxx

@@ -572,6 +572,7 @@ ConfigureFn(config_gobj) {
   ParamTextureImage::init_type();
   ParamTextureSampler::init_type();
   PerspectiveLens::init_type();
+  PreparedGraphicsObjects::EnqueuedObject::init_type();
   QueryContext::init_type();
   SamplerContext::init_type();
   SamplerState::init_type();

+ 147 - 7
panda/src/gobj/preparedGraphicsObjects.cxx

@@ -27,6 +27,8 @@
 #include "config_gobj.h"
 #include "throw_event.h"
 
+TypeHandle PreparedGraphicsObjects::EnqueuedObject::_type_handle;
+
 int PreparedGraphicsObjects::_name_index = 0;
 
 /**
@@ -191,7 +193,25 @@ void PreparedGraphicsObjects::
 enqueue_texture(Texture *tex) {
   ReMutexHolder holder(_lock);
 
-  _enqueued_textures.insert(tex);
+  _enqueued_textures.insert(EnqueuedTextures::value_type(tex, nullptr));
+}
+
+/**
+ * Like enqueue_texture, but returns an AsyncFuture that can be used to query
+ * the status of the texture's preparation.
+ */
+PT(PreparedGraphicsObjects::EnqueuedObject) PreparedGraphicsObjects::
+enqueue_texture_future(Texture *tex) {
+  ReMutexHolder holder(_lock);
+
+  pair<EnqueuedTextures::iterator, bool> result =
+    _enqueued_textures.insert(EnqueuedTextures::value_type(tex, nullptr));
+  if (result.first->second == nullptr) {
+    result.first->second = new EnqueuedObject(this, tex);
+  }
+  PT(EnqueuedObject) fut = result.first->second;
+  nassertr(!fut->cancelled(), fut)
+  return fut;
 }
 
 /**
@@ -220,6 +240,9 @@ dequeue_texture(Texture *tex) {
 
   EnqueuedTextures::iterator qi = _enqueued_textures.find(tex);
   if (qi != _enqueued_textures.end()) {
+    if (qi->second != nullptr) {
+      qi->second->notify_removed();
+    }
     _enqueued_textures.erase(qi);
     return true;
   }
@@ -291,6 +314,17 @@ release_all_textures() {
   }
 
   _prepared_textures.clear();
+
+  // Mark any futures as cancelled.
+  EnqueuedTextures::iterator qti;
+  for (qti = _enqueued_textures.begin();
+       qti != _enqueued_textures.end();
+       ++qti) {
+    if (qti->second != nullptr) {
+      qti->second->notify_removed();
+    }
+  }
+
   _enqueued_textures.clear();
 
   return num_textures;
@@ -665,10 +699,28 @@ prepare_geom_now(Geom *geom, GraphicsStateGuardianBase *gsg) {
  * when the GSG is next ready to do this (presumably at the next frame).
  */
 void PreparedGraphicsObjects::
-enqueue_shader(Shader *se) {
+enqueue_shader(Shader *shader) {
   ReMutexHolder holder(_lock);
 
-  _enqueued_shaders.insert(se);
+  _enqueued_shaders.insert(EnqueuedShaders::value_type(shader, nullptr));
+}
+
+/**
+ * Like enqueue_shader, but returns an AsyncFuture that can be used to query
+ * the status of the shader's preparation.
+ */
+PT(PreparedGraphicsObjects::EnqueuedObject) PreparedGraphicsObjects::
+enqueue_shader_future(Shader *shader) {
+  ReMutexHolder holder(_lock);
+
+  pair<EnqueuedShaders::iterator, bool> result =
+    _enqueued_shaders.insert(EnqueuedShaders::value_type(shader, nullptr));
+  if (result.first->second == nullptr) {
+    result.first->second = new EnqueuedObject(this, shader);
+  }
+  PT(EnqueuedObject) fut = result.first->second;
+  nassertr(!fut->cancelled(), fut)
+  return fut;
 }
 
 /**
@@ -697,6 +749,9 @@ dequeue_shader(Shader *se) {
 
   EnqueuedShaders::iterator qi = _enqueued_shaders.find(se);
   if (qi != _enqueued_shaders.end()) {
+    if (qi->second != nullptr) {
+      qi->second->notify_removed();
+    }
     _enqueued_shaders.erase(qi);
     return true;
   }
@@ -759,6 +814,17 @@ release_all_shaders() {
   }
 
   _prepared_shaders.clear();
+
+  // Mark any futures as cancelled.
+  EnqueuedShaders::iterator qsi;
+  for (qsi = _enqueued_shaders.begin();
+       qsi != _enqueued_shaders.end();
+       ++qsi) {
+    if (qsi->second != nullptr) {
+      qsi->second->notify_removed();
+    }
+  }
+
   _enqueued_shaders.clear();
 
   return num_shaders;
@@ -1358,6 +1424,73 @@ prepare_shader_buffer_now(ShaderBuffer *data, GraphicsStateGuardianBase *gsg) {
   return bc;
 }
 
+/**
+ * Creates a new future for the given object.
+ */
+PreparedGraphicsObjects::EnqueuedObject::
+EnqueuedObject(PreparedGraphicsObjects *pgo, TypedWritableReferenceCount *object) :
+  _pgo(pgo),
+  _object(object) {
+}
+
+/**
+ * Indicates that the preparation request is done.
+ */
+void PreparedGraphicsObjects::EnqueuedObject::
+set_result(SavedContext *context) {
+  nassertv(!done());
+  AsyncFuture::set_result(context);
+  _pgo = nullptr;
+}
+
+/**
+ * Called by PreparedGraphicsObjects to indicate that the preparation request
+ * has been cancelled.
+ */
+void PreparedGraphicsObjects::EnqueuedObject::
+notify_removed() {
+  _pgo = nullptr;
+  nassertv_always(AsyncFuture::cancel());
+}
+
+/**
+ * Cancels the pending preparation request.  Has no effect if the preparation
+ * is already complete or was already cancelled.
+ */
+bool PreparedGraphicsObjects::EnqueuedObject::
+cancel() {
+  PreparedGraphicsObjects *pgo = _pgo;
+  if (_object == nullptr || pgo == nullptr) {
+    nassertr(done(), false);
+    return false;
+  }
+
+  // We don't upcall here, because the dequeue function will end up calling
+  // notify_removed().
+  _result = nullptr;
+  _pgo = nullptr;
+
+  if (_object->is_of_type(Texture::get_class_type())) {
+    return pgo->dequeue_texture((Texture *)_object.p());
+
+  } else if (_object->is_of_type(Geom::get_class_type())) {
+    return pgo->dequeue_geom((Geom *)_object.p());
+
+  } else if (_object->is_of_type(Shader::get_class_type())) {
+    return pgo->dequeue_shader((Shader *)_object.p());
+
+  } else if (_object->is_of_type(GeomVertexArrayData::get_class_type())) {
+    return pgo->dequeue_vertex_buffer((GeomVertexArrayData *)_object.p());
+
+  } else if (_object->is_of_type(GeomPrimitive::get_class_type())) {
+    return pgo->dequeue_index_buffer((GeomPrimitive *)_object.p());
+
+  } else if (_object->is_of_type(ShaderBuffer::get_class_type())) {
+    return pgo->dequeue_shader_buffer((ShaderBuffer *)_object.p());
+  }
+  return false;
+}
+
 /**
  * This is called by the GraphicsStateGuardian to indicate that it is about to
  * begin processing of the frame.
@@ -1446,11 +1579,15 @@ begin_frame(GraphicsStateGuardianBase *gsg, Thread *current_thread) {
   for (qti = _enqueued_textures.begin();
        qti != _enqueued_textures.end();
        ++qti) {
-    Texture *tex = (*qti);
+    Texture *tex = qti->first;
+    TextureContext *first_tc = nullptr;
     for (int view = 0; view < tex->get_num_views(); ++view) {
       TextureContext *tc = tex->prepare_now(view, this, gsg);
-      if (tc != (TextureContext *)NULL) {
+      if (tc != nullptr) {
         gsg->update_texture(tc, true);
+        if (view == 0 && qti->second != nullptr) {
+          qti->second->set_result(tc);
+        }
       }
     }
   }
@@ -1481,8 +1618,11 @@ begin_frame(GraphicsStateGuardianBase *gsg, Thread *current_thread) {
   for (qsi = _enqueued_shaders.begin();
        qsi != _enqueued_shaders.end();
        ++qsi) {
-    Shader *shader = (*qsi);
-    shader->prepare_now(this, gsg);
+    Shader *shader = qsi->first;
+    ShaderContext *sc = shader->prepare_now(this, gsg);
+    if (qti->second != nullptr) {
+      qti->second->set_result(sc);
+    }
   }
 
   _enqueued_shaders.clear();

+ 54 - 2
panda/src/gobj/preparedGraphicsObjects.h

@@ -29,6 +29,7 @@
 #include "reMutex.h"
 #include "bufferResidencyTracker.h"
 #include "adaptiveLru.h"
+#include "asyncFuture.h"
 
 class TextureContext;
 class SamplerContext;
@@ -38,6 +39,7 @@ class VertexBufferContext;
 class IndexBufferContext;
 class BufferContext;
 class GraphicsStateGuardianBase;
+class SavedContext;
 
 /**
  * A table of objects that are saved within the graphics context for reference
@@ -158,6 +160,56 @@ PUBLISHED:
                             GraphicsStateGuardianBase *gsg);
 
 public:
+  /**
+   * This is a handle to an enqueued object, from which the result can be
+   * obtained upon completion.
+   */
+  class EXPCL_PANDA_GOBJ EnqueuedObject FINAL : public AsyncFuture {
+  public:
+    EnqueuedObject(PreparedGraphicsObjects *pgo, TypedWritableReferenceCount *object);
+
+    TypedWritableReferenceCount *get_object() { return _object.p(); }
+    SavedContext *get_result() { return (SavedContext *)AsyncFuture::get_result(); }
+    void set_result(SavedContext *result);
+
+    void notify_removed();
+    virtual bool cancel() FINAL;
+
+  PUBLISHED:
+    MAKE_PROPERTY(object, get_object);
+
+  private:
+    PreparedGraphicsObjects *_pgo;
+    PT(TypedWritableReferenceCount) const _object;
+
+  public:
+    static TypeHandle get_class_type() {
+      return _type_handle;
+    }
+    static void init_type() {
+      AsyncFuture::init_type();
+      register_type(_type_handle, "EnqueuedObject",
+                    AsyncFuture::get_class_type());
+    }
+    virtual TypeHandle get_type() const {
+      return get_class_type();
+    }
+    virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+  private:
+    static TypeHandle _type_handle;
+  };
+
+  // These are variations of enqueue_xxx that also return a future.  They are
+  // used to implement texture->prepare(), etc.  They are only marked public
+  // so we don't have to define a whole bunch of friend classes.
+  PT(EnqueuedObject) enqueue_texture_future(Texture *tex);
+  //PT(EnqueuedObject) enqueue_geom_future(Geom *geom);
+  PT(EnqueuedObject) enqueue_shader_future(Shader *shader);
+  //PT(EnqueuedObject) enqueue_vertex_buffer_future(GeomVertexArrayData *data);
+  //PT(EnqueuedObject) enqueue_index_buffer_future(GeomPrimitive *data);
+  //PT(EnqueuedObject) enqueue_shader_buffer_future(ShaderBuffer *data);
+
   void begin_frame(GraphicsStateGuardianBase *gsg,
                    Thread *current_thread);
   void end_frame(Thread *current_thread);
@@ -167,11 +219,11 @@ private:
 
 private:
   typedef phash_set<TextureContext *, pointer_hash> Textures;
-  typedef phash_set< PT(Texture) > EnqueuedTextures;
+  typedef phash_map< PT(Texture), PT(EnqueuedObject) > EnqueuedTextures;
   typedef phash_set<GeomContext *, pointer_hash> Geoms;
   typedef phash_set< PT(Geom) > EnqueuedGeoms;
   typedef phash_set<ShaderContext *, pointer_hash> Shaders;
-  typedef phash_set< PT(Shader) > EnqueuedShaders;
+  typedef phash_map< PT(Shader), PT(EnqueuedObject) > EnqueuedShaders;
   typedef phash_set<BufferContext *, pointer_hash> Buffers;
   typedef phash_set< PT(GeomVertexArrayData) > EnqueuedVertexBuffers;
   typedef phash_set< PT(GeomPrimitive) > EnqueuedIndexBuffers;

+ 3 - 2
panda/src/gobj/shader.cxx

@@ -3407,9 +3407,10 @@ parse_eof() {
  * Use this function instead of prepare_now() to preload textures from a user
  * interface standpoint.
  */
-void Shader::
+PT(AsyncFuture) Shader::
 prepare(PreparedGraphicsObjects *prepared_objects) {
-  prepared_objects->enqueue_shader(this);
+  PT(PreparedGraphicsObjects::EnqueuedObject) obj = prepared_objects->enqueue_shader_future(this);
+  return obj.p();
 }
 
 /**

+ 2 - 1
panda/src/gobj/shader.h

@@ -32,6 +32,7 @@
 #include "pta_LVecBase3.h"
 #include "pta_LVecBase2.h"
 #include "epvector.h"
+#include "asyncFuture.h"
 
 #ifdef HAVE_CG
 // I don't want to include the Cg header file into panda as a whole.  Instead,
@@ -109,7 +110,7 @@ PUBLISHED:
   INLINE bool get_cache_compiled_shader() const;
   INLINE void set_cache_compiled_shader(bool flag);
 
-  void prepare(PreparedGraphicsObjects *prepared_objects);
+  PT(AsyncFuture) prepare(PreparedGraphicsObjects *prepared_objects);
   bool is_prepared(PreparedGraphicsObjects *prepared_objects) const;
   bool release(PreparedGraphicsObjects *prepared_objects);
   int release_all();

+ 59 - 4
panda/src/gobj/texture.I

@@ -279,12 +279,12 @@ clear_clear_color() {
  * Returns the raw image data for a single pixel if it were set to the clear
  * color.
  */
-INLINE string Texture::
+INLINE vector_uchar Texture::
 get_clear_data() const {
   CDReader cdata(_cycler);
-  unsigned char data[16];
-  size_t size = do_get_clear_data(cdata, data);
-  return string((char *)data, size);
+  vector_uchar data(16);
+  data.resize(do_get_clear_data(cdata, &data[0]));
+  return data;
 }
 
 /**
@@ -2324,6 +2324,61 @@ get_unsigned_short(const unsigned char *&p) {
   return (double)v.us / 65535.0;
 }
 
+/**
+ * This is used by store() to retrieve the next consecutive component value
+ * from the indicated element of the array, which is taken to be an array of
+ * unsigned ints.
+ */
+INLINE double Texture::
+get_unsigned_int(const unsigned char *&p) {
+  union {
+    unsigned int ui;
+    uchar uc[4];
+  } v;
+  v.uc[0] = (*p++);
+  v.uc[1] = (*p++);
+  v.uc[2] = (*p++);
+  v.uc[3] = (*p++);
+  return (double)v.ui / 4294967295.0;
+}
+
+/**
+ * This is used by store() to retrieve the next consecutive component value
+ * from the indicated element of the array, which is taken to be an array of
+ * floats.
+ */
+INLINE double Texture::
+get_float(const unsigned char *&p) {
+  double v = *((float *)p);
+  p += 4;
+  return v;
+}
+
+/**
+ * This is used by store() to retrieve the next consecutive component value
+ * from the indicated element of the array, which is taken to be an array of
+ * half-floats.
+ */
+INLINE double Texture::
+get_half_float(const unsigned char *&p) {
+  union {
+    uint32_t ui;
+    float uf;
+  } v;
+  uint16_t in = *(uint16_t *)p;
+  p += 2;
+  uint32_t t1 = in & 0x7fff; // Non-sign bits
+  uint32_t t2 = in & 0x8000; // Sign bit
+  uint32_t t3 = in & 0x7c00; // Exponent
+  t1 <<= 13; // Align mantissa on MSB
+  t2 <<= 16; // Shift sign bit into position
+  t1 += 0x38000000; // Adjust bias
+  t1 = (t3 == 0 ? 0 : t1); // Denormals-as-zero
+  t1 |= t2; // Re-insert sign bit
+  v.ui = t1;
+  return v.uf;
+}
+
 /**
  * Returns true if the indicated filename ends in .txo or .txo.pz or .txo.gz,
  * false otherwise.

+ 108 - 33
panda/src/gobj/texture.cxx

@@ -1417,9 +1417,10 @@ peek() {
  * Use this function instead of prepare_now() to preload textures from a user
  * interface standpoint.
  */
-void Texture::
+PT(AsyncFuture) Texture::
 prepare(PreparedGraphicsObjects *prepared_objects) {
-  prepared_objects->enqueue_texture(this);
+  PT(PreparedGraphicsObjects::EnqueuedObject) obj = prepared_objects->enqueue_texture_future(this);
+  return obj.p();
 }
 
 /**
@@ -5087,7 +5088,8 @@ do_store_one(CData *cdata, PNMImage &pnmimage, int z, int n) {
   return convert_to_pnmimage(pnmimage,
                              do_get_expected_mipmap_x_size(cdata, n),
                              do_get_expected_mipmap_y_size(cdata, n),
-                             cdata->_num_components, cdata->_component_width,
+                             cdata->_num_components, cdata->_component_type,
+                             is_srgb(cdata->_format),
                              cdata->_ram_images[n]._image,
                              do_get_ram_mipmap_page_size(cdata, n), z);
 }
@@ -5110,12 +5112,14 @@ do_store_one(CData *cdata, PfmFile &pfm, int z, int n) {
   if (cdata->_component_type != T_float) {
     // PfmFile by way of PNMImage.
     PNMImage pnmimage;
-    bool success = convert_to_pnmimage(pnmimage,
-                                       do_get_expected_mipmap_x_size(cdata, n),
-                                       do_get_expected_mipmap_y_size(cdata, n),
-                                       cdata->_num_components, cdata->_component_width,
-                                       cdata->_ram_images[n]._image,
-                                       do_get_ram_mipmap_page_size(cdata, n), z);
+    bool success =
+      convert_to_pnmimage(pnmimage,
+                          do_get_expected_mipmap_x_size(cdata, n),
+                          do_get_expected_mipmap_y_size(cdata, n),
+                          cdata->_num_components, cdata->_component_type,
+                          is_srgb(cdata->_format),
+                          cdata->_ram_images[n]._image,
+                          do_get_ram_mipmap_page_size(cdata, n), z);
     if (!success) {
       return false;
     }
@@ -5583,10 +5587,28 @@ do_get_clear_data(const CData *cdata, unsigned char *into) const {
   nassertr(cdata->_has_clear_color, 0);
   nassertr(cdata->_num_components <= 4, 0);
 
-  // TODO: encode the color into the sRGB color space if used
   switch (cdata->_component_type) {
   case T_unsigned_byte:
-    {
+    if (is_srgb(cdata->_format)) {
+      xel color;
+      xelval alpha;
+      encode_sRGB_uchar(cdata->_clear_color, color, alpha);
+      switch (cdata->_num_components) {
+      case 2:
+        into[1] = (unsigned char)color.g;
+      case 1:
+        into[0] = (unsigned char)color.r;
+        break;
+      case 4:
+        into[3] = (unsigned char)alpha;
+      case 3: // BGR <-> RGB
+        into[0] = (unsigned char)color.b;
+        into[1] = (unsigned char)color.g;
+        into[2] = (unsigned char)color.r;
+        break;
+      }
+      break;
+    } else {
       LColor scaled = cdata->_clear_color.fmin(LColor(1)).fmax(LColor::zero());
       scaled *= 255;
       switch (cdata->_num_components) {
@@ -8036,25 +8058,28 @@ convert_from_pfm(PTA_uchar &image, size_t page_size, int z,
  */
 bool Texture::
 convert_to_pnmimage(PNMImage &pnmimage, int x_size, int y_size,
-                    int num_components, int component_width,
-                    CPTA_uchar image, size_t page_size, int z) {
+                    int num_components, ComponentType component_type,
+                    bool is_srgb, CPTA_uchar image, size_t page_size, int z) {
   xelval maxval = 0xff;
-  if (component_width > 1) {
+  if (component_type != T_unsigned_byte && component_type != T_byte) {
     maxval = 0xffff;
   }
-  pnmimage.clear(x_size, y_size, num_components, maxval);
+  ColorSpace color_space = is_srgb ? CS_sRGB : CS_linear;
+  pnmimage.clear(x_size, y_size, num_components, maxval, nullptr, color_space);
   bool has_alpha = pnmimage.has_alpha();
   bool is_grayscale = pnmimage.is_grayscale();
 
   int idx = page_size * z;
   nassertr(idx + page_size <= image.size(), false);
-  const unsigned char *p = &image[idx];
 
-  if (component_width == 1) {
-    xel *array = pnmimage.get_array();
+  xel *array = pnmimage.get_array();
+  xelval *alpha = pnmimage.get_alpha_array();
+
+  switch (component_type) {
+  case T_unsigned_byte:
     if (is_grayscale) {
+      const unsigned char *p = &image[idx];
       if (has_alpha) {
-        xelval *alpha = pnmimage.get_alpha_array();
         for (int j = y_size-1; j >= 0; j--) {
           xel *row = array + j * x_size;
           xelval *alpha_row = alpha + j * x_size;
@@ -8071,9 +8096,10 @@ convert_to_pnmimage(PNMImage &pnmimage, int x_size, int y_size,
           }
         }
       }
+      nassertr(p == &image[idx] + page_size, false);
     } else {
+      const unsigned char *p = &image[idx];
       if (has_alpha) {
-        xelval *alpha = pnmimage.get_alpha_array();
         for (int j = y_size-1; j >= 0; j--) {
           xel *row = array + j * x_size;
           xelval *alpha_row = alpha + j * x_size;
@@ -8094,29 +8120,78 @@ convert_to_pnmimage(PNMImage &pnmimage, int x_size, int y_size,
           }
         }
       }
+      nassertr(p == &image[idx] + page_size, false);
     }
+    break;
 
-  } else if (component_width == 2) {
-    for (int j = y_size-1; j >= 0; j--) {
-      for (int i = 0; i < x_size; i++) {
-        if (is_grayscale) {
-          pnmimage.set_gray(i, j, get_unsigned_short(p));
-        } else {
-          pnmimage.set_blue(i, j, get_unsigned_short(p));
-          pnmimage.set_green(i, j, get_unsigned_short(p));
-          pnmimage.set_red(i, j, get_unsigned_short(p));
+  case T_unsigned_short:
+    {
+      const uint16_t *p = (const uint16_t *)&image[idx];
+
+      for (int j = y_size-1; j >= 0; j--) {
+        xel *row = array + j * x_size;
+        xelval *alpha_row = alpha + j * x_size;
+        for (int i = 0; i < x_size; i++) {
+          PPM_PUTB(row[i], *p++);
+          if (!is_grayscale) {
+            PPM_PUTG(row[i], *p++);
+            PPM_PUTR(row[i], *p++);
+          }
+          if (has_alpha) {
+            alpha_row[i] = *p++;
+          }
         }
-        if (has_alpha) {
-          pnmimage.set_alpha(i, j, get_unsigned_short(p));
+      }
+      nassertr((const unsigned char *)p == &image[idx] + page_size, false);
+    }
+    break;
+
+  case T_unsigned_int:
+    {
+      const uint32_t *p = (const uint32_t *)&image[idx];
+
+      for (int j = y_size-1; j >= 0; j--) {
+        xel *row = array + j * x_size;
+        xelval *alpha_row = alpha + j * x_size;
+        for (int i = 0; i < x_size; i++) {
+          PPM_PUTB(row[i], (*p++) >> 16u);
+          if (!is_grayscale) {
+            PPM_PUTG(row[i], (*p++) >> 16u);
+            PPM_PUTR(row[i], (*p++) >> 16u);
+          }
+          if (has_alpha) {
+            alpha_row[i] = (*p++) >> 16u;
+          }
+        }
+      }
+      nassertr((const unsigned char *)p == &image[idx] + page_size, false);
+    }
+    break;
+
+  case T_half_float:
+    {
+      const unsigned char *p = &image[idx];
+
+      for (int j = y_size-1; j >= 0; j--) {
+        for (int i = 0; i < x_size; i++) {
+          pnmimage.set_blue(i, j, get_half_float(p));
+          if (!is_grayscale) {
+            pnmimage.set_green(i, j, get_half_float(p));
+            pnmimage.set_red(i, j, get_half_float(p));
+          }
+          if (has_alpha) {
+            pnmimage.set_alpha(i, j, get_half_float(p));
+          }
         }
       }
+      nassertr(p == &image[idx] + page_size, false);
     }
+    break;
 
-  } else {
+  default:
     return false;
   }
 
-  nassertr(p == &image[idx] + page_size, false);
   return true;
 }
 

+ 10 - 5
panda/src/gobj/texture.h

@@ -43,9 +43,10 @@
 #include "colorSpace.h"
 #include "geomEnums.h"
 #include "bamCacheRecord.h"
+#include "pnmImage.h"
+#include "pfmFile.h"
+#include "asyncFuture.h"
 
-class PNMImage;
-class PfmFile;
 class TextureContext;
 class FactoryParams;
 class PreparedGraphicsObjects;
@@ -264,7 +265,7 @@ PUBLISHED:
   INLINE LColor get_clear_color() const;
   INLINE void set_clear_color(const LColor &color);
   INLINE void clear_clear_color();
-  INLINE string get_clear_data() const;
+  INLINE vector_uchar get_clear_data() const;
   MAKE_PROPERTY2(clear_color, has_clear_color, get_clear_color,
                               set_clear_color, clear_clear_color);
 
@@ -522,7 +523,7 @@ PUBLISHED:
   MAKE_PROPERTY(auto_texture_scale, get_auto_texture_scale,
                                     set_auto_texture_scale);
 
-  void prepare(PreparedGraphicsObjects *prepared_objects);
+  PT(AsyncFuture) prepare(PreparedGraphicsObjects *prepared_objects);
   bool is_prepared(PreparedGraphicsObjects *prepared_objects) const;
   bool was_image_modified(PreparedGraphicsObjects *prepared_objects) const;
   size_t get_data_size_bytes(PreparedGraphicsObjects *prepared_objects) const;
@@ -800,7 +801,8 @@ private:
                                int z, const PfmFile &pfm,
                                int num_components, int component_width);
   static bool convert_to_pnmimage(PNMImage &pnmimage, int x_size, int y_size,
-                                  int num_components, int component_width,
+                                  int num_components,
+                                  ComponentType component_type, bool is_srgb,
                                   CPTA_uchar image, size_t page_size,
                                   int z);
   static bool convert_to_pfm(PfmFile &pfm, int x_size, int y_size,
@@ -855,6 +857,9 @@ private:
   INLINE static void store_scaled_short(unsigned char *&p, int value, double scale);
   INLINE static double get_unsigned_byte(const unsigned char *&p);
   INLINE static double get_unsigned_short(const unsigned char *&p);
+  INLINE static double get_unsigned_int(const unsigned char *&p);
+  INLINE static double get_float(const unsigned char *&p);
+  INLINE static double get_half_float(const unsigned char *&p);
 
   INLINE static bool is_txo_filename(const Filename &fullpath);
   INLINE static bool is_dds_filename(const Filename &fullpath);

+ 55 - 2
panda/src/gobj/texturePeeker.cxx

@@ -82,6 +82,18 @@ TexturePeeker(Texture *tex, Texture::CData *cdata) {
     _get_component = Texture::get_unsigned_short;
     break;
 
+  case Texture::T_unsigned_int:
+    _get_component = Texture::get_unsigned_int;
+    break;
+
+  case Texture::T_float:
+    _get_component = Texture::get_float;
+    break;
+
+  case Texture::T_half_float:
+    _get_component = Texture::get_half_float;
+    break;
+
   default:
     // Not supported.
     _image.clear();
@@ -123,7 +135,6 @@ TexturePeeker(Texture *tex, Texture::CData *cdata) {
     break;
 
   case Texture::F_rgb:
-  case Texture::F_srgb:
   case Texture::F_rgb5:
   case Texture::F_rgb8:
   case Texture::F_rgb12:
@@ -135,7 +146,6 @@ TexturePeeker(Texture *tex, Texture::CData *cdata) {
     break;
 
   case Texture::F_rgba:
-  case Texture::F_srgb_alpha:
   case Texture::F_rgbm:
   case Texture::F_rgba4:
   case Texture::F_rgba5:
@@ -146,6 +156,25 @@ TexturePeeker(Texture *tex, Texture::CData *cdata) {
   case Texture::F_rgb10_a2:
     _get_texel = get_texel_rgba;
     break;
+
+  case Texture::F_srgb:
+    if (_component_type == Texture::T_unsigned_byte) {
+      _get_texel = get_texel_srgb;
+    } else {
+      gobj_cat.error()
+        << "sRGB texture should have component type T_unsigned_byte\n";
+    }
+    break;
+
+  case Texture::F_srgb_alpha:
+    if (_component_type == Texture::T_unsigned_byte) {
+      _get_texel = get_texel_srgba;
+    } else {
+      gobj_cat.error()
+        << "sRGB texture should have component type T_unsigned_byte\n";
+    }
+    break;
+
   default:
     // Not supported.
     gobj_cat.error() << "Unsupported texture peeker format: "
@@ -570,3 +599,27 @@ get_texel_rgba(LColor &color, const unsigned char *&p, GetComponentFunc *get_com
   color[0] = (*get_component)(p);
   color[3] = (*get_component)(p);
 }
+
+/**
+ * Gets the color of the texel at byte p, given that the texture is in format
+ * F_srgb or similar.
+ */
+void TexturePeeker::
+get_texel_srgb(LColor &color, const unsigned char *&p, GetComponentFunc *get_component) {
+  color[2] = decode_sRGB_float(*p++);
+  color[1] = decode_sRGB_float(*p++);
+  color[0] = decode_sRGB_float(*p++);
+  color[3] = 1.0f;
+}
+
+/**
+ * Gets the color of the texel at byte p, given that the texture is in format
+ * F_srgb_alpha or similar.
+ */
+void TexturePeeker::
+get_texel_srgba(LColor &color, const unsigned char *&p, GetComponentFunc *get_component) {
+  color[2] = decode_sRGB_float(*p++);
+  color[1] = decode_sRGB_float(*p++);
+  color[0] = decode_sRGB_float(*p++);
+  color[3] = (*get_component)(p);
+}

+ 2 - 0
panda/src/gobj/texturePeeker.h

@@ -79,6 +79,8 @@ private:
   static void get_texel_la(LColor &color, const unsigned char *&p, GetComponentFunc *get_component);
   static void get_texel_rgb(LColor &color, const unsigned char *&p, GetComponentFunc *get_component);
   static void get_texel_rgba(LColor &color, const unsigned char *&p, GetComponentFunc *get_component);
+  static void get_texel_srgb(LColor &color, const unsigned char *&p, GetComponentFunc *get_component);
+  static void get_texel_srgba(LColor &color, const unsigned char *&p, GetComponentFunc *get_component);
 
   int _x_size;
   int _y_size;

+ 4 - 3
panda/src/gobj/textureReloadRequest.I

@@ -22,8 +22,7 @@ TextureReloadRequest(const string &name,
   AsyncTask(name),
   _pgo(pgo),
   _texture(texture),
-  _allow_compressed(allow_compressed),
-  _is_ready(false)
+  _allow_compressed(allow_compressed)
 {
   nassertv(_pgo != (PreparedGraphicsObjects *)NULL);
   nassertv(_texture != (Texture *)NULL);
@@ -58,8 +57,10 @@ get_allow_compressed() const {
 
 /**
  * Returns true if this request has completed, false if it is still pending.
+ * Equivalent to `req.done() and not req.cancelled()`.
+ * @see done()
  */
 INLINE bool TextureReloadRequest::
 is_ready() const {
-  return _is_ready;
+  return (FutureState)AtomicAdjust::get(_future_state) == FS_finished;
 }

+ 0 - 1
panda/src/gobj/textureReloadRequest.cxx

@@ -43,7 +43,6 @@ do_task() {
       _texture->prepare(_pgo);
     }
   }
-  _is_ready = true;
 
   // Don't continue the task; we're done.
   return DS_done;

+ 2 - 1
panda/src/gobj/textureReloadRequest.h

@@ -43,6 +43,8 @@ PUBLISHED:
   INLINE bool get_allow_compressed() const;
   INLINE bool is_ready() const;
 
+  MAKE_PROPERTY(texture, get_texture);
+
 protected:
   virtual DoneStatus do_task();
 
@@ -50,7 +52,6 @@ private:
   PT(PreparedGraphicsObjects) _pgo;
   PT(Texture) _texture;
   bool _allow_compressed;
-  bool _is_ready;
 
 public:
   static TypeHandle get_class_type() {

+ 33 - 16
panda/src/gobj/texture_ext.cxx

@@ -33,15 +33,12 @@ set_ram_image(PyObject *image, Texture::CompressionMode compression,
   nassertv(compression != Texture::CM_default);
 
   // Check if perhaps a PointerToArray object was passed in.
-  if (DtoolCanThisBeAPandaInstance(image)) {
-    Dtool_PyInstDef *inst = (Dtool_PyInstDef *)image;
-
-    if (inst->_My_Type == &Dtool_ConstPointerToArray_unsigned_char) {
-      _this->set_ram_image(*(const CPTA_uchar *)inst->_ptr_to_object, compression, page_size);
+  if (DtoolInstance_Check(image)) {
+    if (DtoolInstance_TYPE(image) == &Dtool_ConstPointerToArray_unsigned_char) {
+      _this->set_ram_image(*(const CPTA_uchar *)DtoolInstance_VOID_PTR(image), compression, page_size);
       return;
-
-    } else if (inst->_My_Type == &Dtool_PointerToArray_unsigned_char) {
-      _this->set_ram_image(*(const PTA_uchar *)inst->_ptr_to_object, compression, page_size);
+    } else if (DtoolInstance_TYPE(image) == &Dtool_PointerToArray_unsigned_char) {
+      _this->set_ram_image(*(const PTA_uchar *)DtoolInstance_VOID_PTR(image), compression, page_size);
       return;
     }
   }
@@ -87,6 +84,29 @@ set_ram_image(PyObject *image, Texture::CompressionMode compression,
   }
 #endif
 
+#if PY_MAJOR_VERSION < 3
+  // The old, deprecated buffer interface, as used by eg. the array module.
+  const void *buffer;
+  Py_ssize_t buffer_len;
+  if (!PyUnicode_CheckExact(image) &&
+      PyObject_AsReadBuffer(image, &buffer, &buffer_len) == 0) {
+    if (compression == Texture::CM_off) {
+      int component_width = _this->get_component_width();
+      if (buffer_len % component_width != 0) {
+        PyErr_Format(PyExc_ValueError,
+                    "byte buffer is not a multiple of %d bytes",
+                    component_width);
+        return;
+      }
+    }
+
+    PTA_uchar data = PTA_uchar::empty_array(buffer_len, Texture::get_class_type());
+    memcpy(data.p(), buffer, buffer_len);
+    _this->set_ram_image(MOVE(data), compression, page_size);
+    return;
+  }
+#endif
+
   Dtool_Raise_ArgTypeError(image, 0, "Texture.set_ram_image", "CPTA_uchar or buffer");
 }
 
@@ -99,15 +119,12 @@ set_ram_image(PyObject *image, Texture::CompressionMode compression,
 void Extension<Texture>::
 set_ram_image_as(PyObject *image, const string &provided_format) {
   // Check if perhaps a PointerToArray object was passed in.
-  if (DtoolCanThisBeAPandaInstance(image)) {
-    Dtool_PyInstDef *inst = (Dtool_PyInstDef *)image;
-
-    if (inst->_My_Type == &Dtool_ConstPointerToArray_unsigned_char) {
-      _this->set_ram_image_as(*(const CPTA_uchar *)inst->_ptr_to_object, provided_format);
+  if (DtoolInstance_Check(image)) {
+    if (DtoolInstance_TYPE(image) == &Dtool_ConstPointerToArray_unsigned_char) {
+      _this->set_ram_image_as(*(const CPTA_uchar *)DtoolInstance_VOID_PTR(image), provided_format);
       return;
-
-    } else if (inst->_My_Type == &Dtool_PointerToArray_unsigned_char) {
-      _this->set_ram_image_as(*(const PTA_uchar *)inst->_ptr_to_object, provided_format);
+    } else if (DtoolInstance_TYPE(image) == &Dtool_PointerToArray_unsigned_char) {
+      _this->set_ram_image_as(*(const PTA_uchar *)DtoolInstance_VOID_PTR(image), provided_format);
       return;
     }
   }

+ 2 - 0
panda/src/pgraph/cullResult.cxx

@@ -105,6 +105,8 @@ add_object(CullableObject *object, const CullTraverser *traverser) {
   static const LColor flash_multisample_color(0.78f, 0.05f, 0.81f, 1.0f);
   static const LColor flash_dual_color(0.92, 0.01f, 0.01f, 1.0f);
 
+  nassertv(object->_draw_callback != nullptr || object->_geom != nullptr);
+
   bool force = !traverser->get_effective_incomplete_render();
   Thread *current_thread = traverser->get_current_thread();
   CullBinManager *bin_manager = CullBinManager::get_global_ptr();

+ 1 - 1
panda/src/pgraph/cullTraverser.cxx

@@ -232,7 +232,7 @@ draw_bounding_volume(const BoundingVolume *vol,
   if (bounds_viz != (Geom *)NULL) {
     _geoms_pcollector.add_level(2);
     CullableObject *outer_viz =
-      new CullableObject(move(bounds_viz), get_bounds_outer_viz_state(),
+      new CullableObject(bounds_viz, get_bounds_outer_viz_state(),
                          internal_transform);
     _cull_handler->record_object(outer_viz, this);
 

+ 5 - 4
panda/src/pgraph/geomNode.cxx

@@ -38,6 +38,7 @@
 #include "boundingBox.h"
 #include "boundingSphere.h"
 #include "config_mathutil.h"
+#include "preparedGraphicsObjects.h"
 
 
 bool allow_flatten_color = ConfigVariableBool
@@ -382,14 +383,14 @@ r_prepare_scene(GraphicsStateGuardianBase *gsg, const RenderState *node_state,
     int num_arrays = vdata_reader.get_num_arrays();
     for (int i = 0; i < num_arrays; ++i) {
       CPT(GeomVertexArrayData) array = vdata_reader.get_array(i);
-      ((GeomVertexArrayData *)array.p())->prepare(prepared_objects);
+      prepared_objects->enqueue_vertex_buffer((GeomVertexArrayData *)array.p());
     }
 
     // And also each of the index arrays.
     int num_primitives = geom->get_num_primitives();
     for (int i = 0; i < num_primitives; ++i) {
       CPT(GeomPrimitive) prim = geom->get_primitive(i);
-      ((GeomPrimitive *)prim.p())->prepare(prepared_objects);
+      prepared_objects->enqueue_index_buffer((GeomPrimitive *)prim.p());
     }
 
     if (munger->is_of_type(StateMunger::get_class_type())) {
@@ -405,7 +406,7 @@ r_prepare_scene(GraphicsStateGuardianBase *gsg, const RenderState *node_state,
         Texture *texture = ta->get_on_texture(ta->get_on_stage(i));
         // TODO: prepare the sampler states, if specified.
         if (texture != nullptr) {
-          texture->prepare(prepared_objects);
+          prepared_objects->enqueue_texture(texture);
         }
       }
     }
@@ -415,7 +416,7 @@ r_prepare_scene(GraphicsStateGuardianBase *gsg, const RenderState *node_state,
     if (geom_state->get_attrib(sa)) {
       Shader *shader = (Shader *)sa->get_shader();
       if (shader != nullptr) {
-        shader->prepare(prepared_objects);
+        prepared_objects->enqueue_shader(shader);
       }
       // TODO: prepare the shader inputs.
     }

+ 1 - 0
panda/src/pgraph/loader.I

@@ -135,6 +135,7 @@ stop_threads() {
 /**
  * Removes a pending asynchronous load request.  Returns true if successful,
  * false otherwise.
+ * @deprecated use task.cancel() to cancel the request instead.
  */
 INLINE bool Loader::
 remove(AsyncTask *task) {

+ 8 - 20
panda/src/pgraph/modelFlattenRequest.I

@@ -18,8 +18,7 @@
 INLINE ModelFlattenRequest::
 ModelFlattenRequest(PandaNode *orig) :
   AsyncTask(orig->get_name()),
-  _orig(orig),
-  _is_ready(false)
+  _orig(orig)
 {
 }
 
@@ -35,32 +34,21 @@ get_orig() const {
  * Returns true if this request has completed, false if it is still pending.
  * When this returns true, you may retrieve the model loaded by calling
  * result().
+ * Equivalent to `req.done() and not req.cancelled()`.
+ * @see done()
  */
 INLINE bool ModelFlattenRequest::
 is_ready() const {
-  return _is_ready;
+  return (FutureState)AtomicAdjust::get(_future_state) == FS_finished;
 }
 
 /**
  * Returns the flattened copy of the model.  It is an error to call this
- * unless is_ready() returns true.
+ * unless done() returns true.
+ * @deprecated Use result() instead.
  */
 INLINE PandaNode *ModelFlattenRequest::
 get_model() const {
-  nassertr(_is_ready, nullptr);
-  return _model;
-}
-
-/**
- * Returns the flattened copy of the model wrapped in a NodePath.  It is an
- * error to call this unless is_ready() returns true.
- */
-INLINE NodePath ModelFlattenRequest::
-result() const {
-  nassertr(_is_ready, NodePath::fail());
-  if (_model != nullptr) {
-    return NodePath(_model);
-  } else {
-    return NodePath::fail();
-  }
+  nassertr_always(done(), nullptr);
+  return (PandaNode *)_result;
 }

+ 2 - 2
panda/src/pgraph/modelFlattenRequest.cxx

@@ -35,8 +35,8 @@ do_task() {
     np.attach_new_node(_orig);
   }
   np.flatten_strong();
-  _model = np.get_child(0).node();
-  _is_ready = true;
+
+  set_result(np.get_child(0).node());
 
   // Don't continue the task; we're done.
   return DS_done;

+ 0 - 5
panda/src/pgraph/modelFlattenRequest.h

@@ -39,18 +39,13 @@ PUBLISHED:
   INLINE bool is_ready() const;
   INLINE PandaNode *get_model() const;
 
-  INLINE NodePath result() const;
-
   MAKE_PROPERTY(orig, get_orig);
-  MAKE_PROPERTY(ready, is_ready);
 
 protected:
   virtual DoneStatus do_task();
 
 private:
   PT(PandaNode) _orig;
-  bool _is_ready;
-  PT(PandaNode) _model;
 
 public:
   static TypeHandle get_class_type() {

+ 11 - 18
panda/src/pgraph/modelLoadRequest.I

@@ -38,31 +38,24 @@ get_loader() const {
 }
 
 /**
- * Returns the model that was loaded asynchronously as a NodePath, if any, or
- * the empty NodePath if there was an error.
- */
-INLINE NodePath ModelLoadRequest::
-result() const {
-  nassertr_always(_is_ready, NodePath::fail());
-  return NodePath(_model);
-}
-
-/**
- * Returns true if this request has completed, false if it is still pending.
- * When this returns true, you may retrieve the model loaded by calling
- * get_model().
+ * Returns true if this request has completed, false if it is still pending or
+ * if it has been cancelled.  When this returns true, you may retrieve the
+ * model loaded by calling get_model().
+ * Equivalent to `req.done() and not req.cancelled()`.
+ * @see done()
  */
 INLINE bool ModelLoadRequest::
 is_ready() const {
-  return _is_ready;
+  return (FutureState)AtomicAdjust::get(_future_state) == FS_finished;
 }
 
 /**
- * Returns the model that was loaded asynchronously, if any, or NULL if there
- * was an error.  It is an error to call this unless is_ready() returns true.
+ * Returns the model that was loaded asynchronously, if any, or null if there
+ * was an error.  It is an error to call this unless done() returns true.
+ * @deprecated Use result() instead.
  */
 INLINE PandaNode *ModelLoadRequest::
 get_model() const {
-  nassertr(_is_ready, NULL);
-  return _model;
+  nassertr_always(done(), nullptr);
+  return (PandaNode *)_result;
 }

+ 3 - 4
panda/src/pgraph/modelLoadRequest.cxx

@@ -28,8 +28,7 @@ ModelLoadRequest(const string &name,
   AsyncTask(name),
   _filename(filename),
   _options(options),
-  _loader(loader),
-  _is_ready(false)
+  _loader(loader)
 {
 }
 
@@ -43,8 +42,8 @@ do_task() {
     Thread::sleep(delay);
   }
 
-  _model = _loader->load_sync(_filename, _options);
-  _is_ready = true;
+  PT(PandaNode) model = _loader->load_sync(_filename, _options);
+  set_result(model);
 
   // Don't continue the task; we're done.
   return DS_done;

+ 0 - 5
panda/src/pgraph/modelLoadRequest.h

@@ -43,15 +43,12 @@ PUBLISHED:
   INLINE const LoaderOptions &get_options() const;
   INLINE Loader *get_loader() const;
 
-  INLINE NodePath result() const;
-
   INLINE bool is_ready() const;
   INLINE PandaNode *get_model() const;
 
   MAKE_PROPERTY(filename, get_filename);
   MAKE_PROPERTY(options, get_options);
   MAKE_PROPERTY(loader, get_loader);
-  MAKE_PROPERTY(ready, is_ready);
 
 protected:
   virtual DoneStatus do_task();
@@ -60,8 +57,6 @@ private:
   Filename _filename;
   LoaderOptions _options;
   PT(Loader) _loader;
-  bool _is_ready;
-  PT(PandaNode) _model;
 
 public:
   static TypeHandle get_class_type() {

+ 5 - 13
panda/src/pgraph/modelSaveRequest.I

@@ -49,28 +49,20 @@ get_loader() const {
  * Returns true if this request has completed, false if it is still pending.
  * When this returns true, you may retrieve the success flag with
  * get_success().
+ * Equivalent to `req.done() and not req.cancelled()`.
+ * @see done()
  */
 INLINE bool ModelSaveRequest::
 is_ready() const {
-  return _is_ready;
+  return (FutureState)AtomicAdjust::get(_future_state) == FS_finished;
 }
 
 /**
  * Returns the true if the model was saved successfully, false otherwise.  It
- * is an error to call this unless is_ready() returns true.
+ * is an error to call this unless done() returns true.
  */
 INLINE bool ModelSaveRequest::
 get_success() const {
-  nassertr(_is_ready, false);
-  return _success;
-}
-
-/**
- * Returns a boolean indicating whether the model saved correctly.  It is an
- * error to call this unless is_ready() returns true.
- */
-INLINE bool ModelSaveRequest::
-result() const {
-  nassertr(_is_ready, false);
+  nassertr_always(done(), false);
   return _success;
 }

+ 0 - 2
panda/src/pgraph/modelSaveRequest.cxx

@@ -30,7 +30,6 @@ ModelSaveRequest(const string &name,
   _options(options),
   _node(node),
   _loader(loader),
-  _is_ready(false),
   _success(false)
 {
 }
@@ -46,7 +45,6 @@ do_task() {
   }
 
   _success = _loader->save_sync(_filename, _options, _node);
-  _is_ready = true;
 
   // Don't continue the task; we're done.
   return DS_done;

+ 0 - 4
panda/src/pgraph/modelSaveRequest.h

@@ -46,13 +46,10 @@ PUBLISHED:
   INLINE bool is_ready() const;
   INLINE bool get_success() const;
 
-  INLINE bool result() const;
-
   MAKE_PROPERTY(filename, get_filename);
   MAKE_PROPERTY(options, get_options);
   MAKE_PROPERTY(node, get_node);
   MAKE_PROPERTY(loader, get_loader);
-  MAKE_PROPERTY(ready, is_ready);
 
 protected:
   virtual DoneStatus do_task();
@@ -62,7 +59,6 @@ private:
   LoaderOptions _options;
   PT(PandaNode) _node;
   PT(Loader) _loader;
-  bool _is_ready;
   bool _success;
 
 public:

+ 1 - 2
panda/src/pgraph/nodePathCollection_ext.cxx

@@ -46,8 +46,7 @@ __init__(PyObject *self, PyObject *sequence) {
     }
 
     NodePath *path;
-    DTOOL_Call_ExtractThisPointerForType(item, &Dtool_NodePath, (void **)&path);
-    if (path == (NodePath *)NULL) {
+    if (!DtoolInstance_GetPointer(item, path, Dtool_NodePath)) {
       // Unable to add item--probably it wasn't of the appropriate type.
       ostringstream stream;
       stream << "Element " << i << " in sequence passed to NodePathCollection constructor is not a NodePath";

+ 3 - 3
panda/src/pgraph/nodePath_ext.cxx

@@ -114,7 +114,7 @@ __reduce_persist__(PyObject *self, PyObject *pickler) const {
       // It's OK if there's no bamWriter.
       PyErr_Clear();
     } else {
-      DTOOL_Call_ExtractThisPointerForType(py_writer, &Dtool_BamWriter, (void **)&writer);
+      DtoolInstance_GetPointer(py_writer, writer, Dtool_BamWriter);
       Py_DECREF(py_writer);
     }
   }
@@ -228,7 +228,7 @@ py_decode_NodePath_from_bam_stream_persist(PyObject *unpickler, vector_uchar dat
       // It's OK if there's no bamReader.
       PyErr_Clear();
     } else {
-      DTOOL_Call_ExtractThisPointerForType(py_reader, &Dtool_BamReader, (void **)&reader);
+      DtoolInstance_GetPointer(py_reader, reader, Dtool_BamReader);
       Py_DECREF(py_reader);
     }
   }
@@ -251,7 +251,7 @@ set_shader_input(CPT_InternalName name, PyObject *value, int priority) {
   }
 
   ShaderInput &input = attrib->_inputs[name];
-  invoke_extension(&input).__init__(move(name), value);
+  invoke_extension(&input).__init__(move(name), value, priority);
 
   if (!_PyErr_OCCURRED()) {
     node->set_attrib(ShaderAttrib::return_new(attrib));

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů