Browse Source

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

rdb 8 years ago
parent
commit
c911ba8059
100 changed files with 2798 additions and 1024 deletions
  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
         return self.currT
 
 
     def start(self, startT = 0.0, endT = -1.0, playRate = 1.0):
     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.setupPlay(startT, endT, playRate, 0)
-        self.__spawnTask()
+        return self.__spawnTask()
 
 
     def loop(self, startT = 0.0, endT = -1.0, playRate = 1.0):
     def loop(self, startT = 0.0, endT = -1.0, playRate = 1.0):
         self.setupPlay(startT, endT, playRate, 1)
         self.setupPlay(startT, endT, playRate, 1)
@@ -427,6 +428,7 @@ class Interval(DirectObject):
         task = Task(self.__playTask)
         task = Task(self.__playTask)
         task.interval = self
         task.interval = self
         taskMgr.add(task, taskName)
         taskMgr.add(task, taskName)
+        return task
 
 
     def __removeTask(self):
     def __removeTask(self):
         # Kill old task(s), including those from a similarly-named but
         # 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):
         def cancel(self):
             "Cancels the request.  Callback won't be called."
             "Cancels the request.  Callback won't be called."
             if self._loader:
             if self._loader:
-                self._loader = None
                 for request in self.requests:
                 for request in self.requests:
                     self._loader.loader.remove(request)
                     self._loader.loader.remove(request)
                     del self._loader._requests[request]
                     del self._loader._requests[request]
+                self._loader = None
                 self.requests = None
                 self.requests = None
                 self.requestList = None
                 self.requestList = None
 
 
@@ -66,7 +66,9 @@ class Loader(DirectObject):
             return not self.requests
             return not self.requests
 
 
         def result(self):
         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:
             if self.gotList:
                 return self.objects
                 return self.objects
             else:
             else:
@@ -132,7 +134,8 @@ class Loader(DirectObject):
     # model loading funcs
     # model loading funcs
     def loadModel(self, modelPath, loaderOptions = None, noCache = None,
     def loadModel(self, modelPath, loaderOptions = None, noCache = None,
                   allowInstance = False, okMissing = 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
         Attempts to load a model or models from one or more relative
         pathnames.  If the input modelPath is a string (a single model
         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
         If callback is not None, then the model load will be performed
         asynchronously.  In this case, loadModel() will initiate a
         asynchronously.  In this case, loadModel() will initiate a
         background load and return immediately.  The return value will
         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
         loaded models passed as its parameter list.  It is possible
         that the callback will be invoked immediately, even before
         that the callback will be invoked immediately, even before
         loadModel() returns.  If you use callback, you may also
         loadModel() returns.  If you use callback, you may also
@@ -224,7 +227,10 @@ class Loader(DirectObject):
             modelList = modelPath
             modelList = modelPath
             gotList = True
             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.
             # We got no callback, so it's a synchronous load.
 
 
             result = []
             result = []
@@ -362,7 +368,8 @@ class Loader(DirectObject):
         ModelPool.releaseModel(modelNode)
         ModelPool.releaseModel(modelNode)
 
 
     def saveModel(self, modelPath, node, loaderOptions = None,
     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
         """ Saves the model (a NodePath or PandaNode) to the indicated
         filename path.  Returns true on success, false on failure.  If
         filename path.  Returns true on success, false on failure.  If
         a callback is used, the model is saved asynchronously, and the
         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.
         # From here on, we deal with a list of (filename, node) pairs.
         modelList = list(zip(modelList, nodeList))
         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.
             # We got no callback, so it's a synchronous save.
 
 
             result = []
             result = []
@@ -1059,7 +1069,7 @@ class Loader(DirectObject):
             return
             return
 
 
         cb, i = self._requests[request]
         cb, i = self._requests[request]
-        if cb.cancelled():
+        if cb.cancelled() or request.cancelled():
             # Shouldn't be here.
             # Shouldn't be here.
             del self._requests[request]
             del self._requests[request]
             return
             return
@@ -1068,7 +1078,11 @@ class Loader(DirectObject):
         if not cb.requests:
         if not cb.requests:
             del self._requests[request]
             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
     load_model = loadModel
     unload_model = unloadModel
     unload_model = unloadModel

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

@@ -109,6 +109,12 @@ class Messenger:
             if record[0] <= 0:
             if record[0] <= 0:
                 del self._id2object[id]
                 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):
     def accept(self, event, object, method, extraArgs=[], persistent=1):
         """ accept(self, string, DirectObject, Function, List, Boolean)
         """ accept(self, string, DirectObject, Function, List, Boolean)
 
 
@@ -409,10 +415,14 @@ class Messenger:
                 # Release the lock temporarily while we call the method.
                 # Release the lock temporarily while we call the method.
                 self.lock.release()
                 self.lock.release()
                 try:
                 try:
-                    method (*(extraArgs + sentArgs))
+                    result = method (*(extraArgs + sentArgs))
                 finally:
                 finally:
                     self.lock.acquire()
                     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):
     def clear(self):
         """
         """
         Start fresh with a clear dict
         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):
             if isinstance(self.win, GraphicsWindow):
                 self.setupMouse(self.win)
                 self.setupMouse(self.win)
             self.makeCamera2d(self.win)
             self.makeCamera2d(self.win)
-            self.makeCamera2dp(self.win)
+
+            if self.wantRender2dp:
+                self.makeCamera2dp(self.win)
 
 
             if oldLens != None:
             if oldLens != None:
                 # Restore the previous lens properties.
                 # Restore the previous lens properties.
@@ -1258,9 +1260,9 @@ class ShowBase(DirectObject.DirectObject):
         if win == None:
         if win == None:
             win = self.win
             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())
             aspectRatio = float(win.getSbsLeftXSize()) / float(win.getSbsLeftYSize())
-
         else:
         else:
             if win == None or not hasattr(win, "getRequestedProperties"):
             if win == None or not hasattr(win, "getRequestedProperties"):
                 props = WindowProperties.getDefault()
                 props = WindowProperties.getDefault()
@@ -1295,8 +1297,7 @@ class ShowBase(DirectObject.DirectObject):
                 if not props.hasSize():
                 if not props.hasSize():
                     props = WindowProperties.getDefault()
                     props = WindowProperties.getDefault()
 
 
-            if props.hasSize():
-                return props.getXSize(), props.getYSize()
+            return props.getXSize(), props.getYSize()
 
 
     def makeCamera(self, win, sort = 0, scene = None,
     def makeCamera(self, win, sort = 0, scene = None,
                    displayRegion = (0, 1, 0, 1), stereo = 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.
         # Tell the gui system about our new mouse watcher.
         self.aspect2d.node().setMouseWatcher(mw.node())
         self.aspect2d.node().setMouseWatcher(mw.node())
-        self.aspect2dp.node().setMouseWatcher(mw.node())
         self.pixel2d.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())
         mw.node().addRegion(PGMouseWatcherBackground())
 
 
         return self.buttonThrowers[0]
         return self.buttonThrowers[0]
@@ -2723,13 +2726,15 @@ class ShowBase(DirectObject.DirectObject):
             # changed and update the camera lenses and aspect2d parameters
             # changed and update the camera lenses and aspect2d parameters
             self.adjustWindowAspectRatio(self.getAspectRatio())
             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.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())
                 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):
     def adjustWindowAspectRatio(self, aspectRatio):
         """ This function is normally called internally by
         """ This function is normally called internally by
@@ -2753,11 +2758,12 @@ class ShowBase(DirectObject.DirectObject):
                 self.a2dLeft = -1
                 self.a2dLeft = -1
                 self.a2dRight = 1.0
                 self.a2dRight = 1.0
                 # Don't forget 2dp
                 # 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:
             else:
                 # If the window is WIDE, lets expand the left and right
                 # If the window is WIDE, lets expand the left and right
@@ -2767,41 +2773,43 @@ class ShowBase(DirectObject.DirectObject):
                 self.a2dLeft = -aspectRatio
                 self.a2dLeft = -aspectRatio
                 self.a2dRight = aspectRatio
                 self.a2dRight = aspectRatio
                 # Don't forget 2dp
                 # 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
             # 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.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.a2dLeftCenterNs.setPos(self.a2dLeft, 0, 0)
+            self.a2dRightCenter.setPos(self.a2dRight, 0, 0)
             self.a2dRightCenterNs.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
             # 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
             # If anybody needs to update their GUI, put a callback on this event
             messenger.send("aspectRatioChanged")
             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
 # 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
 # syntax is necessary because it's a C++-wrapped extension type, not a
 # true Python class.
 # 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['cont'] = cont
 Task.DtoolClassDict['again'] = again
 Task.DtoolClassDict['again'] = again
 Task.DtoolClassDict['pickup'] = pickup
 Task.DtoolClassDict['pickup'] = pickup
@@ -84,6 +86,8 @@ Task.DtoolClassDict['exit'] = exit
 pause = AsyncTaskPause
 pause = AsyncTaskPause
 Task.DtoolClassDict['pause'] = staticmethod(pause)
 Task.DtoolClassDict['pause'] = staticmethod(pause)
 
 
+gather = Task.gather
+
 def sequence(*taskList):
 def sequence(*taskList):
     seq = AsyncTaskSequence('sequence')
     seq = AsyncTaskSequence('sequence')
     for task in taskList:
     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) {
   if (Py_TYPE(path) == &Dtool_Filename._PyType) {
     // Copy constructor.
     // Copy constructor.
-    (*_this) = *((Filename *)((Dtool_PyInstDef *)path)->_ptr_to_object);
+    *_this = *(Filename *)DtoolInstance_VOID_PTR(path);
     return;
     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 == "kwargs" ||
           _parameters[first_param + 1]._name == "kwds")) {
           _parameters[first_param + 1]._name == "kwds")) {
       _flags |= F_explicit_args;
       _flags |= F_explicit_args;
+      _args_type = InterfaceMaker::AT_keyword_args;
     }
     }
   }
   }
 
 
@@ -909,6 +910,13 @@ setup_properties(const InterrogateFunction &ifunc, InterfaceMaker *interface_mak
             break;
             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;
     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.
   // 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 << "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 << "    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 << "    return NULL;\n";
     out << "  }\n";
     out << "  }\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 << "    return local_this;\n";
     out << "  }\n";
     out << "  }\n";
 
 
@@ -2220,13 +2220,13 @@ write_module_class(ostream &out, Object *obj) {
           // provide a writable buffer or a readonly buffer.
           // provide a writable buffer or a readonly buffer.
           const string const_this = "(const " + cClassName + " *)local_this";
           const string const_this = "(const " + cClassName + " *)local_this";
           if (remap_const != NULL && remap_nonconst != NULL) {
           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 << "    return " << remap_nonconst->call_function(out, 4, false, "local_this", params_nonconst) << ";\n";
             out << "  } else {\n";
             out << "  } else {\n";
             out << "    return " << remap_const->call_function(out, 4, false, const_this, params_const) << ";\n";
             out << "    return " << remap_const->call_function(out, 4, false, const_this, params_const) << ";\n";
             out << "  }\n";
             out << "  }\n";
           } else if (remap_nonconst != NULL) {
           } 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 << "    return " << remap_nonconst->call_function(out, 4, false, "local_this", params_nonconst) << ";\n";
             out << "  } else {\n";
             out << "  } else {\n";
             out << "    Dtool_Raise_TypeError(\"Cannot call " << ClassName << ".__getbuffer__() on a const object.\");\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;
           string return_expr;
           const string const_this = "(const " + cClassName + " *)local_this";
           const string const_this = "(const " + cClassName + " *)local_this";
           if (remap_const != NULL && remap_nonconst != NULL) {
           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);
             return_expr = remap_nonconst->call_function(out, 4, false, "local_this", params_nonconst);
             if (!return_expr.empty()) {
             if (!return_expr.empty()) {
               out << "    " << return_expr << ";\n";
               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";
     out << "    Dtool_" << ClassName << "._PyType.tp_bases = PyTuple_Pack(" << bases.size() << baseargs << ");\n";
   } else {
   } 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();
   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 << ", "
       out << "  if (!Dtool_Call_ExtractThisPointer_NonConst(self, Dtool_" << ClassName << ", "
           << "(void **)&local_this, \"" << classNameFromCppName(cClassName, false)
           << "(void **)&local_this, \"" << classNameFromCppName(cClassName, false)
           << "." << methodNameFromCppName(remap, cClassName, false) << "\")) {\n";
           << "." << methodNameFromCppName(remap, cClassName, false) << "\")) {\n";
+
     } else {
     } 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);
     error_return(out, 4, return_flags);
@@ -3616,7 +3617,7 @@ write_function_for_name(ostream &out, Object *obj,
       if (strip_keyword_args) {
       if (strip_keyword_args) {
         // None of the remaps take any keyword arguments, so let's check that
         // None of the remaps take any keyword arguments, so let's check that
         // we take none.  This saves some checks later on.
         // 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) {
         if (min_args == 1 && min_args == 1) {
           indent(out, 4) << "  PyObject *arg = PyTuple_GET_ITEM(args, 0);\n";
           indent(out, 4) << "  PyObject *arg = PyTuple_GET_ITEM(args, 0);\n";
           write_function_forset(out, mii->second, min_args, max_args, expected_params, 6,
           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
     // 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
     // currently the case in all invocations, but this may not be true in the
     // future.
     // 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";
     out << "    // The argument is already of matching type, no need to coerce.\n";
     if (!is_const) {
     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 << "      // A non-const instance is required, which this is.\n";
       out << "      coerced->ref();\n";
       out << "      coerced->ref();\n";
       out << "      return true;\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 << " *Dtool_Coerce_" << ClassName << "(PyObject *args, " << cClassName << " &coerced) {\n";
 
 
     out << "  " << cClassName << " *local_this;\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 << "      // This is a const object.  Make a copy.\n";
     out << "      coerced = *(const " << cClassName << " *)local_this;\n";
     out << "      coerced = *(const " << cClassName << " *)local_this;\n";
     out << "      return &coerced;\n";
     out << "      return &coerced;\n";
@@ -4340,7 +4339,7 @@ write_function_forset(ostream &out,
     if (all_nonconst) {
     if (all_nonconst) {
       // Yes, they do.  Check that the parameter has the required constness.
       // Yes, they do.  Check that the parameter has the required constness.
       indent(out, indent_level)
       indent(out, indent_level)
-        << "if (!((Dtool_PyInstDef *)self)->_is_const) {\n";
+        << "if (!DtoolInstance_IS_CONST(self)) {\n";
       indent_level += 2;
       indent_level += 2;
       verify_const = false;
       verify_const = false;
     }
     }
@@ -4406,7 +4405,7 @@ write_function_forset(ostream &out,
       if (verify_const && (remap->_has_this && !remap->_const_method)) {
       if (verify_const && (remap->_has_this && !remap->_const_method)) {
         // If it's a non-const method, we only allow a non-const this.
         // If it's a non-const method, we only allow a non-const this.
         indent(out, indent_level)
         indent(out, indent_level)
-          << "if (!((Dtool_PyInstDef *)self)->_is_const) {\n";
+          << "if (!DtoolInstance_IS_CONST(self)) {\n";
       } else {
       } else {
         indent(out, indent_level)
         indent(out, indent_level)
           << "{\n";
           << "{\n";
@@ -4441,7 +4440,7 @@ write_function_forset(ostream &out,
 
 
         if (verify_const && (remap->_has_this && !remap->_const_method)) {
         if (verify_const && (remap->_has_this && !remap->_const_method)) {
           indent(out, indent_level)
           indent(out, indent_level)
-            << "if (!((Dtool_PyInstDef *)self)->_is_const) {\n";
+            << "if (!DtoolInstance_IS_CONST(self)) {\n";
         } else {
         } else {
           indent(out, indent_level)
           indent(out, indent_level)
             << "{\n";
             << "{\n";
@@ -5525,14 +5524,13 @@ write_function_instance(ostream &out, FunctionRemap *remap,
           // This function does the same thing in this case and is slightly
           // This function does the same thing in this case and is slightly
           // simpler.  But maybe we should just reorganize these functions
           // simpler.  But maybe we should just reorganize these functions
           // entirely?
           // 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 {
         } else {
           extra_convert << boolalpha
           extra_convert << boolalpha
             << " = (" << class_name << " *)"
             << " = (" << class_name << " *)"
@@ -6796,7 +6794,7 @@ write_getset(ostream &out, Object *obj, Property *property) {
     out << "  if (wrap != NULL) {\n"
     out << "  if (wrap != NULL) {\n"
            "    wrap->_getitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Mapping_Getitem;\n";
            "    wrap->_getitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Mapping_Getitem;\n";
     if (!property->_setter_remaps.empty()) {
     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 << "      wrap->_setitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Mapping_Setitem;\n";
       out << "    }\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->_len_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Len;\n"
         "    wrap->_getitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Sequence_Getitem;\n";
         "    wrap->_getitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Sequence_Getitem;\n";
       if (!property->_setter_remaps.empty()) {
       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";
         out << "      wrap->_setitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Sequence_Setitem;\n";
         if (property->_inserter != nullptr) {
         if (property->_inserter != nullptr) {
           out << "      wrap->_insert_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Sequence_insert;\n";
           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_interface.cxx"
 #include "interrogate_request.cxx"
 #include "interrogate_request.cxx"
 #include "py_panda.cxx"
 #include "py_panda.cxx"
+#include "py_compat.cxx"
 #include "py_wrappers.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
  * @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
  * Template function that can be used to extract any TypedObject pointer from
  * a wrapped Python object.
  * a wrapped Python object.
  */
  */
 template<class T> INLINE bool
 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());
     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;
   return false;
 }
 }
 
 
@@ -73,7 +107,7 @@ Dtool_CheckNoArgs(PyObject *args) {
 ALWAYS_INLINE bool
 ALWAYS_INLINE bool
 Dtool_CheckNoArgs(PyObject *args, PyObject *kwds) {
 Dtool_CheckNoArgs(PyObject *args, PyObject *kwds) {
   return PyTuple_GET_SIZE(args) == 0 &&
   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
 #ifdef HAVE_PYTHON
 
 
-PyTupleObject Dtool_EmptyTuple;
-
 PyMemberDef standard_type_members[] = {
 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", (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"},
   {(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 RuntimeTypeSet runtime_type_set;
 static NamedTypeMap named_type_map;
 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) {
 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 {
   } 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.
  * was of the wrong type, raises an AttributeError.
  */
  */
 bool Dtool_Call_ExtractThisPointer(PyObject *self, Dtool_PyTypedObject &classdef, void **answer) {
 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.");
     Dtool_Raise_TypeError("C++ object is not yet constructed, or already destructed.");
     return false;
     return false;
   }
   }
 
 
-  *answer = ((Dtool_PyInstDef *)self)->_My_Type->_Dtool_UpcastInterface(self, &classdef);
+  *answer = DtoolInstance_UPCAST(self, classdef);
   return true;
   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,
 bool Dtool_Call_ExtractThisPointer_NonConst(PyObject *self, Dtool_PyTypedObject &classdef,
                                             void **answer, const char *method_name) {
                                             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.");
     Dtool_Raise_TypeError("C++ object is not yet constructed, or already destructed.");
     return false;
     return false;
   }
   }
 
 
-  if (((Dtool_PyInstDef *)self)->_is_const) {
+  if (DtoolInstance_IS_CONST(self)) {
     // All overloads of this function are non-const.
     // All overloads of this function are non-const.
     PyErr_Format(PyExc_TypeError,
     PyErr_Format(PyExc_TypeError,
                  "Cannot call %s() on a const object.",
                  "Cannot call %s() on a const object.",
@@ -136,7 +81,7 @@ bool Dtool_Call_ExtractThisPointer_NonConst(PyObject *self, Dtool_PyTypedObject
     return false;
     return false;
   }
   }
 
 
-  *answer = ((Dtool_PyInstDef *)self)->_My_Type->_Dtool_UpcastInterface(self, &classdef);
+  *answer = DtoolInstance_UPCAST(self, classdef);
   return true;
   return true;
 }
 }
 
 
@@ -166,19 +111,19 @@ void *
 DTOOL_Call_GetPointerThisClass(PyObject *self, Dtool_PyTypedObject *classdef,
 DTOOL_Call_GetPointerThisClass(PyObject *self, Dtool_PyTypedObject *classdef,
                                int param, const string &function_name, bool const_ok,
                                int param, const string &function_name, bool const_ok,
                                bool report_errors) {
                                bool report_errors) {
-  // if (PyErr_Occurred()) { return NULL; }
-  if (self == NULL) {
+  // if (PyErr_Occurred()) { return nullptr; }
+  if (self == nullptr) {
     if (report_errors) {
     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;
         return result;
       }
       }
 
 
@@ -187,7 +132,7 @@ DTOOL_Call_GetPointerThisClass(PyObject *self, Dtool_PyTypedObject *classdef,
                             "%s() argument %d may not be const",
                             "%s() argument %d may not be const",
                             function_name.c_str(), param);
                             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 Dtool_Raise_ArgTypeError(self, param, function_name.c_str(), classdef->_PyType.tp_name);
   }
   }
 
 
-  return NULL;
+  return nullptr;
 }
 }
 
 
 void *DTOOL_Call_GetPointerThis(PyObject *self) {
 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)");
       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.
     // Initialize the base class of everything.
     Dtool_PyModuleClassInit_DTOOL_SUPER_BASE(NULL);
     Dtool_PyModuleClassInit_DTOOL_SUPER_BASE(NULL);
@@ -749,7 +692,7 @@ PyObject *Dtool_BorrowThisReference(PyObject *self, PyObject *args) {
   PyObject *to_in = NULL;
   PyObject *to_in = NULL;
   if (PyArg_UnpackTuple(args, "Dtool_BorrowThisReference", 2, 2, &to_in, &from_in)) {
   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 *from = (Dtool_PyInstDef *) from_in;
       Dtool_PyInstDef *to = (Dtool_PyInstDef *) to_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) {
 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;
   return -1;
 }
 }
@@ -969,14 +911,14 @@ bool Dtool_ExtractArg(PyObject **result, PyObject *args, PyObject *kwds,
                       const char *keyword) {
                       const char *keyword) {
 
 
   if (PyTuple_GET_SIZE(args) == 1) {
   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);
       *result = PyTuple_GET_ITEM(args, 0);
       return true;
       return true;
     }
     }
   } else if (PyTuple_GET_SIZE(args) == 0) {
   } else if (PyTuple_GET_SIZE(args) == 0) {
     PyObject *key;
     PyObject *key;
     Py_ssize_t ppos = 0;
     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)) {
         PyDict_Next(kwds, &ppos, &key, result)) {
       // We got the item, we just need to make sure that it had the right key.
       // We got the item, we just need to make sure that it had the right key.
 #if PY_VERSION_HEX >= 0x03060000
 #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) {
 bool Dtool_ExtractArg(PyObject **result, PyObject *args, PyObject *kwds) {
   if (PyTuple_GET_SIZE(args) == 1 &&
   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);
     *result = PyTuple_GET_ITEM(args, 0);
     return true;
     return true;
   }
   }
@@ -1015,12 +957,12 @@ bool Dtool_ExtractOptionalArg(PyObject **result, PyObject *args, PyObject *kwds,
                               const char *keyword) {
                               const char *keyword) {
 
 
   if (PyTuple_GET_SIZE(args) == 1) {
   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);
       *result = PyTuple_GET_ITEM(args, 0);
       return true;
       return true;
     }
     }
   } else if (PyTuple_GET_SIZE(args) == 0) {
   } 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;
       PyObject *key;
       Py_ssize_t ppos = 0;
       Py_ssize_t ppos = 0;
       if (!PyDict_Next(kwds, &ppos, &key, result)) {
       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.
  * Variant of Dtool_ExtractOptionalArg that does not accept a keyword argument.
  */
  */
 bool Dtool_ExtractOptionalArg(PyObject **result, PyObject *args, PyObject *kwds) {
 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;
     return false;
   }
   }
   if (PyTuple_GET_SIZE(args) == 1) {
   if (PyTuple_GET_SIZE(args) == 1) {

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

@@ -20,131 +20,15 @@
 #define  Py_DEBUG
 #define  Py_DEBUG
 #endif
 #endif
 
 
-#ifndef NO_RUNTIME_TYPES
-
-#include "dtoolbase.h"
-#include "typedObject.h"
-#include "typeRegistry.h"
-
-#endif
-
 #include "pnotify.h"
 #include "pnotify.h"
 #include "vector_uchar.h"
 #include "vector_uchar.h"
 
 
 #if defined(HAVE_PYTHON) && !defined(CPPPARSER)
 #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"
 #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;
 using namespace std;
 
 
 // this is tempory .. untill this is glued better into the panda build system
 // 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
 #else // NDEBUG
 #define Define_Dtool_FreeInstance_Private(CLASS_NAME,CNAME)\
 #define Define_Dtool_FreeInstance_Private(CLASS_NAME,CNAME)\
 static void Dtool_FreeInstance_##CLASS_NAME(PyObject *self) {\
 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) {\
     if (((Dtool_PyInstDef *)self)->_memory_rules) {\
       cerr << "Detected leak for " << #CLASS_NAME \
       cerr << "Detected leak for " << #CLASS_NAME \
            << " which interrogate cannot delete.\n"; \
            << " which interrogate cannot delete.\n"; \
@@ -255,9 +139,9 @@ static void Dtool_FreeInstance_##CLASS_NAME(PyObject *self) {\
 
 
 #define Define_Dtool_FreeInstance(CLASS_NAME,CNAME)\
 #define Define_Dtool_FreeInstance(CLASS_NAME,CNAME)\
 static void Dtool_FreeInstance_##CLASS_NAME(PyObject *self) {\
 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) {\
     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);\
   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)\
 #define Define_Dtool_FreeInstanceRef(CLASS_NAME,CNAME)\
 static void Dtool_FreeInstance_##CLASS_NAME(PyObject *self) {\
 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) {\
     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);\
   Py_TYPE(self)->tp_free(self);\
@@ -279,8 +163,17 @@ static void Dtool_FreeInstance_##CLASS_NAME(PyObject *self) {\
   Py_TYPE(self)->tp_free(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
 // ** HACK ** allert.. Need to keep a runtime type dictionary ... that is
 // forward declared of typed object.  We rely on the fact that typed objects
 // 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,
 EXPCL_INTERROGATEDB bool Dtool_Call_ExtractThisPointer_NonConst(PyObject *self, Dtool_PyTypedObject &classdef,
                                                               void **answer, const char *method_name);
                                                               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.
 // Functions related to error reporting.
 EXPCL_INTERROGATEDB bool _Dtool_CheckErrorOccurred();
 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
 #ifdef NDEBUG
-#define Dtool_CheckErrorOccurred() (UNLIKELY(_PyErr_OCCURRED() != NULL))
+#define Dtool_CheckErrorOccurred() (UNLIKELY(_PyErr_OCCURRED() != nullptr))
 #else
 #else
 #define Dtool_CheckErrorOccurred() (UNLIKELY(_Dtool_CheckErrorOccurred()))
 #define Dtool_CheckErrorOccurred() (UNLIKELY(_Dtool_CheckErrorOccurred()))
 #endif
 #endif
@@ -348,8 +235,8 @@ EXPCL_INTERROGATEDB PyObject *Dtool_Return_Bool(bool value);
 EXPCL_INTERROGATEDB PyObject *_Dtool_Return(PyObject *value);
 EXPCL_INTERROGATEDB PyObject *_Dtool_Return(PyObject *value);
 
 
 #ifdef NDEBUG
 #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
 #else
 #define Dtool_Return_None() _Dtool_Return_None()
 #define Dtool_Return_None() _Dtool_Return_None()
 #define Dtool_Return(value) _Dtool_Return(value)
 #define Dtool_Return(value) _Dtool_Return(value)

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

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

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

@@ -16,6 +16,8 @@
 
 
 #include "py_panda.h"
 #include "py_panda.h"
 
 
+#ifdef HAVE_PYTHON
+
 /**
 /**
  * These classes are returned from properties that require a subscript
  * These classes are returned from properties that require a subscript
  * interface, ie. something.children[i] = 3.
  * 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_NewGenerator(PyObject *self, const char *name, iternextfunc func);
 EXPCL_INTERROGATEDB PyObject *Dtool_NewStaticProperty(PyTypeObject *obj, const PyGetSetDef *getset);
 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
     SetOutPath $INSTDIR\plugins
     File /nonfatal /r "${BUILT}\plugins\*.dle"
     File /nonfatal /r "${BUILT}\plugins\*.dle"
     File /nonfatal /r "${BUILT}\plugins\*.dlo"
     File /nonfatal /r "${BUILT}\plugins\*.dlo"
+    File /nonfatal /r "${BUILT}\plugins\*.ms"
     File "${SOURCE}\doc\INSTALLING-PLUGINS.TXT"
     File "${SOURCE}\doc\INSTALLING-PLUGINS.TXT"
 SectionEnd
 SectionEnd
 !endif
 !endif
@@ -601,7 +602,6 @@ Section "Maya plug-ins" SecMayaPlugins
     SetOutPath $INSTDIR\plugins
     SetOutPath $INSTDIR\plugins
     File /nonfatal /r "${BUILT}\plugins\*.mll"
     File /nonfatal /r "${BUILT}\plugins\*.mll"
     File /nonfatal /r "${BUILT}\plugins\*.mel"
     File /nonfatal /r "${BUILT}\plugins\*.mel"
-    File /nonfatal /r "${BUILT}\plugins\*.ms"
     File "${SOURCE}\doc\INSTALLING-PLUGINS.TXT"
     File "${SOURCE}\doc\INSTALLING-PLUGINS.TXT"
 SectionEnd
 SectionEnd
 !endif
 !endif

+ 4 - 2
makepanda/makepanda.py

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

+ 0 - 1
makepanda/makewheel.py

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

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

@@ -20,8 +20,7 @@ AudioLoadRequest(AudioManager *audio_manager, const string &filename,
                  bool positional) :
                  bool positional) :
   _audio_manager(audio_manager),
   _audio_manager(audio_manager),
   _filename(filename),
   _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.
  * 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
  * When this returns true, you may retrieve the sound loaded by calling
  * get_sound().
  * get_sound().
+ * Equivalent to `req.done() and not req.cancelled()`.
+ * @see done()
  */
  */
 INLINE bool AudioLoadRequest::
 INLINE bool AudioLoadRequest::
 is_ready() const {
 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
  * 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.
  * true.
  * @deprecated Use result() instead.
  * @deprecated Use result() instead.
  */
  */
 INLINE AudioSound *AudioLoadRequest::
 INLINE AudioSound *AudioLoadRequest::
 get_sound() const {
 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::
 AsyncTask::DoneStatus AudioLoadRequest::
 do_task() {
 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.
   // Don't continue the task; we're done.
   return DS_done;
   return DS_done;

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

@@ -43,8 +43,6 @@ PUBLISHED:
   INLINE bool is_ready() const;
   INLINE bool is_ready() const;
   INLINE AudioSound *get_sound() const;
   INLINE AudioSound *get_sound() const;
 
 
-  INLINE AudioSound *result() const;
-
 protected:
 protected:
   virtual DoneStatus do_task();
   virtual DoneStatus do_task();
 
 
@@ -53,9 +51,6 @@ private:
   string _filename;
   string _filename;
   bool _positional;
   bool _positional;
 
 
-  bool _is_ready;
-  PT(AudioSound) _sound;
-
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {
     return _type_handle;
     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
  * license.  You should have received a copy of this license along
  * with this source code in a file named "LICENSE."
  * 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 {
 get_display_id() const {
   return _display;
   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 {
 class CocoaGraphicsPipe : public GraphicsPipe {
 public:
 public:
-  CocoaGraphicsPipe();
-  CocoaGraphicsPipe(CGDirectDisplayID display);
-  CocoaGraphicsPipe(NSScreen *screen);
+  CocoaGraphicsPipe(CGDirectDisplayID display = CGMainDisplayID());
   virtual ~CocoaGraphicsPipe();
   virtual ~CocoaGraphicsPipe();
 
 
   INLINE CGDirectDisplayID get_display_id() const;
   INLINE CGDirectDisplayID get_display_id() const;
-  INLINE NSScreen *get_nsscreen() const;
 
 
   virtual string get_interface_name() const;
   virtual string get_interface_name() const;
   static PT(GraphicsPipe) pipe_constructor();
   static PT(GraphicsPipe) pipe_constructor();
@@ -64,11 +61,8 @@ protected:
 private:
 private:
   void load_display_information();
   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;
   CGDirectDisplayID _display;
-  NSScreen *_screen;
 
 
   friend class CocoaGraphicsWindow;
   friend class CocoaGraphicsWindow;
 
 

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

@@ -12,7 +12,7 @@
  */
  */
 
 
 #include "cocoaGraphicsPipe.h"
 #include "cocoaGraphicsPipe.h"
-// #include "cocoaGraphicsBuffer.h"
+#include "cocoaGraphicsBuffer.h"
 #include "cocoaGraphicsWindow.h"
 #include "cocoaGraphicsWindow.h"
 #include "cocoaGraphicsStateGuardian.h"
 #include "cocoaGraphicsStateGuardian.h"
 #include "cocoaPandaApp.h"
 #include "cocoaPandaApp.h"
@@ -30,104 +30,32 @@
 
 
 TypeHandle CocoaGraphicsPipe::_type_handle;
 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() {
+CocoaGraphicsPipe(CGDirectDisplayID display) : _display(display) {
   _supported_types = OT_window | OT_buffer | OT_texture_buffer;
   _supported_types = OT_window | OT_buffer | OT_texture_buffer;
   _is_valid = true;
   _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_width = CGDisplayPixelsWide(_display);
   _display_height = CGDisplayPixelsHigh(_display);
   _display_height = CGDisplayPixelsHigh(_display);
   load_display_information();
   load_display_information();
 
 
   cocoadisplay_cat.debug()
   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);
                                    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 (retry == 1) {
-    if (!gl_support_fbo || host == NULL ||
+    if (!gl_support_fbo ||
         (flags & (BF_require_parasite | BF_require_window)) != 0) {
         (flags & (BF_require_parasite | BF_require_window)) != 0) {
       return NULL;
       return NULL;
     }
     }
@@ -334,33 +264,14 @@ make_output(const string &name,
         precertify = true;
         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.
   // Nothing else left to try.
   return NULL;
   return NULL;

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

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

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

@@ -24,6 +24,10 @@
 #define kCGLRendererIDMatchingMask   0x00FE7F00
 #define kCGLRendererIDMatchingMask   0x00FE7F00
 #endif
 #endif
 
 
+#ifndef NSAppKitVersionNumber10_7
+#define NSAppKitVersionNumber10_7 1138
+#endif
+
 TypeHandle CocoaGraphicsStateGuardian::_type_handle;
 TypeHandle CocoaGraphicsStateGuardian::_type_handle;
 
 
 /**
 /**
@@ -207,7 +211,8 @@ choose_pixel_format(const FrameBufferProperties &properties,
     attribs.push_back(NSOpenGLPFAAccelerated);
     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) {
   if (need_pbuffer) {
     attribs.push_back(NSOpenGLPFAPixelBuffer);
     attribs.push_back(NSOpenGLPFAPixelBuffer);
@@ -217,6 +222,16 @@ choose_pixel_format(const FrameBufferProperties &properties,
   attribs.push_back(NSOpenGLPFAScreenMask);
   attribs.push_back(NSOpenGLPFAScreenMask);
   attribs.push_back(CGDisplayIDToOpenGLDisplayMask(display));
   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
   // End of the array
   attribs.push_back((NSOpenGLPixelFormatAttribute)0);
   attribs.push_back((NSOpenGLPixelFormatAttribute)0);
 
 

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

@@ -65,6 +65,16 @@ CocoaGraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe,
   _fullscreen_mode = NULL;
   _fullscreen_mode = NULL;
   _windowed_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 device =
     GraphicsWindowInputDevice::pointer_and_keyboard(this, "keyboard_mouse");
     GraphicsWindowInputDevice::pointer_and_keyboard(this, "keyboard_mouse");
   add_input_device(device);
   add_input_device(device);
@@ -144,7 +154,7 @@ begin_frame(FrameMode mode, Thread *current_thread) {
   nassertr(_view != nil, false);
   nassertr(_view != nil, false);
 
 
   // Place a lock on the context.
   // Place a lock on the context.
-  CGLLockContext((CGLContextObj) [cocoagsg->_context CGLContextObj]);
+  cocoagsg->lock_context();
 
 
   // Set the drawable.
   // Set the drawable.
   if (_properties.get_fullscreen()) {
   if (_properties.get_fullscreen()) {
@@ -210,7 +220,7 @@ end_frame(FrameMode mode, Thread *current_thread) {
   CocoaGraphicsStateGuardian *cocoagsg;
   CocoaGraphicsStateGuardian *cocoagsg;
   DCAST_INTO_V(cocoagsg, _gsg);
   DCAST_INTO_V(cocoagsg, _gsg);
 
 
-  CGLUnlockContext((CGLContextObj) [cocoagsg->_context CGLContextObj]);
+  cocoagsg->unlock_context();
 
 
   if (mode == FM_render) {
   if (mode == FM_render) {
     // end_render_texture();
     // end_render_texture();
@@ -239,7 +249,7 @@ end_flip() {
     CocoaGraphicsStateGuardian *cocoagsg;
     CocoaGraphicsStateGuardian *cocoagsg;
     DCAST_INTO_V(cocoagsg, _gsg);
     DCAST_INTO_V(cocoagsg, _gsg);
 
 
-    CGLLockContext((CGLContextObj) [cocoagsg->_context CGLContextObj]);
+    cocoagsg->lock_context();
 
 
     // Swap the front and back buffer.
     // Swap the front and back buffer.
     [cocoagsg->_context flushBuffer];
     [cocoagsg->_context flushBuffer];
@@ -247,7 +257,7 @@ end_flip() {
     // Flush the window
     // Flush the window
     [[_view window] flushWindow];
     [[_view window] flushWindow];
 
 
-    CGLUnlockContext((CGLContextObj) [cocoagsg->_context CGLContextObj]);
+    cocoagsg->unlock_context();
   }
   }
   GraphicsWindow::end_flip();
   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
   // 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
   // future, in the case of -1, it should use the origin used in a previous
   // run of Panda
   // run of Panda
@@ -406,7 +426,7 @@ open_window() {
   if (parent_nsview != NULL) {
   if (parent_nsview != NULL) {
     container = [parent_nsview bounds];
     container = [parent_nsview bounds];
   } else {
   } else {
-    container = [cocoa_pipe->_screen frame];
+    container = [screen frame];
     container.origin = NSMakePoint(0, 0);
     container.origin = NSMakePoint(0, 0);
   }
   }
   int x = _properties.get_x_origin();
   int x = _properties.get_x_origin();
@@ -453,7 +473,7 @@ open_window() {
     _window = [[CocoaPandaWindow alloc]
     _window = [[CocoaPandaWindow alloc]
                initWithContentRect: rect
                initWithContentRect: rect
                styleMask:windowStyle
                styleMask:windowStyle
-               screen:cocoa_pipe->_screen
+               screen:screen
                window:this];
                window:this];
 
 
     if (_window == nil) {
     if (_window == nil) {
@@ -464,7 +484,7 @@ open_window() {
   }
   }
 
 
   // Lock the context, so we can safely operate on it.
   // Lock the context, so we can safely operate on it.
-  CGLLockContext((CGLContextObj) [cocoagsg->_context CGLContextObj]);
+  cocoagsg->lock_context();
 
 
   // Create the NSView to render to.
   // Create the NSView to render to.
   NSRect rect = NSMakeRect(0, 0, _properties.get_x_size(), _properties.get_y_size());
   NSRect rect = NSMakeRect(0, 0, _properties.get_x_size(), _properties.get_y_size());
@@ -588,7 +608,7 @@ open_window() {
   cocoagsg->reset_if_new();
   cocoagsg->reset_if_new();
 
 
   // Release the context.
   // Release the context.
-  CGLUnlockContext((CGLContextObj) [cocoagsg->_context CGLContextObj]);
+  cocoagsg->unlock_context();
 
 
   if (!cocoagsg->is_valid()) {
   if (!cocoagsg->is_valid()) {
     close_window();
     close_window();
@@ -637,9 +657,9 @@ close_window() {
     cocoagsg = DCAST(CocoaGraphicsStateGuardian, _gsg);
     cocoagsg = DCAST(CocoaGraphicsStateGuardian, _gsg);
 
 
     if (cocoagsg != NULL && cocoagsg->_context != nil) {
     if (cocoagsg != NULL && cocoagsg->_context != nil) {
-      CGLLockContext((CGLContextObj) [cocoagsg->_context CGLContextObj]);
+      cocoagsg->lock_context();
       [cocoagsg->_context clearDrawable];
       [cocoagsg->_context clearDrawable];
-      CGLUnlockContext((CGLContextObj) [cocoagsg->_context CGLContextObj]);
+      cocoagsg->unlock_context();
     }
     }
     _gsg.clear();
     _gsg.clear();
   }
   }
@@ -1429,9 +1449,9 @@ handle_close_event() {
     cocoagsg = DCAST(CocoaGraphicsStateGuardian, _gsg);
     cocoagsg = DCAST(CocoaGraphicsStateGuardian, _gsg);
 
 
     if (cocoagsg != NULL && cocoagsg->_context != nil) {
     if (cocoagsg != NULL && cocoagsg->_context != nil) {
-      CGLLockContext((CGLContextObj) [cocoagsg->_context CGLContextObj]);
+      cocoagsg->lock_context();
       [cocoagsg->_context clearDrawable];
       [cocoagsg->_context clearDrawable];
-      CGLUnlockContext((CGLContextObj) [cocoagsg->_context CGLContextObj]);
+      cocoagsg->unlock_context();
     }
     }
     _gsg.clear();
     _gsg.clear();
   }
   }

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

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

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

@@ -1,4 +1,5 @@
 #include "config_cocoadisplay.mm"
 #include "config_cocoadisplay.mm"
+#include "cocoaGraphicsBuffer.mm"
 #include "cocoaGraphicsPipe.mm"
 #include "cocoaGraphicsPipe.mm"
 #include "cocoaGraphicsStateGuardian.mm"
 #include "cocoaGraphicsStateGuardian.mm"
 #include "cocoaGraphicsWindow.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
  * When the GraphicsOutput is in triggered copy mode, this function triggers
  * the copy (at the end of the next frame).
  * 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_left_dimensions.set(0.0f, 1.0f, 0.0f, 1.0f);
   _sbs_right_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;
   _delete_flag = false;
-  _trigger_copy = false;
 
 
   if (_fb_properties.is_single_buffered()) {
   if (_fb_properties.is_single_buffered()) {
     _draw_buffer_type = RenderBuffer::T_front;
     _draw_buffer_type = RenderBuffer::T_front;
@@ -276,12 +275,21 @@ add_render_texture(Texture *tex, RenderTextureMode mode,
 
 
   // Choose a default bitplane.
   // Choose a default bitplane.
   if (plane == RTP_COUNT) {
   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;
       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;
       plane = RTP_depth;
-    } else {
+      break;
+
+    default:
       plane = RTP_color;
       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;
   return okflag;
 }
 }

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

@@ -40,6 +40,7 @@
 #include "cycleDataWriter.h"
 #include "cycleDataWriter.h"
 #include "pipelineCycler.h"
 #include "pipelineCycler.h"
 #include "updateSeq.h"
 #include "updateSeq.h"
+#include "asyncFuture.h"
 
 
 class PNMImage;
 class PNMImage;
 class GraphicsEngine;
 class GraphicsEngine;
@@ -197,7 +198,7 @@ PUBLISHED:
   INLINE int get_child_sort() const;
   INLINE int get_child_sort() const;
   MAKE_PROPERTY(child_sort, get_child_sort, set_child_sort);
   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();
   INLINE DisplayRegion *make_display_region(PN_stdfloat l, PN_stdfloat r, PN_stdfloat b, PN_stdfloat t);
   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;
   int _target_tex_view;
   DisplayRegion *_prev_page_dr;
   DisplayRegion *_prev_page_dr;
   PT(GeomNode) _texture_card;
   PT(GeomNode) _texture_card;
-  bool _trigger_copy;
+  PT(AsyncFuture) _trigger_copy;
 
 
   class RenderTexture {
   class RenderTexture {
   public:
   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
  * 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
  * 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
  * 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) {
 async_reload_texture(TextureContext *tc) {
-  nassertv(_loader != (Loader *)NULL);
+  nassertr(_loader != nullptr, nullptr);
 
 
   int priority = 0;
   int priority = 0;
-  if (_current_display_region != (DisplayRegion *)NULL) {
+  if (_current_display_region != nullptr) {
     priority = _current_display_region->get_texture_reload_priority();
     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.
   // See if we are already loading this task.
   AsyncTaskCollection orig_tasks = task_mgr->find_tasks(task_name);
   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);
     AsyncTask *task = orig_tasks.get_task(ti);
     if (task->is_exact_type(TextureReloadRequest::get_class_type()) &&
     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,
       // This texture is already queued to be reloaded.  Don't queue it again,
       // just make sure the priority is updated, and return.
       // just make sure the priority is updated, and return.
       task->set_priority(max(task->get_priority(), priority));
       task->set_priority(max(task->get_priority(), priority));
-      return;
+      return (AsyncFuture *)task;
     }
     }
   }
   }
 
 
@@ -3220,6 +3220,7 @@ async_reload_texture(TextureContext *tc) {
                              _supports_compressed_texture);
                              _supports_compressed_texture);
   request->set_priority(priority);
   request->set_priority(priority);
   _loader->load_async(request);
   _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_unclipped_state();
   static CPT(RenderState) get_untextured_state();
   static CPT(RenderState) get_untextured_state();
 
 
-  void async_reload_texture(TextureContext *tc);
+  AsyncFuture *async_reload_texture(TextureContext *tc);
 
 
 protected:
 protected:
   PT(SceneSetup) _scene_null;
   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
  * license.  You should have received a copy of this license along
  * with this source code in a file named "LICENSE."
  * with this source code in a file named "LICENSE."
  *
  *
- * @file asyncTask_ext.h
+ * @file asyncFuture_ext.h
  * @author rdb
  * @author rdb
  * @date 2017-10-29
  * @date 2017-10-29
  */
  */
 
 
-#ifndef ASYNCTASK_EXT_H
-#define ASYNCTASK_EXT_H
+#ifndef ASYNCFUTURE_EXT_H
+#define ASYNCFUTURE_EXT_H
 
 
 #include "extension.h"
 #include "extension.h"
 #include "py_panda.h"
 #include "py_panda.h"
@@ -21,15 +21,21 @@
 #ifdef HAVE_PYTHON
 #ifdef HAVE_PYTHON
 
 
 /**
 /**
- * Extension class for AsyncTask
+ * Extension class for AsyncFuture
  */
  */
 template<>
 template<>
-class Extension<AsyncTask> : public ExtensionBase<AsyncTask> {
+class Extension<AsyncFuture> : public ExtensionBase<AsyncFuture> {
 public:
 public:
   static PyObject *__await__(PyObject *self);
   static PyObject *__await__(PyObject *self);
   static PyObject *__iter__(PyObject *self) { return __await__(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  // 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;
   _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
  * Returns the amount of time elapsed during the task's previous run cycle, in
  * seconds.
  * seconds.

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

@@ -35,7 +35,6 @@ AsyncTask(const string &name) :
   _priority(0),
   _priority(0),
   _state(S_inactive),
   _state(S_inactive),
   _servicing_thread(NULL),
   _servicing_thread(NULL),
-  _manager(NULL),
   _chain(NULL),
   _chain(NULL),
   _start_time(0.0),
   _start_time(0.0),
   _start_frame(0),
   _start_frame(0),
@@ -68,11 +67,27 @@ AsyncTask::
  * S_inactive (or possible S_servicing_removed).  This is a no-op if the state
  * S_inactive (or possible S_servicing_removed).  This is a no-op if the state
  * is already S_inactive.
  * is already S_inactive.
  */
  */
-void AsyncTask::
+bool AsyncTask::
 remove() {
 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::
 AsyncTask::DoneStatus AsyncTask::
 unlock_and_do_task() {
 unlock_and_do_task() {
-  nassertr(_manager != (AsyncTaskManager *)NULL, DS_done);
+  nassertr(_manager != nullptr, DS_done);
   PT(ClockObject) clock = _manager->get_clock();
   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();
   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.
   // It's important to release the lock while the task is being serviced.
   _manager->_lock.release();
   _manager->_lock.release();
@@ -403,11 +433,36 @@ unlock_and_do_task() {
 
 
   _chain->_time_in_frame += _dt;
   _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;
   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
  * Override this function to return true if the task can be successfully
  * executed, false if it cannot.  Mainly intended as a sanity check when
  * 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
  * task has been removed because it exited normally (returning DS_done), or
  * false if it was removed for some other reason (e.g.
  * false if it was removed for some other reason (e.g.
  * AsyncTaskManager::remove()).  By the time this method is called, _manager
  * 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.
  * 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.
  * This function is called with the lock *not* held.
  */
  */
 void AsyncTask::
 void AsyncTask::
 upon_death(AsyncTaskManager *manager, bool clean_exit) {
 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) {
   if (manager != (AsyncTaskManager *)NULL) {
     string remove_name = manager->get_name() + "-removeTask";
     string remove_name = manager->get_name() + "-removeTask";
     PT_Event event = new Event(remove_name);
     PT_Event event = new Event(remove_name);

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

@@ -15,8 +15,8 @@
 #define ASYNCTASK_H
 #define ASYNCTASK_H
 
 
 #include "pandabase.h"
 #include "pandabase.h"
-
-#include "asyncTaskBase.h"
+#include "asyncFuture.h"
+#include "namable.h"
 #include "pmutex.h"
 #include "pmutex.h"
 #include "conditionVar.h"
 #include "conditionVar.h"
 #include "pStatCollector.h"
 #include "pStatCollector.h"
@@ -29,7 +29,7 @@ class AsyncTaskChain;
  * Normally, you would subclass from this class, and override do_task(), to
  * Normally, you would subclass from this class, and override do_task(), to
  * define the functionality you wish to have the task perform.
  * 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:
 public:
   AsyncTask(const string &name = string());
   AsyncTask(const string &name = string());
   ALLOC_DELETED_CHAIN(AsyncTask);
   ALLOC_DELETED_CHAIN(AsyncTask);
@@ -62,7 +62,7 @@ PUBLISHED:
   INLINE bool is_alive() const;
   INLINE bool is_alive() const;
   INLINE AsyncTaskManager *get_manager() const;
   INLINE AsyncTaskManager *get_manager() const;
 
 
-  void remove();
+  bool remove();
 
 
   INLINE void set_delay(double delay);
   INLINE void set_delay(double delay);
   INLINE void clear_delay();
   INLINE void clear_delay();
@@ -92,7 +92,6 @@ PUBLISHED:
   INLINE int get_priority() const;
   INLINE int get_priority() const;
 
 
   INLINE void set_done_event(const string &done_event);
   INLINE void set_done_event(const string &done_event);
-  INLINE const string &get_done_event() const;
 
 
   INLINE double get_dt() const;
   INLINE double get_dt() const;
   INLINE double get_max_dt() const;
   INLINE double get_max_dt() const;
@@ -100,13 +99,13 @@ PUBLISHED:
 
 
   virtual void output(ostream &out) const;
   virtual void output(ostream &out) const;
 
 
-  EXTENSION(static PyObject *__await__(PyObject *self));
-  EXTENSION(static PyObject *__iter__(PyObject *self));
-
 protected:
 protected:
   void jump_to_task_chain(AsyncTaskManager *manager);
   void jump_to_task_chain(AsyncTaskManager *manager);
   DoneStatus unlock_and_do_task();
   DoneStatus unlock_and_do_task();
 
 
+  virtual bool cancel() FINAL;
+  virtual bool is_task() const FINAL {return true;}
+
   virtual bool is_runnable();
   virtual bool is_runnable();
   virtual DoneStatus do_task();
   virtual DoneStatus do_task();
   virtual void upon_birth(AsyncTaskManager *manager);
   virtual void upon_birth(AsyncTaskManager *manager);
@@ -120,11 +119,9 @@ protected:
   double _wake_time;
   double _wake_time;
   int _sort;
   int _sort;
   int _priority;
   int _priority;
-  string _done_event;
 
 
   State _state;
   State _state;
   Thread *_servicing_thread;
   Thread *_servicing_thread;
-  AsyncTaskManager *_manager;
   AsyncTaskChain *_chain;
   AsyncTaskChain *_chain;
 
 
   double _start_time;
   double _start_time;
@@ -135,9 +132,6 @@ protected:
   double _total_dt;
   double _total_dt;
   int _num_frames;
   int _num_frames;
 
 
-  // Tasks waiting for this one to complete.
-  pvector<PT(AsyncTask)> _waiting_tasks;
-
   static AtomicAdjust::Integer _next_task_id;
   static AtomicAdjust::Integer _next_task_id;
 
 
   static PStatCollector _show_code_pcollector;
   static PStatCollector _show_code_pcollector;
@@ -150,9 +144,9 @@ public:
     return _type_handle;
     return _type_handle;
   }
   }
   static void init_type() {
   static void init_type() {
-    AsyncTaskBase::init_type();
+    AsyncFuture::init_type();
     register_type(_type_handle, "AsyncTask",
     register_type(_type_handle, "AsyncTask",
-                  AsyncTaskBase::get_class_type());
+                  AsyncFuture::get_class_type());
   }
   }
   virtual TypeHandle get_type() const {
   virtual TypeHandle get_type() const {
     return get_class_type();
     return get_class_type();
@@ -162,6 +156,7 @@ public:
 private:
 private:
   static TypeHandle _type_handle;
   static TypeHandle _type_handle;
 
 
+  friend class AsyncFuture;
   friend class AsyncTaskManager;
   friend class AsyncTaskManager;
   friend class AsyncTaskChain;
   friend class AsyncTaskChain;
   friend class AsyncTaskSequence;
   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
  * Removes the indicated task from this chain.  Returns true if removed, false
  * otherwise.  Assumes the lock is already held.  The task->upon_death()
  * 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::
 bool AsyncTaskChain::
-do_remove(AsyncTask *task) {
-  bool removed = false;
-
+do_remove(AsyncTask *task, bool upon_death) {
   nassertr(task->_chain == this, false);
   nassertr(task->_chain == this, false);
 
 
   switch (task->_state) {
   switch (task->_state) {
   case AsyncTask::S_servicing:
   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;
     task->_state = AsyncTask::S_servicing_removed;
-    removed = true;
-    break;
+    return true;
 
 
   case AsyncTask::S_servicing_removed:
   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:
   case AsyncTask::S_sleeping:
     // Sleeping, easy.
     // Sleeping, easy.
@@ -482,10 +479,9 @@ do_remove(AsyncTask *task) {
       nassertr(index != -1, false);
       nassertr(index != -1, false);
       _sleeping.erase(_sleeping.begin() + index);
       _sleeping.erase(_sleeping.begin() + index);
       make_heap(_sleeping.begin(), _sleeping.end(), AsyncTaskSortWakeTime());
       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:
   case AsyncTask::S_active:
     {
     {
@@ -503,15 +499,15 @@ do_remove(AsyncTask *task) {
           nassertr(index != -1, false);
           nassertr(index != -1, false);
         }
         }
       }
       }
-      removed = true;
-      cleanup_task(task, false, false);
+      cleanup_task(task, upon_death, false);
+      return true;
     }
     }
 
 
   default:
   default:
     break;
     break;
   }
   }
 
 
-  return removed;
+  return false;
 }
 }
 
 
 /**
 /**
@@ -776,27 +772,19 @@ cleanup_task(AsyncTask *task, bool upon_death, bool clean_exit) {
   PT(AsyncTask) hold_task = task;
   PT(AsyncTask) hold_task = task;
 
 
   task->_state = AsyncTask::S_inactive;
   task->_state = AsyncTask::S_inactive;
-  task->_chain = NULL;
-  task->_manager = NULL;
+  task->_chain = nullptr;
   --_num_tasks;
   --_num_tasks;
   --(_manager->_num_tasks);
   --(_manager->_num_tasks);
 
 
   _manager->remove_task_by_name(task);
   _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) {
   if (upon_death) {
     _manager->_lock.release();
     _manager->_lock.release();
     task->upon_death(_manager, clean_exit);
     task->upon_death(_manager, clean_exit);

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

@@ -96,7 +96,7 @@ protected:
   typedef pvector< PT(AsyncTask) > TaskHeap;
   typedef pvector< PT(AsyncTask) > TaskHeap;
 
 
   void do_add(AsyncTask *task);
   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_wait_for_tasks();
   void do_cleanup();
   void do_cleanup();
 
 
@@ -206,6 +206,7 @@ public:
 private:
 private:
   static TypeHandle _type_handle;
   static TypeHandle _type_handle;
 
 
+  friend class AsyncFuture;
   friend class AsyncTaskChainThread;
   friend class AsyncTaskChainThread;
   friend class AsyncTask;
   friend class AsyncTask;
   friend class AsyncTaskManager;
   friend class AsyncTaskManager;

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

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

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

@@ -307,25 +307,20 @@ find_tasks_matching(const GlobPattern &pattern) const {
  */
  */
 bool AsyncTaskManager::
 bool AsyncTaskManager::
 remove(AsyncTask *task) {
 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
  * Removes all of the tasks in the AsyncTaskCollection.  Returns the number of
  * tasks removed.
  * tasks removed.
  */
  */
-int AsyncTaskManager::
+size_t AsyncTaskManager::
 remove(const AsyncTaskCollection &tasks) {
 remove(const AsyncTaskCollection &tasks) {
   MutexHolder holder(_lock);
   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);
     PT(AsyncTask) task = tasks.get_task(i);
 
 
     if (task->_manager != this) {
     if (task->_manager != this) {
@@ -337,10 +332,7 @@ remove(const AsyncTaskCollection &tasks) {
         task_cat.debug()
         task_cat.debug()
           << "Removing " << *task << "\n";
           << "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;
         ++num_removed;
       } else {
       } else {
         if (task_cat.is_debug()) {
         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;
   AsyncTaskCollection find_tasks_matching(const GlobPattern &pattern) const;
 
 
   bool remove(AsyncTask *task);
   bool remove(AsyncTask *task);
-  int remove(const AsyncTaskCollection &tasks);
+  size_t remove(const AsyncTaskCollection &tasks);
 
 
   BLOCKING void wait_for_tasks();
   BLOCKING void wait_for_tasks();
   BLOCKING void stop_threads();
   BLOCKING void stop_threads();
   void start_threads();
   void start_threads();
 
 
-  INLINE int get_num_tasks() const;
+  INLINE size_t get_num_tasks() const;
 
 
   AsyncTaskCollection get_tasks() const;
   AsyncTaskCollection get_tasks() const;
   AsyncTaskCollection get_active_tasks() const;
   AsyncTaskCollection get_active_tasks() const;
@@ -126,7 +126,7 @@ protected:
   typedef ov_set<PT(AsyncTaskChain), IndirectCompareNames<AsyncTaskChain> > TaskChains;
   typedef ov_set<PT(AsyncTaskChain), IndirectCompareNames<AsyncTaskChain> > TaskChains;
   TaskChains _task_chains;
   TaskChains _task_chains;
 
 
-  int _num_tasks;
+  size_t _num_tasks;
   TasksByName _tasks_by_name;
   TasksByName _tasks_by_name;
   PT(ClockObject) _clock;
   PT(ClockObject) _clock;
 
 
@@ -151,6 +151,7 @@ public:
 private:
 private:
   static TypeHandle _type_handle;
   static TypeHandle _type_handle;
 
 
+  friend class AsyncFuture;
   friend class AsyncTaskChain;
   friend class AsyncTaskChain;
   friend class AsyncTaskChain::AsyncTaskChainThread;
   friend class AsyncTaskChain::AsyncTaskChainThread;
   friend class AsyncTask;
   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 "config_event.h"
+#include "asyncFuture.h"
 #include "asyncTask.h"
 #include "asyncTask.h"
 #include "asyncTaskChain.h"
 #include "asyncTaskChain.h"
 #include "asyncTaskManager.h"
 #include "asyncTaskManager.h"
@@ -31,6 +32,8 @@ NotifyCategoryDef(event, "");
 NotifyCategoryDef(task, "");
 NotifyCategoryDef(task, "");
 
 
 ConfigureFn(config_event) {
 ConfigureFn(config_event) {
+  AsyncFuture::init_type();
+  AsyncGatheringFuture::init_type();
   AsyncTask::init_type();
   AsyncTask::init_type();
   AsyncTaskChain::init_type();
   AsyncTaskChain::init_type();
   AsyncTaskManager::init_type();
   AsyncTaskManager::init_type();

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

@@ -27,6 +27,26 @@ EventHandler::
 EventHandler(EventQueue *ev_queue) : _queue(*ev_queue) {
 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
  * The main processing loop of the EventHandler.  This function must be called
  * periodically to service events.  Walks through each pending event and calls
  * 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);
       ((*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 "event.h"
 #include "pt_Event.h"
 #include "pt_Event.h"
+#include "asyncFuture.h"
 
 
 #include "pset.h"
 #include "pset.h"
 #include "pmap.h"
 #include "pmap.h"
@@ -41,10 +42,13 @@ public:
 
 
 PUBLISHED:
 PUBLISHED:
   explicit EventHandler(EventQueue *ev_queue);
   explicit EventHandler(EventQueue *ev_queue);
+  ~EventHandler() {}
+
+  AsyncFuture *get_future(const string &event_name);
 
 
   void process_events();
   void process_events();
 
 
-  virtual void dispatch_event(const Event *);
+  virtual void dispatch_event(const Event *event);
 
 
   void write(ostream &out) const;
   void write(ostream &out) const;
 
 
@@ -74,9 +78,11 @@ protected:
   typedef pair<EventCallbackFunction*, void*> CallbackFunction;
   typedef pair<EventCallbackFunction*, void*> CallbackFunction;
   typedef pset<CallbackFunction> CallbackFunctions;
   typedef pset<CallbackFunction> CallbackFunctions;
   typedef pmap<string, CallbackFunctions> CallbackHooks;
   typedef pmap<string, CallbackFunctions> CallbackHooks;
+  typedef pmap<string, PT(AsyncFuture)> Futures;
 
 
   Hooks _hooks;
   Hooks _hooks;
   CallbackHooks _cbhooks;
   CallbackHooks _cbhooks;
+  Futures _futures;
   EventQueue &_queue;
   EventQueue &_queue;
 
 
   static EventHandler *_global_event_handler;
   static EventHandler *_global_event_handler;

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

@@ -11,13 +11,6 @@
  * @date 1999-02-08
  * @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
  * Defines an EventParameter that stores a pointer to any kind of
  * TypedWritableReferenceCount object.  This is the most general constructor.
  * TypedWritableReferenceCount object.  This is the most general constructor.

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

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

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

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

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

@@ -45,17 +45,12 @@ get_owner() const {
  */
  */
 INLINE void PythonTask::
 INLINE void PythonTask::
 set_result(PyObject *result) {
 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(is_alive());
+  nassertv(!done());
   nassertv(_exception == nullptr);
   nassertv(_exception == nullptr);
   Py_INCREF(result);
   Py_INCREF(result);
   Py_XDECREF(_exc_value);
   Py_XDECREF(_exc_value);
   _exc_value = result;
   _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
 #ifndef CPPPARSER
 extern struct Dtool_PyTypedObject Dtool_TypedReferenceCount;
 extern struct Dtool_PyTypedObject Dtool_TypedReferenceCount;
-extern struct Dtool_PyTypedObject Dtool_AsyncTask;
+extern struct Dtool_PyTypedObject Dtool_AsyncFuture;
 extern struct Dtool_PyTypedObject Dtool_PythonTask;
 extern struct Dtool_PyTypedObject Dtool_PythonTask;
 #endif
 #endif
 
 
@@ -45,6 +45,7 @@ PythonTask(PyObject *func_or_coro, const string &name) :
   _exc_traceback(nullptr),
   _exc_traceback(nullptr),
   _generator(nullptr),
   _generator(nullptr),
   _future_done(nullptr),
   _future_done(nullptr),
+  _ignore_return(false),
   _retrieved_exception(false) {
   _retrieved_exception(false) {
 
 
   nassertv(func_or_coro != nullptr);
   nassertv(func_or_coro != nullptr);
@@ -169,9 +170,7 @@ get_args() {
     }
     }
 
 
     this->ref();
     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);
     PyTuple_SET_ITEM(with_task, num_args, self);
     return with_task;
     return with_task;
 
 
@@ -238,8 +237,8 @@ set_owner(PyObject *owner) {
  * exception occurred within this task, it is raised instead.
  * exception occurred within this task, it is raised instead.
  */
  */
 PyObject *PythonTask::
 PyObject *PythonTask::
-result() const {
-  nassertr(!is_alive(), nullptr);
+get_result() const {
+  nassertr(done(), nullptr);
 
 
   if (_exception == nullptr) {
   if (_exception == nullptr) {
     // The result of the call is stored in _exc_value.
     // 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
  * Maps from an expression like "task.attr_name = v". This is customized here
  * so we can support some traditional task interfaces that supported directly
  * 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.
         // 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()) {
             if (task_cat.is_debug()) {
               task_cat.debug()
               task_cat.debug()
-                << *this << " is now awaiting <" << *task << ">.\n";
+                << *this << " is now awaiting <" << *fut << ">.\n";
             }
             }
-            task->_waiting_tasks.push_back(this);
           } else {
           } else {
             // The task is already done.  Continue at next opportunity.
             // 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);
             Py_DECREF(result);
-            manager->_lock.release();
             return DS_cont;
             return DS_cont;
           }
           }
         } else {
         } else {
@@ -658,14 +617,12 @@ do_python_task() {
           task_cat.error()
           task_cat.error()
             << *this << " cannot await itself\n";
             << *this << " cannot await itself\n";
         }
         }
-        task->_manager->_lock.release();
         Py_DECREF(result);
         Py_DECREF(result);
         return DS_await;
         return DS_await;
       }
       }
-
     } else {
     } 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");
       PyObject *check = PyObject_GetAttrString(result, "_asyncio_future_blocking");
       if (check != nullptr && check != Py_None) {
       if (check != nullptr && check != Py_None) {
         Py_DECREF(check);
         Py_DECREF(check);
@@ -680,7 +637,7 @@ do_python_task() {
         if (task_cat.is_debug()) {
         if (task_cat.is_debug()) {
           PyObject *str = PyObject_ASCII(result);
           PyObject *str = PyObject_ASCII(result);
           task_cat.debug()
           task_cat.debug()
-            << *this << " is now awaiting " << PyUnicode_AsUTF8(str) << ".\n";
+            << *this << " is now polling " << PyUnicode_AsUTF8(str) << ".done()\n";
           Py_DECREF(str);
           Py_DECREF(str);
         }
         }
 #endif
 #endif
@@ -707,7 +664,7 @@ do_python_task() {
     return DS_interrupt;
     return DS_interrupt;
   }
   }
 
 
-  if (result == Py_None) {
+  if (result == Py_None || _ignore_return) {
     Py_DECREF(result);
     Py_DECREF(result);
     return DS_done;
     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;
   ostringstream strm;
 #if PY_MAJOR_VERSION >= 3
 #if PY_MAJOR_VERSION >= 3
   PyObject *str = PyObject_ASCII(result);
   PyObject *str = PyObject_ASCII(result);
@@ -886,15 +861,10 @@ void PythonTask::
 call_function(PyObject *function) {
 call_function(PyObject *function) {
   if (function != Py_None) {
   if (function != Py_None) {
     this->ref();
     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_XDECREF(result);
-    Py_DECREF(args);
+    Py_DECREF(self);
   }
   }
 }
 }
 
 

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

@@ -20,12 +20,13 @@
 
 
 #ifdef HAVE_PYTHON
 #ifdef HAVE_PYTHON
 #include "py_panda.h"
 #include "py_panda.h"
+#include "extension.h"
 
 
 /**
 /**
  * This class exists to allow association of a Python function or coroutine
  * This class exists to allow association of a Python function or coroutine
  * with the AsyncTaskManager.
  * with the AsyncTaskManager.
  */
  */
-class PythonTask : public AsyncTask {
+class PythonTask FINAL : public AsyncTask {
 PUBLISHED:
 PUBLISHED:
   PythonTask(PyObject *function = Py_None, const string &name = string());
   PythonTask(PyObject *function = Py_None, const string &name = string());
   virtual ~PythonTask();
   virtual ~PythonTask();
@@ -44,12 +45,14 @@ PUBLISHED:
   INLINE PyObject *get_owner() const;
   INLINE PyObject *get_owner() const;
 
 
   INLINE void set_result(PyObject *result);
   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 __setattr__(PyObject *self, PyObject *attr, PyObject *v);
   int __delattr__(PyObject *self, PyObject *attr);
   int __delattr__(PyObject *self, PyObject *attr);
   PyObject *__getattr__(PyObject *attr) const;
   PyObject *__getattr__(PyObject *attr) const;
@@ -101,8 +104,6 @@ protected:
   virtual void upon_death(AsyncTaskManager *manager, bool clean_exit);
   virtual void upon_death(AsyncTaskManager *manager, bool clean_exit);
 
 
 private:
 private:
-  static PyObject *gen_next(PyObject *self);
-
   void register_to_owner();
   void register_to_owner();
   void unregister_from_owner();
   void unregister_from_owner();
   void call_owner_method(const char *method_name);
   void call_owner_method(const char *method_name);
@@ -122,9 +123,12 @@ private:
   PyObject *_future_done;
   PyObject *_future_done;
 
 
   bool _append_task;
   bool _append_task;
+  bool _ignore_return;
   bool _registered_to_owner;
   bool _registered_to_owner;
   mutable bool _retrieved_exception;
   mutable bool _retrieved_exception;
 
 
+  friend class Extension<AsyncFuture>;
+
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {
     return _type_handle;
     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
   // Now construct the internal list by copying the elements one-at-a-time
   // from Python.
   // 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");
   PyObject *push_back = PyDict_GetItemString(dict, "push_back");
   if (push_back == NULL) {
   if (push_back == NULL) {
     PyErr_BadArgument();
     PyErr_BadArgument();
@@ -105,7 +105,7 @@ __init__(PyObject *self, PyObject *source) {
   }
   }
 
 
   // We need to initialize the this pointer before we can call push_back.
   // 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);
   Py_ssize_t size = PySequence_Size(source);
   this->_this->reserve(size);
   this->_this->reserve(size);
@@ -203,34 +203,37 @@ set_data(PyObject *data) {
     }
     }
 
 
     PyBuffer_Release(&view);
     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,
       PyErr_Format(PyExc_ValueError,
-                   "str object is not a multiple of %zu bytes",
+                   "byte buffer is not a multiple of %zu bytes",
                    sizeof(Element));
                    sizeof(Element));
       return;
       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 {
     } else {
       this->_this->clear();
       this->_this->clear();
     }
     }
-  } else {
-    Dtool_Raise_TypeError("PointerToArray.set_data() requires a str");
+
+    return;
   }
   }
 #endif
 #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;
     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.
   // 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 (_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.
         // We also need to rebuild if we need to change size.
         _needs_rebuild = true;
         _needs_rebuild = true;
       }
       }
@@ -356,7 +364,7 @@ rebuild_bitplanes() {
 
 
   // Calculate bitplane size.  This can be larger than the buffer.
   // Calculate bitplane size.  This can be larger than the buffer.
   if (_creation_flags & GraphicsPipe::BF_size_track_host) {
   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(),
       set_size_and_recalc(_host->get_x_size(),
                           _host->get_y_size());
                           _host->get_y_size());
     }
     }
@@ -1253,7 +1261,11 @@ end_frame(FrameMode mode, Thread *current_thread) {
     generate_mipmaps();
     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) {
   if (mode == FM_render) {
     trigger_flip();
     trigger_flip();
@@ -1315,8 +1327,11 @@ bool CLP(GraphicsBuffer)::
 open_buffer() {
 open_buffer() {
   report_my_gl_errors();
   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.
   // Count total color buffers.
   int totalcolor =
   int totalcolor =
@@ -1491,8 +1506,10 @@ open_buffer() {
   _fb_properties.set_back_buffers(0);
   _fb_properties.set_back_buffers(0);
   _fb_properties.set_indexed_color(0);
   _fb_properties.set_indexed_color(0);
   _fb_properties.set_rgb_color(1);
   _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;
   _is_valid = true;
   _needs_rebuild = true;
   _needs_rebuild = true;
@@ -1508,7 +1525,7 @@ open_buffer() {
  */
  */
 GraphicsOutput *CLP(GraphicsBuffer)::
 GraphicsOutput *CLP(GraphicsBuffer)::
 get_host() {
 get_host() {
-  return _host;
+  return (_host != nullptr) ? _host : this;
 }
 }
 
 
 /**
 /**
@@ -1699,7 +1716,7 @@ report_my_errors(int line, const char *file) {
  */
  */
 void CLP(GraphicsBuffer)::
 void CLP(GraphicsBuffer)::
 check_host_valid() {
 check_host_valid() {
-  if ((_host == 0)||(!_host->is_valid())) {
+  if (_host != nullptr && !_host->is_valid()) {
     _rb_data_size_bytes = 0;
     _rb_data_size_bytes = 0;
     if (_rb_context != NULL) {
     if (_rb_context != NULL) {
       // We must delete this object first, because when the GSG destructs, so
       // 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);
   void report_my_errors(int line, const char *file);
 
 
-private:
-
   void bind_slot(int layer, bool rb_resize, Texture **attach,
   void bind_slot(int layer, bool rb_resize, Texture **attach,
                  RenderTexturePlane plane, GLenum attachpoint);
                  RenderTexturePlane plane, GLenum attachpoint);
   void bind_slot_multisample(bool rb_resize, Texture **attach,
   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.
 // drawing GUIs and such.
 static const string default_vshader =
 static const string default_vshader =
 #ifndef OPENGLES
 #ifndef OPENGLES
+#ifdef __APPLE__ // Apple's GL 3.2 contexts require at least GLSL 1.50.
+  "#version 150\n"
+#else
   "#version 130\n"
   "#version 130\n"
+#endif
   "in vec4 p3d_Vertex;\n"
   "in vec4 p3d_Vertex;\n"
   "in vec4 p3d_Color;\n"
   "in vec4 p3d_Color;\n"
   "in vec2 p3d_MultiTexCoord0;\n"
   "in vec2 p3d_MultiTexCoord0;\n"
@@ -179,7 +183,11 @@ static const string default_vshader =
 
 
 static const string default_fshader =
 static const string default_fshader =
 #ifndef OPENGLES
 #ifndef OPENGLES
+#ifdef __APPLE__  // Apple's GL 3.2 contexts require at least GLSL 1.50.
+  "#version 150\n"
+#else
   "#version 130\n"
   "#version 130\n"
+#endif
   "in vec2 texcoord;\n"
   "in vec2 texcoord;\n"
   "in vec4 color;\n"
   "in vec4 color;\n"
   "out vec4 p3d_FragColor;\n"
   "out vec4 p3d_FragColor;\n"
@@ -6455,6 +6463,15 @@ framebuffer_copy_to_ram(Texture *tex, int view, int z,
     }
     }
     break;
     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:
   default:
     if (_current_properties->get_srgb_color()) {
     if (_current_properties->get_srgb_color()) {
       if (_current_properties->get_alpha_bits()) {
       if (_current_properties->get_alpha_bits()) {
@@ -12314,20 +12331,20 @@ upload_texture_image(CLP(TextureContext) *gtc, bool needs_reload,
               if (_supports_clear_texture) {
               if (_supports_clear_texture) {
                 // We can do that with the convenient glClearTexImage
                 // We can do that with the convenient glClearTexImage
                 // function.
                 // function.
-                string clear_data = tex->get_clear_data();
+                vector_uchar clear_data = tex->get_clear_data();
 
 
                 _glClearTexImage(gtc->_index, n - mipmap_bias, external_format,
                 _glClearTexImage(gtc->_index, n - mipmap_bias, external_format,
-                                 component_type, (void *)clear_data.data());
+                                 component_type, (void *)&clear_data[0]);
                 continue;
                 continue;
               }
               }
             } else {
             } else {
               if (_supports_clear_buffer) {
               if (_supports_clear_buffer) {
                 // For buffer textures we need to clear the underlying
                 // For buffer textures we need to clear the underlying
                 // storage.
                 // storage.
-                string clear_data = tex->get_clear_data();
+                vector_uchar clear_data = tex->get_clear_data();
 
 
                 _glClearBufferData(GL_TEXTURE_BUFFER, internal_format, external_format,
                 _glClearBufferData(GL_TEXTURE_BUFFER, internal_format, external_format,
-                                   component_type, (const void *)clear_data.data());
+                                   component_type, (const void *)&clear_data[0]);
                 continue;
                 continue;
               }
               }
             }
             }

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

@@ -16,15 +16,16 @@
  */
  */
 INLINE AnimateVerticesRequest::
 INLINE AnimateVerticesRequest::
 AnimateVerticesRequest(GeomVertexData *geom_vertex_data) :
 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.
  * 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::
 INLINE bool AnimateVerticesRequest::
 is_ready() const {
 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
   // 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.
   // the result and it will be used later in the rendering process.
   _geom_vertex_data->animate_vertices(true, current_thread);
   _geom_vertex_data->animate_vertices(true, current_thread);
-  _is_ready = true;
 
 
   // Don't continue the task; we're done.
   // Don't continue the task; we're done.
   return AsyncTask::DS_done;
   return AsyncTask::DS_done;

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

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

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

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

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

@@ -27,6 +27,8 @@
 #include "config_gobj.h"
 #include "config_gobj.h"
 #include "throw_event.h"
 #include "throw_event.h"
 
 
+TypeHandle PreparedGraphicsObjects::EnqueuedObject::_type_handle;
+
 int PreparedGraphicsObjects::_name_index = 0;
 int PreparedGraphicsObjects::_name_index = 0;
 
 
 /**
 /**
@@ -191,7 +193,25 @@ void PreparedGraphicsObjects::
 enqueue_texture(Texture *tex) {
 enqueue_texture(Texture *tex) {
   ReMutexHolder holder(_lock);
   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);
   EnqueuedTextures::iterator qi = _enqueued_textures.find(tex);
   if (qi != _enqueued_textures.end()) {
   if (qi != _enqueued_textures.end()) {
+    if (qi->second != nullptr) {
+      qi->second->notify_removed();
+    }
     _enqueued_textures.erase(qi);
     _enqueued_textures.erase(qi);
     return true;
     return true;
   }
   }
@@ -291,6 +314,17 @@ release_all_textures() {
   }
   }
 
 
   _prepared_textures.clear();
   _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();
   _enqueued_textures.clear();
 
 
   return num_textures;
   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).
  * when the GSG is next ready to do this (presumably at the next frame).
  */
  */
 void PreparedGraphicsObjects::
 void PreparedGraphicsObjects::
-enqueue_shader(Shader *se) {
+enqueue_shader(Shader *shader) {
   ReMutexHolder holder(_lock);
   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);
   EnqueuedShaders::iterator qi = _enqueued_shaders.find(se);
   if (qi != _enqueued_shaders.end()) {
   if (qi != _enqueued_shaders.end()) {
+    if (qi->second != nullptr) {
+      qi->second->notify_removed();
+    }
     _enqueued_shaders.erase(qi);
     _enqueued_shaders.erase(qi);
     return true;
     return true;
   }
   }
@@ -759,6 +814,17 @@ release_all_shaders() {
   }
   }
 
 
   _prepared_shaders.clear();
   _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();
   _enqueued_shaders.clear();
 
 
   return num_shaders;
   return num_shaders;
@@ -1358,6 +1424,73 @@ prepare_shader_buffer_now(ShaderBuffer *data, GraphicsStateGuardianBase *gsg) {
   return bc;
   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
  * This is called by the GraphicsStateGuardian to indicate that it is about to
  * begin processing of the frame.
  * begin processing of the frame.
@@ -1446,11 +1579,15 @@ begin_frame(GraphicsStateGuardianBase *gsg, Thread *current_thread) {
   for (qti = _enqueued_textures.begin();
   for (qti = _enqueued_textures.begin();
        qti != _enqueued_textures.end();
        qti != _enqueued_textures.end();
        ++qti) {
        ++qti) {
-    Texture *tex = (*qti);
+    Texture *tex = qti->first;
+    TextureContext *first_tc = nullptr;
     for (int view = 0; view < tex->get_num_views(); ++view) {
     for (int view = 0; view < tex->get_num_views(); ++view) {
       TextureContext *tc = tex->prepare_now(view, this, gsg);
       TextureContext *tc = tex->prepare_now(view, this, gsg);
-      if (tc != (TextureContext *)NULL) {
+      if (tc != nullptr) {
         gsg->update_texture(tc, true);
         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();
   for (qsi = _enqueued_shaders.begin();
        qsi != _enqueued_shaders.end();
        qsi != _enqueued_shaders.end();
        ++qsi) {
        ++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();
   _enqueued_shaders.clear();

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

@@ -29,6 +29,7 @@
 #include "reMutex.h"
 #include "reMutex.h"
 #include "bufferResidencyTracker.h"
 #include "bufferResidencyTracker.h"
 #include "adaptiveLru.h"
 #include "adaptiveLru.h"
+#include "asyncFuture.h"
 
 
 class TextureContext;
 class TextureContext;
 class SamplerContext;
 class SamplerContext;
@@ -38,6 +39,7 @@ class VertexBufferContext;
 class IndexBufferContext;
 class IndexBufferContext;
 class BufferContext;
 class BufferContext;
 class GraphicsStateGuardianBase;
 class GraphicsStateGuardianBase;
+class SavedContext;
 
 
 /**
 /**
  * A table of objects that are saved within the graphics context for reference
  * A table of objects that are saved within the graphics context for reference
@@ -158,6 +160,56 @@ PUBLISHED:
                             GraphicsStateGuardianBase *gsg);
                             GraphicsStateGuardianBase *gsg);
 
 
 public:
 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,
   void begin_frame(GraphicsStateGuardianBase *gsg,
                    Thread *current_thread);
                    Thread *current_thread);
   void end_frame(Thread *current_thread);
   void end_frame(Thread *current_thread);
@@ -167,11 +219,11 @@ private:
 
 
 private:
 private:
   typedef phash_set<TextureContext *, pointer_hash> Textures;
   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<GeomContext *, pointer_hash> Geoms;
   typedef phash_set< PT(Geom) > EnqueuedGeoms;
   typedef phash_set< PT(Geom) > EnqueuedGeoms;
   typedef phash_set<ShaderContext *, pointer_hash> Shaders;
   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<BufferContext *, pointer_hash> Buffers;
   typedef phash_set< PT(GeomVertexArrayData) > EnqueuedVertexBuffers;
   typedef phash_set< PT(GeomVertexArrayData) > EnqueuedVertexBuffers;
   typedef phash_set< PT(GeomPrimitive) > EnqueuedIndexBuffers;
   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
  * Use this function instead of prepare_now() to preload textures from a user
  * interface standpoint.
  * interface standpoint.
  */
  */
-void Shader::
+PT(AsyncFuture) Shader::
 prepare(PreparedGraphicsObjects *prepared_objects) {
 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_LVecBase3.h"
 #include "pta_LVecBase2.h"
 #include "pta_LVecBase2.h"
 #include "epvector.h"
 #include "epvector.h"
+#include "asyncFuture.h"
 
 
 #ifdef HAVE_CG
 #ifdef HAVE_CG
 // I don't want to include the Cg header file into panda as a whole.  Instead,
 // 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 bool get_cache_compiled_shader() const;
   INLINE void set_cache_compiled_shader(bool flag);
   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 is_prepared(PreparedGraphicsObjects *prepared_objects) const;
   bool release(PreparedGraphicsObjects *prepared_objects);
   bool release(PreparedGraphicsObjects *prepared_objects);
   int release_all();
   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
  * Returns the raw image data for a single pixel if it were set to the clear
  * color.
  * color.
  */
  */
-INLINE string Texture::
+INLINE vector_uchar Texture::
 get_clear_data() const {
 get_clear_data() const {
   CDReader cdata(_cycler);
   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;
   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,
  * Returns true if the indicated filename ends in .txo or .txo.pz or .txo.gz,
  * false otherwise.
  * 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
  * Use this function instead of prepare_now() to preload textures from a user
  * interface standpoint.
  * interface standpoint.
  */
  */
-void Texture::
+PT(AsyncFuture) Texture::
 prepare(PreparedGraphicsObjects *prepared_objects) {
 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,
   return convert_to_pnmimage(pnmimage,
                              do_get_expected_mipmap_x_size(cdata, n),
                              do_get_expected_mipmap_x_size(cdata, n),
                              do_get_expected_mipmap_y_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,
                              cdata->_ram_images[n]._image,
                              do_get_ram_mipmap_page_size(cdata, n), z);
                              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) {
   if (cdata->_component_type != T_float) {
     // PfmFile by way of PNMImage.
     // PfmFile by way of PNMImage.
     PNMImage 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) {
     if (!success) {
       return false;
       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->_has_clear_color, 0);
   nassertr(cdata->_num_components <= 4, 0);
   nassertr(cdata->_num_components <= 4, 0);
 
 
-  // TODO: encode the color into the sRGB color space if used
   switch (cdata->_component_type) {
   switch (cdata->_component_type) {
   case T_unsigned_byte:
   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());
       LColor scaled = cdata->_clear_color.fmin(LColor(1)).fmax(LColor::zero());
       scaled *= 255;
       scaled *= 255;
       switch (cdata->_num_components) {
       switch (cdata->_num_components) {
@@ -8036,25 +8058,28 @@ convert_from_pfm(PTA_uchar &image, size_t page_size, int z,
  */
  */
 bool Texture::
 bool Texture::
 convert_to_pnmimage(PNMImage &pnmimage, int x_size, int y_size,
 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;
   xelval maxval = 0xff;
-  if (component_width > 1) {
+  if (component_type != T_unsigned_byte && component_type != T_byte) {
     maxval = 0xffff;
     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 has_alpha = pnmimage.has_alpha();
   bool is_grayscale = pnmimage.is_grayscale();
   bool is_grayscale = pnmimage.is_grayscale();
 
 
   int idx = page_size * z;
   int idx = page_size * z;
   nassertr(idx + page_size <= image.size(), false);
   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) {
     if (is_grayscale) {
+      const unsigned char *p = &image[idx];
       if (has_alpha) {
       if (has_alpha) {
-        xelval *alpha = pnmimage.get_alpha_array();
         for (int j = y_size-1; j >= 0; j--) {
         for (int j = y_size-1; j >= 0; j--) {
           xel *row = array + j * x_size;
           xel *row = array + j * x_size;
           xelval *alpha_row = alpha + 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 {
     } else {
+      const unsigned char *p = &image[idx];
       if (has_alpha) {
       if (has_alpha) {
-        xelval *alpha = pnmimage.get_alpha_array();
         for (int j = y_size-1; j >= 0; j--) {
         for (int j = y_size-1; j >= 0; j--) {
           xel *row = array + j * x_size;
           xel *row = array + j * x_size;
           xelval *alpha_row = alpha + 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;
     return false;
   }
   }
 
 
-  nassertr(p == &image[idx] + page_size, false);
   return true;
   return true;
 }
 }
 
 

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

@@ -43,9 +43,10 @@
 #include "colorSpace.h"
 #include "colorSpace.h"
 #include "geomEnums.h"
 #include "geomEnums.h"
 #include "bamCacheRecord.h"
 #include "bamCacheRecord.h"
+#include "pnmImage.h"
+#include "pfmFile.h"
+#include "asyncFuture.h"
 
 
-class PNMImage;
-class PfmFile;
 class TextureContext;
 class TextureContext;
 class FactoryParams;
 class FactoryParams;
 class PreparedGraphicsObjects;
 class PreparedGraphicsObjects;
@@ -264,7 +265,7 @@ PUBLISHED:
   INLINE LColor get_clear_color() const;
   INLINE LColor get_clear_color() const;
   INLINE void set_clear_color(const LColor &color);
   INLINE void set_clear_color(const LColor &color);
   INLINE void clear_clear_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,
   MAKE_PROPERTY2(clear_color, has_clear_color, get_clear_color,
                               set_clear_color, clear_clear_color);
                               set_clear_color, clear_clear_color);
 
 
@@ -522,7 +523,7 @@ PUBLISHED:
   MAKE_PROPERTY(auto_texture_scale, get_auto_texture_scale,
   MAKE_PROPERTY(auto_texture_scale, get_auto_texture_scale,
                                     set_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 is_prepared(PreparedGraphicsObjects *prepared_objects) const;
   bool was_image_modified(PreparedGraphicsObjects *prepared_objects) const;
   bool was_image_modified(PreparedGraphicsObjects *prepared_objects) const;
   size_t get_data_size_bytes(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 z, const PfmFile &pfm,
                                int num_components, int component_width);
                                int num_components, int component_width);
   static bool convert_to_pnmimage(PNMImage &pnmimage, int x_size, int y_size,
   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,
                                   CPTA_uchar image, size_t page_size,
                                   int z);
                                   int z);
   static bool convert_to_pfm(PfmFile &pfm, int x_size, int y_size,
   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 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_byte(const unsigned char *&p);
   INLINE static double get_unsigned_short(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_txo_filename(const Filename &fullpath);
   INLINE static bool is_dds_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;
     _get_component = Texture::get_unsigned_short;
     break;
     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:
   default:
     // Not supported.
     // Not supported.
     _image.clear();
     _image.clear();
@@ -123,7 +135,6 @@ TexturePeeker(Texture *tex, Texture::CData *cdata) {
     break;
     break;
 
 
   case Texture::F_rgb:
   case Texture::F_rgb:
-  case Texture::F_srgb:
   case Texture::F_rgb5:
   case Texture::F_rgb5:
   case Texture::F_rgb8:
   case Texture::F_rgb8:
   case Texture::F_rgb12:
   case Texture::F_rgb12:
@@ -135,7 +146,6 @@ TexturePeeker(Texture *tex, Texture::CData *cdata) {
     break;
     break;
 
 
   case Texture::F_rgba:
   case Texture::F_rgba:
-  case Texture::F_srgb_alpha:
   case Texture::F_rgbm:
   case Texture::F_rgbm:
   case Texture::F_rgba4:
   case Texture::F_rgba4:
   case Texture::F_rgba5:
   case Texture::F_rgba5:
@@ -146,6 +156,25 @@ TexturePeeker(Texture *tex, Texture::CData *cdata) {
   case Texture::F_rgb10_a2:
   case Texture::F_rgb10_a2:
     _get_texel = get_texel_rgba;
     _get_texel = get_texel_rgba;
     break;
     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:
   default:
     // Not supported.
     // Not supported.
     gobj_cat.error() << "Unsupported texture peeker format: "
     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[0] = (*get_component)(p);
   color[3] = (*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_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_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_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 _x_size;
   int _y_size;
   int _y_size;

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

@@ -22,8 +22,7 @@ TextureReloadRequest(const string &name,
   AsyncTask(name),
   AsyncTask(name),
   _pgo(pgo),
   _pgo(pgo),
   _texture(texture),
   _texture(texture),
-  _allow_compressed(allow_compressed),
-  _is_ready(false)
+  _allow_compressed(allow_compressed)
 {
 {
   nassertv(_pgo != (PreparedGraphicsObjects *)NULL);
   nassertv(_pgo != (PreparedGraphicsObjects *)NULL);
   nassertv(_texture != (Texture *)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.
  * 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::
 INLINE bool TextureReloadRequest::
 is_ready() const {
 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);
       _texture->prepare(_pgo);
     }
     }
   }
   }
-  _is_ready = true;
 
 
   // Don't continue the task; we're done.
   // Don't continue the task; we're done.
   return DS_done;
   return DS_done;

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

@@ -43,6 +43,8 @@ PUBLISHED:
   INLINE bool get_allow_compressed() const;
   INLINE bool get_allow_compressed() const;
   INLINE bool is_ready() const;
   INLINE bool is_ready() const;
 
 
+  MAKE_PROPERTY(texture, get_texture);
+
 protected:
 protected:
   virtual DoneStatus do_task();
   virtual DoneStatus do_task();
 
 
@@ -50,7 +52,6 @@ private:
   PT(PreparedGraphicsObjects) _pgo;
   PT(PreparedGraphicsObjects) _pgo;
   PT(Texture) _texture;
   PT(Texture) _texture;
   bool _allow_compressed;
   bool _allow_compressed;
-  bool _is_ready;
 
 
 public:
 public:
   static TypeHandle get_class_type() {
   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);
   nassertv(compression != Texture::CM_default);
 
 
   // Check if perhaps a PointerToArray object was passed in.
   // 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;
       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;
       return;
     }
     }
   }
   }
@@ -87,6 +84,29 @@ set_ram_image(PyObject *image, Texture::CompressionMode compression,
   }
   }
 #endif
 #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");
   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>::
 void Extension<Texture>::
 set_ram_image_as(PyObject *image, const string &provided_format) {
 set_ram_image_as(PyObject *image, const string &provided_format) {
   // Check if perhaps a PointerToArray object was passed in.
   // 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;
       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;
       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_multisample_color(0.78f, 0.05f, 0.81f, 1.0f);
   static const LColor flash_dual_color(0.92, 0.01f, 0.01f, 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();
   bool force = !traverser->get_effective_incomplete_render();
   Thread *current_thread = traverser->get_current_thread();
   Thread *current_thread = traverser->get_current_thread();
   CullBinManager *bin_manager = CullBinManager::get_global_ptr();
   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) {
   if (bounds_viz != (Geom *)NULL) {
     _geoms_pcollector.add_level(2);
     _geoms_pcollector.add_level(2);
     CullableObject *outer_viz =
     CullableObject *outer_viz =
-      new CullableObject(move(bounds_viz), get_bounds_outer_viz_state(),
+      new CullableObject(bounds_viz, get_bounds_outer_viz_state(),
                          internal_transform);
                          internal_transform);
     _cull_handler->record_object(outer_viz, this);
     _cull_handler->record_object(outer_viz, this);
 
 

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

@@ -38,6 +38,7 @@
 #include "boundingBox.h"
 #include "boundingBox.h"
 #include "boundingSphere.h"
 #include "boundingSphere.h"
 #include "config_mathutil.h"
 #include "config_mathutil.h"
+#include "preparedGraphicsObjects.h"
 
 
 
 
 bool allow_flatten_color = ConfigVariableBool
 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();
     int num_arrays = vdata_reader.get_num_arrays();
     for (int i = 0; i < num_arrays; ++i) {
     for (int i = 0; i < num_arrays; ++i) {
       CPT(GeomVertexArrayData) array = vdata_reader.get_array(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.
     // And also each of the index arrays.
     int num_primitives = geom->get_num_primitives();
     int num_primitives = geom->get_num_primitives();
     for (int i = 0; i < num_primitives; ++i) {
     for (int i = 0; i < num_primitives; ++i) {
       CPT(GeomPrimitive) prim = geom->get_primitive(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())) {
     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));
         Texture *texture = ta->get_on_texture(ta->get_on_stage(i));
         // TODO: prepare the sampler states, if specified.
         // TODO: prepare the sampler states, if specified.
         if (texture != nullptr) {
         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)) {
     if (geom_state->get_attrib(sa)) {
       Shader *shader = (Shader *)sa->get_shader();
       Shader *shader = (Shader *)sa->get_shader();
       if (shader != nullptr) {
       if (shader != nullptr) {
-        shader->prepare(prepared_objects);
+        prepared_objects->enqueue_shader(shader);
       }
       }
       // TODO: prepare the shader inputs.
       // 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,
  * Removes a pending asynchronous load request.  Returns true if successful,
  * false otherwise.
  * false otherwise.
+ * @deprecated use task.cancel() to cancel the request instead.
  */
  */
 INLINE bool Loader::
 INLINE bool Loader::
 remove(AsyncTask *task) {
 remove(AsyncTask *task) {

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

@@ -18,8 +18,7 @@
 INLINE ModelFlattenRequest::
 INLINE ModelFlattenRequest::
 ModelFlattenRequest(PandaNode *orig) :
 ModelFlattenRequest(PandaNode *orig) :
   AsyncTask(orig->get_name()),
   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.
  * 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
  * When this returns true, you may retrieve the model loaded by calling
  * result().
  * result().
+ * Equivalent to `req.done() and not req.cancelled()`.
+ * @see done()
  */
  */
 INLINE bool ModelFlattenRequest::
 INLINE bool ModelFlattenRequest::
 is_ready() const {
 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
  * 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::
 INLINE PandaNode *ModelFlattenRequest::
 get_model() const {
 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.attach_new_node(_orig);
   }
   }
   np.flatten_strong();
   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.
   // Don't continue the task; we're done.
   return DS_done;
   return DS_done;

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

@@ -39,18 +39,13 @@ PUBLISHED:
   INLINE bool is_ready() const;
   INLINE bool is_ready() const;
   INLINE PandaNode *get_model() const;
   INLINE PandaNode *get_model() const;
 
 
-  INLINE NodePath result() const;
-
   MAKE_PROPERTY(orig, get_orig);
   MAKE_PROPERTY(orig, get_orig);
-  MAKE_PROPERTY(ready, is_ready);
 
 
 protected:
 protected:
   virtual DoneStatus do_task();
   virtual DoneStatus do_task();
 
 
 private:
 private:
   PT(PandaNode) _orig;
   PT(PandaNode) _orig;
-  bool _is_ready;
-  PT(PandaNode) _model;
 
 
 public:
 public:
   static TypeHandle get_class_type() {
   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::
 INLINE bool ModelLoadRequest::
 is_ready() const {
 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::
 INLINE PandaNode *ModelLoadRequest::
 get_model() const {
 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),
   AsyncTask(name),
   _filename(filename),
   _filename(filename),
   _options(options),
   _options(options),
-  _loader(loader),
-  _is_ready(false)
+  _loader(loader)
 {
 {
 }
 }
 
 
@@ -43,8 +42,8 @@ do_task() {
     Thread::sleep(delay);
     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.
   // Don't continue the task; we're done.
   return DS_done;
   return DS_done;

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

@@ -43,15 +43,12 @@ PUBLISHED:
   INLINE const LoaderOptions &get_options() const;
   INLINE const LoaderOptions &get_options() const;
   INLINE Loader *get_loader() const;
   INLINE Loader *get_loader() const;
 
 
-  INLINE NodePath result() const;
-
   INLINE bool is_ready() const;
   INLINE bool is_ready() const;
   INLINE PandaNode *get_model() const;
   INLINE PandaNode *get_model() const;
 
 
   MAKE_PROPERTY(filename, get_filename);
   MAKE_PROPERTY(filename, get_filename);
   MAKE_PROPERTY(options, get_options);
   MAKE_PROPERTY(options, get_options);
   MAKE_PROPERTY(loader, get_loader);
   MAKE_PROPERTY(loader, get_loader);
-  MAKE_PROPERTY(ready, is_ready);
 
 
 protected:
 protected:
   virtual DoneStatus do_task();
   virtual DoneStatus do_task();
@@ -60,8 +57,6 @@ private:
   Filename _filename;
   Filename _filename;
   LoaderOptions _options;
   LoaderOptions _options;
   PT(Loader) _loader;
   PT(Loader) _loader;
-  bool _is_ready;
-  PT(PandaNode) _model;
 
 
 public:
 public:
   static TypeHandle get_class_type() {
   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.
  * Returns true if this request has completed, false if it is still pending.
  * When this returns true, you may retrieve the success flag with
  * When this returns true, you may retrieve the success flag with
  * get_success().
  * get_success().
+ * Equivalent to `req.done() and not req.cancelled()`.
+ * @see done()
  */
  */
 INLINE bool ModelSaveRequest::
 INLINE bool ModelSaveRequest::
 is_ready() const {
 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
  * 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::
 INLINE bool ModelSaveRequest::
 get_success() const {
 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;
   return _success;
 }
 }

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

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

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

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

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

@@ -46,8 +46,7 @@ __init__(PyObject *self, PyObject *sequence) {
     }
     }
 
 
     NodePath *path;
     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.
       // Unable to add item--probably it wasn't of the appropriate type.
       ostringstream stream;
       ostringstream stream;
       stream << "Element " << i << " in sequence passed to NodePathCollection constructor is not a NodePath";
       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.
       // It's OK if there's no bamWriter.
       PyErr_Clear();
       PyErr_Clear();
     } else {
     } else {
-      DTOOL_Call_ExtractThisPointerForType(py_writer, &Dtool_BamWriter, (void **)&writer);
+      DtoolInstance_GetPointer(py_writer, writer, Dtool_BamWriter);
       Py_DECREF(py_writer);
       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.
       // It's OK if there's no bamReader.
       PyErr_Clear();
       PyErr_Clear();
     } else {
     } else {
-      DTOOL_Call_ExtractThisPointerForType(py_reader, &Dtool_BamReader, (void **)&reader);
+      DtoolInstance_GetPointer(py_reader, reader, Dtool_BamReader);
       Py_DECREF(py_reader);
       Py_DECREF(py_reader);
     }
     }
   }
   }
@@ -251,7 +251,7 @@ set_shader_input(CPT_InternalName name, PyObject *value, int priority) {
   }
   }
 
 
   ShaderInput &input = attrib->_inputs[name];
   ShaderInput &input = attrib->_inputs[name];
-  invoke_extension(&input).__init__(move(name), value);
+  invoke_extension(&input).__init__(move(name), value, priority);
 
 
   if (!_PyErr_OCCURRED()) {
   if (!_PyErr_OCCURRED()) {
     node->set_attrib(ShaderAttrib::return_new(attrib));
     node->set_attrib(ShaderAttrib::return_new(attrib));

Some files were not shown because too many files changed in this diff