Browse Source

Separate out CPython calls from core libs; eliminate need for pystub

rdb 9 years ago
parent
commit
47388b0dbc
100 changed files with 807 additions and 1093 deletions
  1. 1 1
      direct/src/dcparser/dcArrayParameter.h
  2. 1 1
      direct/src/dcparser/dcAtomicField.h
  3. 1 1
      direct/src/dcparser/dcClass.h
  4. 1 1
      direct/src/dcparser/dcClassParameter.h
  5. 1 1
      direct/src/dcparser/dcDeclaration.h
  6. 1 1
      direct/src/dcparser/dcField.h
  7. 1 1
      direct/src/dcparser/dcFile.h
  8. 1 1
      direct/src/dcparser/dcKeyword.h
  9. 1 1
      direct/src/dcparser/dcKeywordList.h
  10. 1 1
      direct/src/dcparser/dcMolecularField.h
  11. 1 1
      direct/src/dcparser/dcPackData.h
  12. 2 2
      direct/src/dcparser/dcPacker.h
  13. 1 1
      direct/src/dcparser/dcPackerCatalog.h
  14. 1 1
      direct/src/dcparser/dcPackerInterface.h
  15. 1 1
      direct/src/dcparser/dcParameter.h
  16. 1 1
      direct/src/dcparser/dcSimpleParameter.h
  17. 1 1
      direct/src/dcparser/dcSwitch.h
  18. 1 1
      direct/src/dcparser/dcSwitchParameter.h
  19. 1 1
      direct/src/dcparser/dcTypedef.h
  20. 22 2
      direct/src/distributed/cConnectionRepository.cxx
  21. 0 2
      direct/src/plugin/p3dCInstance.h
  22. 49 39
      direct/src/showbase/Loader.py
  23. 4 1
      direct/src/showutil/FreezeTool.py
  24. 10 10
      direct/src/stdpy/thread.py
  25. 48 4
      dtool/src/interrogate/interfaceMakerPythonNative.cxx
  26. 2 1
      dtool/src/interrogate/interfaceMakerPythonNative.h
  27. 0 4
      dtool/src/interrogate/interrogate.cxx
  28. 0 4
      dtool/src/interrogate/interrogate_module.cxx
  29. 1 1
      dtool/src/parser-inc/Python.h
  30. 0 1
      dtool/src/test_interrogate/test_interrogate.cxx
  31. 136 126
      makepanda/makepanda.py
  32. 0 4
      panda/src/android/pview.cxx
  33. 17 0
      panda/src/chan/animChannelFixed.cxx
  34. 2 0
      panda/src/chan/animChannelFixed.h
  35. 1 0
      panda/src/chan/p3chan_composite1.cxx
  36. 0 4
      panda/src/downloadertools/apply_patch.cxx
  37. 0 4
      panda/src/downloadertools/build_patch.cxx
  38. 0 4
      panda/src/downloadertools/check_adler.cxx
  39. 0 4
      panda/src/downloadertools/check_crc.cxx
  40. 0 4
      panda/src/downloadertools/check_md5.cxx
  41. 0 4
      panda/src/downloadertools/multify.cxx
  42. 0 4
      panda/src/downloadertools/pdecrypt.cxx
  43. 0 1
      panda/src/downloadertools/pencrypt.cxx
  44. 0 4
      panda/src/downloadertools/punzip.cxx
  45. 0 4
      panda/src/downloadertools/pzip.cxx
  46. 0 4
      panda/src/downloadertools/show_ddb.cxx
  47. 0 39
      panda/src/event/asyncTask.cxx
  48. 0 10
      panda/src/event/asyncTask.h
  49. 0 34
      panda/src/event/pythonTask.I
  50. 3 7
      panda/src/event/pythonTask.cxx
  51. 2 5
      panda/src/event/pythonTask.h
  52. 0 4
      panda/src/express/pointerToArray.h
  53. 12 6
      panda/src/express/pointerToArray_ext.I
  54. 0 4
      panda/src/express/pointerToArray_ext.h
  55. 1 1
      panda/src/gobj/geomVertexArrayData.h
  56. 10 6
      panda/src/gobj/geomVertexArrayData_ext.cxx
  57. 0 2
      panda/src/gobj/geomVertexArrayData_ext.h
  58. 0 20
      panda/src/nativenet/buffered_datagramconnection.cxx
  59. 4 0
      panda/src/pgraph/modelFlattenRequest.h
  60. 6 0
      panda/src/pgraph/modelLoadRequest.h
  61. 7 0
      panda/src/pgraph/modelSaveRequest.h
  62. 12 8
      panda/src/pgraph/nodePath.h
  63. 29 14
      panda/src/pgraph/nodePath_ext.I
  64. 1 1
      panda/src/pgraph/nodePath_ext.cxx
  65. 12 8
      panda/src/pgraph/nodePath_ext.h
  66. 4 1
      panda/src/pgraph/pandaNode.I
  67. 18 180
      panda/src/pgraph/pandaNode.cxx
  68. 18 11
      panda/src/pgraph/pandaNode.h
  69. 98 89
      panda/src/pgraph/pandaNode_ext.cxx
  70. 19 5
      panda/src/pgraph/pandaNode_ext.h
  71. 0 4
      panda/src/pipeline/config_pipeline.cxx
  72. 0 1
      panda/src/pipeline/p3pipeline_composite2.cxx
  73. 187 13
      panda/src/pipeline/pythonThread.cxx
  74. 10 1
      panda/src/pipeline/pythonThread.h
  75. 19 0
      panda/src/pipeline/thread.I
  76. 1 261
      panda/src/pipeline/thread.cxx
  77. 13 14
      panda/src/pipeline/thread.h
  78. 1 1
      panda/src/pnmimage/pfmFile.h
  79. 4 4
      panda/src/pnmimage/pfmFile_ext.cxx
  80. 0 2
      panda/src/pnmimage/pfmFile_ext.h
  81. 2 2
      panda/src/putil/bamReader_ext.cxx
  82. 2 3
      panda/src/putil/pythonCallbackObject.cxx
  83. 0 4
      panda/src/testbed/pgrid.cxx
  84. 0 4
      panda/src/testbed/pview.cxx
  85. 0 4
      pandatool/src/bam/bamInfo.cxx
  86. 0 4
      pandatool/src/bam/bamToEgg.cxx
  87. 0 4
      pandatool/src/bam/eggToBam.cxx
  88. 0 4
      pandatool/src/bam/ptsToBam.cxx
  89. 0 4
      pandatool/src/cvscopy/testCopy.cxx
  90. 0 4
      pandatool/src/daeprogs/daeToEgg.cxx
  91. 0 4
      pandatool/src/daeprogs/eggToDAE.cxx
  92. 0 4
      pandatool/src/dxfprogs/dxfPoints.cxx
  93. 0 4
      pandatool/src/dxfprogs/dxfToEgg.cxx
  94. 0 4
      pandatool/src/dxfprogs/eggToDXF.cxx
  95. 0 4
      pandatool/src/egg-mkfont/eggMakeFont.cxx
  96. 0 4
      pandatool/src/egg-optchar/eggOptchar.cxx
  97. 0 4
      pandatool/src/egg-palettize/eggPalettize.cxx
  98. 0 4
      pandatool/src/egg-qtess/eggQtess.cxx
  99. 0 4
      pandatool/src/eggprogs/eggCrop.cxx
  100. 0 4
      pandatool/src/eggprogs/eggListTextures.cxx

+ 1 - 1
direct/src/dcparser/dcArrayParameter.h

@@ -23,7 +23,7 @@
  * parameter type accepts an arbitrary (or possibly fixed) number of nested
  * fields, all of which are of the same type.
  */
-class EXPCL_DIRECT DCArrayParameter : public DCParameter {
+class DCArrayParameter : public DCParameter {
 public:
   DCArrayParameter(DCParameter *element_type,
                    const DCUnsignedIntRange &size = DCUnsignedIntRange());

+ 1 - 1
direct/src/dcparser/dcAtomicField.h

@@ -27,7 +27,7 @@
  * This defines an interface to the Distributed Class, and is always
  * implemented as a remote procedure method.
  */
-class EXPCL_DIRECT DCAtomicField : public DCField {
+class DCAtomicField : public DCField {
 public:
   DCAtomicField(const string &name, DCClass *dclass, bool bogus_field);
   virtual ~DCAtomicField();

+ 1 - 1
direct/src/dcparser/dcClass.h

@@ -41,7 +41,7 @@ class DCParameter;
 /**
  * Defines a particular DistributedClass as read from an input .dc file.
  */
-class EXPCL_DIRECT DCClass : public DCDeclaration {
+class DCClass : public DCDeclaration {
 public:
   DCClass(DCFile *dc_file, const string &name,
           bool is_struct, bool bogus_class);

+ 1 - 1
direct/src/dcparser/dcClassParameter.h

@@ -23,7 +23,7 @@ class DCClass;
  * This represents a class (or struct) object used as a parameter itself.
  * This means that all the fields of the class get packed into the message.
  */
-class EXPCL_DIRECT DCClassParameter : public DCParameter {
+class DCClassParameter : public DCParameter {
 public:
   DCClassParameter(const DCClass *dclass);
   DCClassParameter(const DCClassParameter &copy);

+ 1 - 1
direct/src/dcparser/dcDeclaration.h

@@ -26,7 +26,7 @@ class DCSwitch;
  * only purpose is so that classes and typedefs can be stored in one list
  * together so they can be ordered correctly on output.
  */
-class EXPCL_DIRECT DCDeclaration {
+class DCDeclaration {
 public:
   virtual ~DCDeclaration();
 

+ 1 - 1
direct/src/dcparser/dcField.h

@@ -34,7 +34,7 @@ class HashGenerator;
 /**
  * A single field of a Distributed Class, either atomic or molecular.
  */
-class EXPCL_DIRECT DCField : public DCPackerInterface, public DCKeywordList {
+class DCField : public DCPackerInterface, public DCKeywordList {
 public:
   DCField();
   DCField(const string &name, DCClass *dclass);

+ 1 - 1
direct/src/dcparser/dcFile.h

@@ -29,7 +29,7 @@ class DCDeclaration;
  * Represents the complete list of Distributed Class descriptions as read from
  * a .dc file.
  */
-class EXPCL_DIRECT DCFile {
+class DCFile {
 PUBLISHED:
   DCFile();
   ~DCFile();

+ 1 - 1
direct/src/dcparser/dcKeyword.h

@@ -25,7 +25,7 @@ class HashGenerator;
  * define a communication property associated with a field, for instance
  * "broadcast" or "airecv".
  */
-class EXPCL_DIRECT DCKeyword : public DCDeclaration {
+class DCKeyword : public DCDeclaration {
 public:
   DCKeyword(const string &name, int historical_flag = ~0);
   virtual ~DCKeyword();

+ 1 - 1
direct/src/dcparser/dcKeywordList.h

@@ -23,7 +23,7 @@ class HashGenerator;
  * This is a list of keywords (see DCKeyword) that may be set on a particular
  * field.
  */
-class EXPCL_DIRECT DCKeywordList {
+class DCKeywordList {
 public:
   DCKeywordList();
   DCKeywordList(const DCKeywordList &copy);

+ 1 - 1
direct/src/dcparser/dcMolecularField.h

@@ -25,7 +25,7 @@ class DCParameter;
  * This represents a combination of two or more related atomic fields, that
  * will often be treated as a unit.
  */
-class EXPCL_DIRECT DCMolecularField : public DCField {
+class DCMolecularField : public DCField {
 public:
   DCMolecularField(const string &name, DCClass *dclass);
 

+ 1 - 1
direct/src/dcparser/dcPackData.h

@@ -19,7 +19,7 @@
 /**
  * This is a block of data that receives the results of DCPacker.
  */
-class EXPCL_DIRECT DCPackData {
+class DCPackData {
 PUBLISHED:
   INLINE DCPackData();
   INLINE ~DCPackData();

+ 2 - 2
direct/src/dcparser/dcPacker.h

@@ -31,7 +31,7 @@ class DCSwitchParameter;
  * See also direct/src/doc/dcPacker.txt for a more complete description and
  * examples of using this class.
  */
-class EXPCL_DIRECT DCPacker {
+class DCPacker {
 PUBLISHED:
   DCPacker();
   ~DCPacker();
@@ -216,7 +216,7 @@ private:
   const DCPackerCatalog *_catalog;
   const DCPackerCatalog::LiveCatalog *_live_catalog;
 
-  class EXPCL_DIRECT StackElement {
+  class StackElement {
   public:
     // As an optimization, we implement operator new and delete here to
     // minimize allocation overhead during push() and pop().

+ 1 - 1
direct/src/dcparser/dcPackerCatalog.h

@@ -26,7 +26,7 @@ class DCSwitchParameter;
  * requested from a particular field; its ownership is retained by the field
  * so it must not be deleted.
  */
-class EXPCL_DIRECT DCPackerCatalog {
+class DCPackerCatalog {
 private:
   DCPackerCatalog(const DCPackerInterface *root);
   DCPackerCatalog(const DCPackerCatalog &copy);

+ 1 - 1
direct/src/dcparser/dcPackerInterface.h

@@ -64,7 +64,7 @@ END_PUBLISH
  * Normally these methods are called only by the DCPacker object; the user
  * wouldn't normally call these directly.
  */
-class EXPCL_DIRECT DCPackerInterface {
+class DCPackerInterface {
 public:
   DCPackerInterface(const string &name = string());
   DCPackerInterface(const DCPackerInterface &copy);

+ 1 - 1
direct/src/dcparser/dcParameter.h

@@ -32,7 +32,7 @@ class HashGenerator;
  * This may also be a typedef reference to another type, which has the same
  * properties as the referenced type, but a different name.
  */
-class EXPCL_DIRECT DCParameter : public DCField {
+class DCParameter : public DCField {
 protected:
   DCParameter();
   DCParameter(const DCParameter &copy);

+ 1 - 1
direct/src/dcparser/dcSimpleParameter.h

@@ -25,7 +25,7 @@
  * divisor, which is meaningful only for the numeric type elements (and
  * represents a fixed-point numeric convention).
  */
-class EXPCL_DIRECT DCSimpleParameter : public DCParameter {
+class DCSimpleParameter : public DCParameter {
 public:
   DCSimpleParameter(DCSubatomicType type, unsigned int divisor = 1);
   DCSimpleParameter(const DCSimpleParameter &copy);

+ 1 - 1
direct/src/dcparser/dcSwitch.h

@@ -27,7 +27,7 @@ class DCField;
  * and represents two or more alternative unpacking schemes based on the first
  * field read.
  */
-class EXPCL_DIRECT DCSwitch : public DCDeclaration {
+class DCSwitch : public DCDeclaration {
 public:
   DCSwitch(const string &name, DCField *key_parameter);
   virtual ~DCSwitch();

+ 1 - 1
direct/src/dcparser/dcSwitchParameter.h

@@ -23,7 +23,7 @@ class DCSwitch;
  * This represents a switch object used as a parameter itself, which packs the
  * appropriate fields of the switch into the message.
  */
-class EXPCL_DIRECT DCSwitchParameter : public DCParameter {
+class DCSwitchParameter : public DCParameter {
 public:
   DCSwitchParameter(const DCSwitch *dswitch);
   DCSwitchParameter(const DCSwitchParameter &copy);

+ 1 - 1
direct/src/dcparser/dcTypedef.h

@@ -23,7 +23,7 @@ class DCParameter;
  * This represents a single typedef declaration in the dc file.  It assigns a
  * particular type to a new name, just like a C typedef.
  */
-class EXPCL_DIRECT DCTypedef : public DCDeclaration {
+class DCTypedef : public DCDeclaration {
 public:
   DCTypedef(DCParameter *parameter, bool implicit = false);
   DCTypedef(const string &name);

+ 22 - 2
direct/src/distributed/cConnectionRepository.cxx

@@ -402,8 +402,28 @@ send_datagram(const Datagram &dg) {
   }
 
 #ifdef WANT_NATIVE_NET
-  if(_native)
-    return _bdc.SendMessage(dg);
+  if (_native) {
+    bool result = _bdc.SendMessage();
+    if (!result && _bdc.IsConnected()) {
+#ifdef HAVE_PYTHON
+      ostringstream s;
+
+#if PY_VERSION_HEX >= 0x03030000
+      PyObject *exc_type = PyExc_ConnectionError;
+#else
+      PyObject *exc_type = PyExc_OSError;
+#endif
+
+      s << endl << "Error sending message: " << endl;
+      msg.dump_hex(s);
+      s << "Message data: " << msg.get_data() << endl;
+
+      string message = s.str();
+      PyErr_SetString(exc_type, message.c_str());
+#endif
+    }
+    return result;
+  }
 #endif
 
 #ifdef HAVE_NET

+ 0 - 2
direct/src/plugin/p3dCInstance.h

@@ -21,8 +21,6 @@
 #include "get_tinyxml.h"
 #include "windowHandle.h"
 
-#include <Python.h>
-
 class P3DSession;
 
 /**

+ 49 - 39
direct/src/showbase/Loader.py

@@ -28,7 +28,7 @@ class Loader(DirectObject):
             self.extraArgs = extraArgs
             self.numRemaining = numObjects
             self.cancelled = False
-            self.requests = {}
+            self.requests = set()
 
         def gotObject(self, index, object):
             self.objects[index] = object
@@ -45,6 +45,8 @@ class Loader(DirectObject):
         self.base = base
         self.loader = PandaLoader.getGlobalPtr()
 
+        self.__requests = {}
+
         self.hook = "async_loader_%s" % (Loader.loaderIndex)
         Loader.loaderIndex += 1
         self.accept(self.hook, self.__gotAsyncObject)
@@ -116,7 +118,7 @@ class Loader(DirectObject):
         """
 
         assert Loader.notify.debug("Loading model: %s" % (modelPath))
-        if loaderOptions == None:
+        if loaderOptions is None:
             loaderOptions = LoaderOptions()
         else:
             loaderOptions = LoaderOptions(loaderOptions)
@@ -156,7 +158,7 @@ class Loader(DirectObject):
             result = []
             for modelPath in modelList:
                 node = self.loader.loadSync(Filename(modelPath), loaderOptions)
-                if (node != None):
+                if node is not None:
                     nodePath = NodePath(node)
                 else:
                     nodePath = None
@@ -179,16 +181,16 @@ class Loader(DirectObject):
             # callback (passing it the models on the parameter list).
 
             cb = Loader.Callback(len(modelList), gotList, callback, extraArgs)
-            i=0
+            i = 0
             for modelPath in modelList:
                 request = self.loader.makeAsyncRequest(Filename(modelPath), loaderOptions)
                 if priority is not None:
                     request.setPriority(priority)
                 request.setDoneEvent(self.hook)
-                request.setPythonObject((cb, i))
-                i+=1
                 self.loader.loadAsync(request)
-                cb.requests[request] = True
+                cb.requests.add(request)
+                self.__requests[request] = (cb, i)
+                i += 1
             return cb
 
     def cancelRequest(self, cb):
@@ -200,6 +202,7 @@ class Loader(DirectObject):
             cb.cancelled = True
             for request in cb.requests:
                 self.loader.remove(request)
+                del self.__requests[request]
             cb.requests = None
 
     def isRequestPending(self, cb):
@@ -273,7 +276,7 @@ class Loader(DirectObject):
             # to resolve it for us.
             options = LoaderOptions(LoaderOptions.LFSearch | LoaderOptions.LFNoDiskCache | LoaderOptions.LFCacheOnly)
             modelNode = self.loader.loadSync(Filename(model), options)
-            if modelNode == None:
+            if modelNode is None:
                 # Model not found.
                 assert Loader.notify.debug("Unloading model not loaded: %s" % (model))
                 return
@@ -293,7 +296,7 @@ class Loader(DirectObject):
         a callback is used, the model is saved asynchronously, and the
         true/false status is passed to the callback function. """
 
-        if loaderOptions == None:
+        if loaderOptions is None:
             loaderOptions = LoaderOptions()
         else:
             loaderOptions = LoaderOptions(loaderOptions)
@@ -342,16 +345,16 @@ class Loader(DirectObject):
             # callback (passing it the models on the parameter list).
 
             cb = Loader.Callback(len(modelList), gotList, callback, extraArgs)
-            i=0
+            i = 0
             for modelPath, node in modelList:
                 request = self.loader.makeAsyncSaveRequest(Filename(modelPath), loaderOptions, node)
                 if priority is not None:
                     request.setPriority(priority)
                 request.setDoneEvent(self.hook)
-                request.setPythonObject((cb, i))
-                i+=1
                 self.loader.saveAsync(request)
-                cb.requests[request] = True
+                cb.requests.add(request)
+                self.__requests[request] = (cb, i)
+                i += 1
             return cb
 
 
@@ -496,7 +499,7 @@ class Loader(DirectObject):
             phaseChecker(modelPath, loaderOptions)
 
         font = FontPool.loadFont(modelPath)
-        if font == None:
+        if font is None:
             if not okMissing:
                 message = 'Could not load font file: %s' % (modelPath)
                 raise IOError(message)
@@ -506,21 +509,21 @@ class Loader(DirectObject):
 
         # The following properties may only be set for dynamic fonts.
         if hasattr(font, "setPointSize"):
-            if pointSize != None:
+            if pointSize is not None:
                 font.setPointSize(pointSize)
-            if pixelsPerUnit != None:
+            if pixelsPerUnit is not None:
                 font.setPixelsPerUnit(pixelsPerUnit)
-            if scaleFactor != None:
+            if scaleFactor is not None:
                 font.setScaleFactor(scaleFactor)
-            if textureMargin != None:
+            if textureMargin is not None:
                 font.setTextureMargin(textureMargin)
-            if polyMargin != None:
+            if polyMargin is not None:
                 font.setPolyMargin(polyMargin)
-            if minFilter != None:
+            if minFilter is not None:
                 font.setMinfilter(minFilter)
-            if magFilter != None:
+            if magFilter is not None:
                 font.setMagfilter(magFilter)
-            if anisotropicDegree != None:
+            if anisotropicDegree is not None:
                 font.setAnisotropicDegree(anisotropicDegree)
             if color:
                 font.setFg(color)
@@ -577,10 +580,10 @@ class Loader(DirectObject):
         the texture and the number of expected mipmap images.
 
         If minfilter or magfilter is not None, they should be a symbol
-        like Texture.FTLinear or Texture.FTNearest.  (minfilter may be
-        further one of the Mipmap filter type symbols.)  These specify
-        the filter mode that will automatically be applied to the
-        texture when it is loaded.  Note that this setting may
+        like SamplerState.FTLinear or SamplerState.FTNearest.  (minfilter
+        may be further one of the Mipmap filter type symbols.)  These
+        specify the filter mode that will automatically be applied to
+        the texture when it is loaded.  Note that this setting may
         override the texture's existing settings, even if it has
         already been loaded.  See egg-texture-cards for a more robust
         way to apply per-texture filter types and settings.
@@ -596,7 +599,7 @@ class Loader(DirectObject):
         left image and '1' for the right image.  Larger numbers are
         also allowed if you need more than two views.
         """
-        if loaderOptions == None:
+        if loaderOptions is None:
             loaderOptions = LoaderOptions()
         else:
             loaderOptions = LoaderOptions(loaderOptions)
@@ -657,7 +660,7 @@ class Loader(DirectObject):
         numbered 8 - 15 will be part of the right eye view.
         """
         assert Loader.notify.debug("Loading 3-D texture: %s" % (texturePattern))
-        if loaderOptions == None:
+        if loaderOptions is None:
             loaderOptions = LoaderOptions()
         else:
             loaderOptions = LoaderOptions(loaderOptions)
@@ -711,7 +714,7 @@ class Loader(DirectObject):
         and each six images will define a new view.
         """
         assert Loader.notify.debug("Loading cube map: %s" % (texturePattern))
-        if loaderOptions == None:
+        if loaderOptions is None:
             loaderOptions = LoaderOptions()
         else:
             loaderOptions = LoaderOptions(loaderOptions)
@@ -821,13 +824,12 @@ class Loader(DirectObject):
             # callback (passing it the sounds on the parameter list).
 
             cb = Loader.Callback(len(soundList), gotList, callback, extraArgs)
-            for i in range(len(soundList)):
-                soundPath = soundList[i]
+            for i, soundPath in enumerate(soundList):
                 request = AudioLoadRequest(manager, soundPath, positional)
                 request.setDoneEvent(self.hook)
-                request.setPythonObject((cb, i))
                 self.loader.loadAsync(request)
-                cb.requests[request] = True
+                cb.requests.add(request)
+                self.__requests[request] = (cb, i)
             return cb
 
     def unloadSfx(self, sfx):
@@ -852,7 +854,7 @@ class Loader(DirectObject):
         return shader
 
     def unloadShader(self, shaderPath):
-        if (shaderPath != None):
+        if shaderPath is not None:
             ShaderPool.releaseShader(shaderPath)
 
     def asyncFlattenStrong(self, model, inPlace = True,
@@ -886,14 +888,15 @@ class Loader(DirectObject):
             gotList = True
 
         cb = Loader.Callback(len(modelList), gotList, callback, extraArgs)
-        i=0
+        i = 0
         for model in modelList:
             request = ModelFlattenRequest(model.node())
             request.setDoneEvent(self.hook)
             request.setPythonObject((cb, i))
-            i+=1
             self.loader.loadAsync(request)
             cb.requests[request] = True
+            self.__requests[request] = (cb, i)
+            i += 1
         return cb
 
     def __asyncFlattenDone(self, models,
@@ -921,16 +924,23 @@ class Loader(DirectObject):
         of loaded objects, and call the appropriate callback when it's
         time."""
 
-        cb, i = request.getPythonObject()
+        if request not in self.__requests:
+            return
+
+        cb, i = self.__requests[request]
         if cb.cancelled:
+            # Shouldn't be here.
+            del self.__requests[request]
             return
 
-        del cb.requests[request]
+        cb.requests.discard(request)
+        if not cb.requests:
+            del self.__requests[request]
 
         object = None
         if hasattr(request, "getModel"):
             node = request.getModel()
-            if (node != None):
+            if node is not None:
                 object = NodePath(node)
 
         elif hasattr(request, "getSound"):

+ 4 - 1
direct/src/showutil/FreezeTool.py

@@ -984,7 +984,10 @@ class Freezer:
             try:
                 self.__loadModule(mdef)
             except ImportError as ex:
-                print("Unknown module: %s (%s)" % (mdef.moduleName, str(ex)))
+                message = "Unknown module: %s" % (mdef.moduleName)
+                if str(ex) != "No module named " + str(mdef.moduleName):
+                    message += " (%s)" % (ex)
+                print(message)
 
         # Also attempt to import any implicit modules.  If any of
         # these fail to import, we don't really care.

+ 10 - 10
direct/src/stdpy/thread.py

@@ -102,7 +102,7 @@ def start_new_thread(function, args, kwargs = {}, name = None):
             name = 'PythonThread-%s' % (threadId)
 
         thread = core.PythonThread(threadFunc, [threadId], name, name)
-        thread.setPythonData(threadId)
+        thread.setPythonIndex(threadId)
         _threads[threadId] = (thread, {}, None)
 
         thread.start(core.TPNormal, False)
@@ -121,7 +121,7 @@ def _add_thread(thread, wrapper):
         threadId = _nextThreadId
         _nextThreadId += 1
 
-        thread.setPythonData(threadId)
+        thread.setPythonIndex(threadId)
         _threads[threadId] = (thread, {}, wrapper)
         return threadId
 
@@ -133,8 +133,8 @@ def _get_thread_wrapper(thread, wrapperClass):
     is not one, creates an instance of the indicated wrapperClass
     instead. """
 
-    threadId = thread.getPythonData()
-    if threadId is None:
+    threadId = thread.getPythonIndex()
+    if threadId == -1:
         # The thread has never been assigned a threadId.  Go assign one.
 
         global _nextThreadId
@@ -143,7 +143,7 @@ def _get_thread_wrapper(thread, wrapperClass):
             threadId = _nextThreadId
             _nextThreadId += 1
 
-            thread.setPythonData(threadId)
+            thread.setPythonIndex(threadId)
             wrapper = wrapperClass(thread, threadId)
             _threads[threadId] = (thread, {}, wrapper)
             return wrapper
@@ -169,8 +169,8 @@ def _get_thread_locals(thread, i):
     """ Returns the locals dictionary for the indicated thread.  If
     there is not one, creates an empty dictionary. """
 
-    threadId = thread.getPythonData()
-    if threadId is None:
+    threadId = thread.getPythonIndex()
+    if threadId == -1:
         # The thread has never been assigned a threadId.  Go assign one.
 
         global _nextThreadId
@@ -179,7 +179,7 @@ def _get_thread_locals(thread, i):
             threadId = _nextThreadId
             _nextThreadId += 1
 
-            thread.setPythonData(threadId)
+            thread.setPythonIndex(threadId)
             locals = {}
             _threads[threadId] = (thread, locals, None)
             return locals.setdefault(i, {})
@@ -205,9 +205,9 @@ def _remove_thread_id(threadId):
     _threadsLock.acquire()
     try:
         thread, locals, wrapper = _threads[threadId]
-        assert thread.getPythonData() == threadId
+        assert thread.getPythonIndex() == threadId
         del _threads[threadId]
-        thread.setPythonData(None)
+        thread.setPythonIndex(-1)
 
     finally:
         _threadsLock.release()

+ 48 - 4
dtool/src/interrogate/interfaceMakerPythonNative.cxx

@@ -1259,9 +1259,16 @@ write_module_support(ostream &out, ostream *out_h, InterrogateModuleDef *def) {
       if (is_cpp_type_legal(object->_itype._cpptype) &&
           isExportThisRun(object->_itype._cpptype)) {
         string class_name = make_safe_name(object->_itype.get_scoped_name());
-        bool is_typed = HasAGetClassTypeFunction(object->_itype._cpptype);
+        bool is_typed = has_get_class_type_function(object->_itype._cpptype);
 
         if (is_typed) {
+          if (has_init_type_function(object->_itype._cpptype)) {
+            // Call the init_type function.  This isn't necessary for all
+            // types as many of them are automatically initialized at static
+            // init type, but for some extension classes it's useful.
+            out << "  " << object->_itype._cpptype->get_local_name(&parser)
+                << "::init_type();\n";
+          }
           out << "  Dtool_" << class_name << "._type = "
               << object->_itype._cpptype->get_local_name(&parser)
               << "::get_class_type();\n"
@@ -1291,7 +1298,7 @@ write_module_support(ostream &out, ostream *out_h, InterrogateModuleDef *def) {
     string class_name = (*ii)->get_local_name(&parser);
     string safe_name = make_safe_name(class_name);
 
-    if (HasAGetClassTypeFunction(*ii)) {
+    if (has_get_class_type_function(*ii)) {
       out << "  Dtool_Ptr_" << safe_name << " = LookupRuntimeTypedClass(" << class_name << "::get_class_type());\n";
     } else {
       out << "  Dtool_Ptr_" << safe_name << " = LookupNamedClass(\"" << class_name << "\");\n";
@@ -1541,7 +1548,7 @@ write_module_class(ostream &out, Object *obj) {
   std::string export_class_name = classNameFromCppName(obj->_itype.get_name(), false);
 
   bool is_runtime_typed = IsPandaTypedObject(obj->_itype._cpptype->as_struct_type());
-  if (!is_runtime_typed && HasAGetClassTypeFunction(obj->_itype._cpptype)) {
+  if (!is_runtime_typed && has_get_class_type_function(obj->_itype._cpptype)) {
     is_runtime_typed = true;
   }
 
@@ -7010,7 +7017,7 @@ DoesInheritFromIsClass(const CPPStructType *inclass, const std::string &name) {
 
  */
 bool InterfaceMakerPythonNative::
-HasAGetClassTypeFunction(CPPType *type) {
+has_get_class_type_function(CPPType *type) {
   while (type->get_subtype() == CPPDeclaration::ST_typedef) {
     type = type->as_typedef_type()->_type;
   }
@@ -7025,6 +7032,43 @@ HasAGetClassTypeFunction(CPPType *type) {
   return scope->_functions.find("get_class_type") != scope->_functions.end();
 }
 
+/**
+ *
+ */
+bool InterfaceMakerPythonNative::
+has_init_type_function(CPPType *type) {
+  while (type->get_subtype() == CPPDeclaration::ST_typedef) {
+    type = type->as_typedef_type()->_type;
+  }
+
+  CPPStructType *struct_type = type->as_struct_type();
+  if (struct_type == NULL) {
+    return false;
+  }
+
+  CPPScope *scope = struct_type->get_scope();
+  CPPScope::Functions::const_iterator it = scope->_functions.find("init_type");
+  if (it == scope->_functions.end()) {
+    return false;
+  }
+  const CPPFunctionGroup *group = it->second;
+
+  CPPFunctionGroup::Instances::const_iterator ii;
+  for (ii = group->_instances.begin(); ii != group->_instances.end(); ++ii) {
+    const CPPInstance *cppinst = *ii;
+    const CPPFunctionType *cppfunc = cppinst->_type->as_function_type();
+
+    if (cppfunc != NULL &&
+        cppfunc->_parameters != NULL &&
+        cppfunc->_parameters->_parameters.size() == 0 &&
+        (cppinst->_storage_class & CPPInstance::SC_static) != 0) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
 /**
  * Returns -1 if the class does not define write() (and therefore cannot
  * support a __str__ function).

+ 2 - 1
dtool/src/interrogate/interfaceMakerPythonNative.h

@@ -201,7 +201,8 @@ public:
   bool DoesInheritFromIsClass(const CPPStructType * inclass, const std::string &name);
   bool IsPandaTypedObject(CPPStructType * inclass) { return DoesInheritFromIsClass(inclass,"TypedObject"); };
   void write_python_instance(ostream &out, int indent_level, const std::string &return_expr, bool owns_memory, const InterrogateType &itype, bool is_const);
-  bool HasAGetClassTypeFunction(CPPType *type);
+  bool has_get_class_type_function(CPPType *type);
+  bool has_init_type_function(CPPType *type);
   int NeedsAStrFunction(const InterrogateType &itype_class);
   int NeedsAReprFunction(const InterrogateType &itype_class);
   bool NeedsARichCompareFunction(const InterrogateType &itype_class);

+ 0 - 4
dtool/src/interrogate/interrogate.cxx

@@ -19,7 +19,6 @@
 #include "pnotify.h"
 #include "panda_getopt_long.h"
 #include "preprocess_argv.h"
-#include "pystub.h"
 #include <time.h>
 
 CPPParser parser;
@@ -306,9 +305,6 @@ predefine_macro(CPPParser& parser, const string& inoption) {
 
 int
 main(int argc, char **argv) {
-  // A call to pystub() to force libpystub.so to be linked in.
-  pystub();
-
   preprocess_argv(argc, argv);
   string command_line;
   int i;

+ 0 - 4
dtool/src/interrogate/interrogate_module.cxx

@@ -19,7 +19,6 @@
 #include "interrogate_interface.h"
 #include "interrogate_request.h"
 #include "load_dso.h"
-#include "pystub.h"
 #include "pnotify.h"
 #include "panda_getopt_long.h"
 #include "preprocess_argv.h"
@@ -380,9 +379,6 @@ int main(int argc, char *argv[]) {
   extern int optind;
   int flag;
 
-  // A call to pystub() to force libpystub.so to be linked in.
-  pystub();
-
   preprocess_argv(argc, argv);
   flag = getopt_long_only(argc, argv, short_options, long_options, NULL);
   while (flag != EOF) {

+ 1 - 1
dtool/src/parser-inc/Python.h

@@ -30,7 +30,7 @@ typedef struct {} PyUnicodeObject;
 
 class PyThreadState;
 typedef int Py_ssize_t;
-struct Py_buffer;
+typedef struct bufferinfo Py_buffer;
 
 // We need to define these accurately since interrogate may want to
 // write these out to default value assignments.

+ 0 - 1
dtool/src/test_interrogate/test_interrogate.cxx

@@ -17,7 +17,6 @@
 #include "interrogate_request.h"
 #include "load_dso.h"
 #include "filename.h"
-#include "pystub.h"
 #include "panda_getopt.h"
 #include "preprocess_argv.h"
 

File diff suppressed because it is too large
+ 136 - 126
makepanda/makepanda.py


+ 0 - 4
panda/src/android/pview.cxx

@@ -13,7 +13,6 @@
 
 #include "pandaFramework.h"
 #include "pandaSystem.h"
-#include "pystub.h"
 #include "texturePool.h"
 #include "multitexReducer.h"
 #include "sceneGraphReducer.h"
@@ -29,9 +28,6 @@
 #include "checkPandaVersion.h"
 
 int main(int argc, char **argv) {
-  // A call to pystub() to force libpystub.so to be linked in.
-  pystub();
-
   PandaFramework framework;
   framework.open_framework(argc, argv);
   framework.set_window_title("Panda Viewer");

+ 17 - 0
panda/src/chan/animChannelFixed.cxx

@@ -0,0 +1,17 @@
+/**
+ * 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 animChannelFixed.cxx
+ * @author rdb
+ * @date 2016-05-29
+ */
+
+#include "animChannelFixed.h"
+
+template class AnimChannelFixed<ACMatrixSwitchType>;
+template class AnimChannelFixed<ACScalarSwitchType>;

+ 2 - 0
panda/src/chan/animChannelFixed.h

@@ -62,6 +62,8 @@ private:
   static TypeHandle _type_handle;
 };
 
+EXPORT_TEMPLATE_CLASS(EXPCL_PANDA_CHAN, EXPTP_PANDA_CHAN, AnimChannelFixed<ACMatrixSwitchType>);
+EXPORT_TEMPLATE_CLASS(EXPCL_PANDA_CHAN, EXPTP_PANDA_CHAN, AnimChannelFixed<ACScalarSwitchType>);
 
 #include "animChannelFixed.I"
 

+ 1 - 0
panda/src/chan/p3chan_composite1.cxx

@@ -3,6 +3,7 @@
 #include "animBundleNode.cxx"
 #include "animChannel.cxx"
 #include "animChannelBase.cxx"
+#include "animChannelFixed.cxx"
 #include "animChannelMatrixDynamic.cxx"
 #include "animChannelMatrixFixed.cxx"
 #include "animChannelMatrixXfmTable.cxx"

+ 0 - 4
panda/src/downloadertools/apply_patch.cxx

@@ -10,7 +10,6 @@
  */
 
 #include "pandabase.h"
-#include "pystub.h"
 #include "panda_getopt.h"
 #include "preprocess_argv.h"
 #include "patchfile.h"
@@ -18,9 +17,6 @@
 
 int
 main(int argc, char **argv) {
-  // A call to pystub() to force libpystub.so to be linked in.
-  pystub();
-
   preprocess_argv(argc, argv);
 
   if (argc < 3) {

+ 0 - 4
panda/src/downloadertools/build_patch.cxx

@@ -10,7 +10,6 @@
  */
 
 #include "pandabase.h"
-#include "pystub.h"
 #include "panda_getopt.h"
 #include "preprocess_argv.h"
 #include "patchfile.h"
@@ -51,9 +50,6 @@ help() {
 
 int
 main(int argc, char **argv) {
-  // A call to pystub() to force libpystub.so to be linked in.
-  pystub();
-
   Filename patch_file;
   bool complete_file = false;
   int footprint_length = 0;

+ 0 - 4
panda/src/downloadertools/check_adler.cxx

@@ -10,13 +10,9 @@
  */
 
 #include "download_utils.h"
-#include "pystub.h"
 
 int
 main(int argc, char *argv[]) {
-  // A call to pystub() to force libpystub.so to be linked in.
-  pystub();
-
   if (argc < 2) {
     cerr << "Usage: check_adler <file>" << endl;
     return 1;

+ 0 - 4
panda/src/downloadertools/check_crc.cxx

@@ -10,13 +10,9 @@
  */
 
 #include "download_utils.h"
-#include "pystub.h"
 
 int
 main(int argc, char *argv[]) {
-  // A call to pystub() to force libpystub.so to be linked in.
-  pystub();
-
   if (argc < 2) {
     cerr << "Usage: check_crc <file>" << endl;
     return 1;

+ 0 - 4
panda/src/downloadertools/check_md5.cxx

@@ -10,7 +10,6 @@
  */
 
 #include "pandabase.h"
-#include "pystub.h"
 #include "hashVal.h"
 #include "filename.h"
 #include "panda_getopt.h"
@@ -65,9 +64,6 @@ output_hash(const string &filename, const HashVal &hash) {
 
 int
 main(int argc, char **argv) {
-  // A call to pystub() to force libpystub.so to be linked in.
-  pystub();
-
   extern char *optarg;
   extern int optind;
   const char *optstr = "i:db:qh";

+ 0 - 4
panda/src/downloadertools/multify.cxx

@@ -10,7 +10,6 @@
  */
 
 #include "pandabase.h"
-#include "pystub.h"
 #include "panda_getopt.h"
 #include "preprocess_argv.h"
 #include "multifile.h"
@@ -758,9 +757,6 @@ tokenize_extensions(const string &str, pset<string> &extensions) {
 
 int
 main(int argc, char **argv) {
-  // A call to pystub() to force libpystub.so to be linked in.
-  pystub();
-
   preprocess_argv(argc, argv);
   if (argc < 2) {
     usage();

+ 0 - 4
panda/src/downloadertools/pdecrypt.cxx

@@ -11,7 +11,6 @@
  * @date 2004-09-01
  */
 
-#include "pystub.h"
 #include "filename.h"
 #include "encrypt_string.h"
 #include "pnotify.h"
@@ -46,9 +45,6 @@ usage() {
 
 int
 main(int argc, char **argv) {
-  // A call to pystub() to force libpystub.so to be linked in.
-  pystub();
-
   extern char *optarg;
   extern int optind;
   const char *optstr = "o:p:h";

+ 0 - 1
panda/src/downloadertools/pencrypt.cxx

@@ -11,7 +11,6 @@
  * @date 2004-09-01
  */
 
-#include "pystub.h"
 #include "filename.h"
 #include "encrypt_string.h"
 #include "pnotify.h"

+ 0 - 4
panda/src/downloadertools/punzip.cxx

@@ -9,7 +9,6 @@
  * @file punzip.cxx
  */
 
-#include "pystub.h"
 #include "filename.h"
 #include "compress_string.h"
 #include "pnotify.h"
@@ -31,9 +30,6 @@ usage() {
 
 int
 main(int argc, char **argv) {
-  // A call to pystub() to force libpystub.so to be linked in.
-  pystub();
-
   extern char *optarg;
   extern int optind;
   const char *optstr = "o:ch";

+ 0 - 4
panda/src/downloadertools/pzip.cxx

@@ -9,7 +9,6 @@
  * @file pzip.cxx
  */
 
-#include "pystub.h"
 #include "filename.h"
 #include "compress_string.h"
 #include "pnotify.h"
@@ -55,9 +54,6 @@ usage() {
 
 int
 main(int argc, char **argv) {
-  // A call to pystub() to force libpystub.so to be linked in.
-  pystub();
-
   extern char *optarg;
   extern int optind;
   const char *optstr = "o:c123456789h";

+ 0 - 4
panda/src/downloadertools/show_ddb.cxx

@@ -12,15 +12,11 @@
  */
 
 #include "pandabase.h"
-#include "pystub.h"
 #include "downloadDb.h"
 #include "filename.h"
 
 int
 main(int argc, char *argv[]) {
-  // A call to pystub() to force libpystub.so to be linked in.
-  pystub();
-
   if (argc != 3) {
     cerr << "Usage: show_ddb server.ddb client.ddb\n";
     return 1;

+ 0 - 39
panda/src/event/asyncTask.cxx

@@ -18,10 +18,6 @@
 #include "throw_event.h"
 #include "eventParameter.h"
 
-#ifdef HAVE_PYTHON
-#include "py_panda.h"
-#endif
-
 AtomicAdjust::Integer AsyncTask::_next_task_id;
 PStatCollector AsyncTask::_show_code_pcollector("App:Show code");
 TypeHandle AsyncTask::_type_handle;
@@ -48,9 +44,6 @@ AsyncTask(const string &name) :
   _total_dt(0.0),
   _num_frames(0)
 {
-#ifdef HAVE_PYTHON
-  _python_object = NULL;
-#endif  // HAVE_PYTHON
   set_name(name);
 
   // Carefully copy _next_task_id and increment it so that we get a unique ID.
@@ -68,9 +61,6 @@ AsyncTask(const string &name) :
 AsyncTask::
 ~AsyncTask() {
   nassertv(_state == S_inactive && _manager == NULL && _chain == NULL);
-#ifdef HAVE_PYTHON
-  set_python_object(NULL);
-#endif
 }
 
 /**
@@ -355,35 +345,6 @@ set_priority(int priority) {
   }
 }
 
-#ifdef HAVE_PYTHON
-/**
- * Specifies an arbitrary Python object that will be piggybacked on the task
- * object.
- */
-void AsyncTask::
-set_python_object(PyObject *python_object) {
-  Py_XINCREF(python_object);
-  Py_XDECREF(_python_object);
-  _python_object = python_object;
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Returns the Python object that was specified to set_python_object(), if
- * any, or None if no object was specified.
- */
-PyObject *AsyncTask::
-get_python_object() const {
-  if (_python_object != (PyObject *)NULL) {
-    Py_XINCREF(_python_object);
-    return _python_object;
-  }
-  Py_INCREF(Py_None);
-  return Py_None;
-}
-#endif  // HAVE_PYTHON
-
 /**
  *
  */

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

@@ -92,11 +92,6 @@ PUBLISHED:
   INLINE void set_done_event(const string &done_event);
   INLINE const string &get_done_event() const;
 
-#ifdef HAVE_PYTHON
-  void set_python_object(PyObject *python_object);
-  PyObject *get_python_object() const;
-#endif  // HAVE_PYTHON
-
   INLINE double get_dt() const;
   INLINE double get_max_dt() const;
   INLINE double get_average_dt() const;
@@ -140,11 +135,6 @@ protected:
   static PStatCollector _show_code_pcollector;
   PStatCollector _task_pcollector;
 
-private:
-#ifdef HAVE_PYTHON
-  PyObject *_python_object;
-#endif  // HAVE_PYTHON
-
 public:
   static TypeHandle get_class_type() {
     return _type_handle;

+ 0 - 34
panda/src/event/pythonTask.I

@@ -10,37 +10,3 @@
  * @author drose
  * @date 2008-09-16
  */
-
-/**
- * If None is passed, calls clear_delay, otherwise sets the delay time.  See
- * AsyncTask::set_delay() and AsyncTask::clear_delay().
- */
-INLINE void PythonTask::
-set_delay(PyObject *delay) {
-  if (delay == Py_None) {
-    AsyncTask::clear_delay();
-    return;
-  }
-
-  PyObject *value = PyNumber_Float(delay);
-  if (value == NULL) {
-    return;
-  }
-
-  AsyncTask::set_delay(PyFloat_AS_DOUBLE(value));
-  Py_DECREF(value);
-}
-
-/**
- * Returns the delay time if set, None otherwise.  See AsyncTask::has_delay()
- * and AsyncTask::get_delay().
- */
-INLINE PyObject *PythonTask::
-get_delay() const {
-  if (AsyncTask::has_delay()) {
-    return PyFloat_FromDouble(AsyncTask::get_delay());
-  } else {
-    Py_INCREF(Py_None);
-    return Py_None;
-  }
-}

+ 3 - 7
panda/src/event/pythonTask.cxx

@@ -18,12 +18,9 @@
 #ifdef HAVE_PYTHON
 #include "py_panda.h"
 
-TypeHandle PythonTask::_type_handle;
+#include "pythonThread.h"
 
-Configure(config_pythonTask);
-ConfigureFn(config_pythonTask) {
-  PythonTask::init_type();
-}
+TypeHandle PythonTask::_type_handle;
 
 #ifndef CPPPARSER
 extern struct Dtool_PyTypedObject Dtool_TypedReferenceCount;
@@ -404,8 +401,7 @@ do_python_task() {
   if (_generator == (PyObject *)NULL) {
     // We are calling the function directly.
     PyObject *args = get_args();
-    result =
-      Thread::get_current_thread()->call_python_func(_function, args);
+    result = PythonThread::call_python_func(_function, args);
     Py_DECREF(args);
 
 #ifdef PyGen_Check

+ 2 - 5
panda/src/event/pythonTask.h

@@ -50,9 +50,6 @@ PUBLISHED:
   int __traverse__(visitproc visit, void *arg);
   int __clear__();
 
-  INLINE void set_delay(PyObject *delay);
-  INLINE PyObject *get_delay() const;
-
 PUBLISHED:
   // The name of this task.
   MAKE_PROPERTY(name, get_name, set_name);
@@ -72,10 +69,10 @@ PUBLISHED:
   MAKE_PROPERTY(wakeTime, get_wake_time);
 
   // The delay value that has been set on this task, if any, or None.
-  MAKE_PROPERTY(delay_time, get_delay, set_delay);
+  MAKE_PROPERTY2(delay_time, has_delay, get_delay, set_delay, clear_delay);
 
   // Alias of delay_time.
-  MAKE_PROPERTY(delayTime, get_delay, set_delay);
+  MAKE_PROPERTY2(delayTime, has_delay, get_delay, set_delay, clear_delay);
 
   // The number of frames that have elapsed since the task was started,
   // according to the task manager's clock.

+ 0 - 4
panda/src/express/pointerToArray.h

@@ -102,11 +102,9 @@ PUBLISHED:
   INLINE int get_node_ref_count() const;
 
 #ifdef HAVE_PYTHON
-#if PY_VERSION_HEX >= 0x02060000
   EXTENSION(int __getbuffer__(PyObject *self, Py_buffer *view, int flags));
   EXTENSION(void __releasebuffer__(PyObject *self, Py_buffer *view) const);
 #endif
-#endif
 
 #else  // CPPPARSER
   // This is the actual, complete interface.
@@ -257,11 +255,9 @@ PUBLISHED:
   INLINE int get_node_ref_count() const;
 
 #ifdef HAVE_PYTHON
-#if PY_VERSION_HEX >= 0x02060000
   EXTENSION(int __getbuffer__(PyObject *self, Py_buffer *view, int flags) const);
   EXTENSION(void __releasebuffer__(PyObject *self, Py_buffer *view) const);
 #endif
-#endif
 
 #else  // CPPPARSER
   // This is the actual, complete interface.

+ 12 - 6
panda/src/express/pointerToArray_ext.I

@@ -175,7 +175,6 @@ __getitem__(size_t n) const {
   return (*this->_this)[n];
 }
 
-#if PY_VERSION_HEX >= 0x02060000
 /**
  * This is used to implement the buffer protocol, in order to allow efficient
  * access to the array data through a Python multiview object.
@@ -183,7 +182,7 @@ __getitem__(size_t n) const {
 template<class Element>
 INLINE int Extension<PointerToArray<Element> >::
 __getbuffer__(PyObject *self, Py_buffer *view, int flags) {
-
+#if PY_VERSION_HEX >= 0x02060000
   const char *format = get_format_code(Element);
   if (format == NULL) {
     // Not supported.
@@ -222,6 +221,9 @@ __getbuffer__(PyObject *self, Py_buffer *view, int flags) {
   view->internal = (void*) this->_this;
 
   return 0;
+#else
+  return -1;
+#endif
 }
 
 /**
@@ -230,13 +232,14 @@ __getbuffer__(PyObject *self, Py_buffer *view, int flags) {
 template<class Element>
 INLINE void Extension<PointerToArray<Element> >::
 __releasebuffer__(PyObject *self, Py_buffer *view) const {
+#if PY_VERSION_HEX >= 0x02060000
   // Note: PyBuffer_Release automatically decrements view->obj.
-
   if (view->internal != NULL) {
     // Oh, right, let's not forget to unref this.
     unref_delete((const PointerToArray<Element> *)view->internal);
     view->internal = NULL;
   }
+#endif
 }
 
 /**
@@ -246,7 +249,7 @@ __releasebuffer__(PyObject *self, Py_buffer *view) const {
 template<class Element>
 INLINE int Extension<ConstPointerToArray<Element> >::
 __getbuffer__(PyObject *self, Py_buffer *view, int flags) const {
-
+#if PY_VERSION_HEX >= 0x02060000
   if ((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE) {
     PyErr_SetString(PyExc_BufferError,
                     "Object is not writable.");
@@ -291,6 +294,9 @@ __getbuffer__(PyObject *self, Py_buffer *view, int flags) const {
   view->internal = (void*) this->_this;
 
   return 0;
+#else
+  return -1;
+#endif
 }
 
 /**
@@ -299,12 +305,12 @@ __getbuffer__(PyObject *self, Py_buffer *view, int flags) const {
 template<class Element>
 INLINE void Extension<ConstPointerToArray<Element> >::
 __releasebuffer__(PyObject *self, Py_buffer *view) const {
+#if PY_VERSION_HEX >= 0x02060000
   // Note: PyBuffer_Release automatically decrements obj->view.
-
   if (view->internal != NULL) {
     // Oh, right, let's not forget to unref this.
     unref_delete((const PointerToArray<Element> *)view->internal);
     view->internal = NULL;
   }
+#endif
 }
-#endif  // PY_VERSION_HEX

+ 0 - 4
panda/src/express/pointerToArray_ext.h

@@ -35,10 +35,8 @@ public:
   INLINE const Element &__getitem__(size_t n) const;
   INLINE void __setitem__(size_t n, const Element &value);
 
-#if PY_VERSION_HEX >= 0x02060000
   INLINE int __getbuffer__(PyObject *self, Py_buffer *view, int flags);
   INLINE void __releasebuffer__(PyObject *self, Py_buffer *view) const;
-#endif
 };
 
 /**
@@ -55,10 +53,8 @@ public:
 
   INLINE const Element &__getitem__(size_t n) const;
 
-#if PY_VERSION_HEX >= 0x02060000
   INLINE int __getbuffer__(PyObject *self, Py_buffer *view, int flags) const;
   INLINE void __releasebuffer__(PyObject *self, Py_buffer *view) const;
-#endif
 };
 
 #ifdef _MSC_VER

+ 1 - 1
panda/src/gobj/geomVertexArrayData.h

@@ -113,7 +113,7 @@ PUBLISHED:
   static void lru_epoch();
   INLINE static VertexDataBook &get_book();
 
-#if PY_VERSION_HEX >= 0x02060000
+#ifdef HAVE_PYTHON
   EXTENSION(int __getbuffer__(PyObject *self, Py_buffer *view, int flags));
   EXTENSION(int __getbuffer__(PyObject *self, Py_buffer *view, int flags) const);
   EXTENSION(void __releasebuffer__(PyObject *self, Py_buffer *view) const);

+ 10 - 6
panda/src/gobj/geomVertexArrayData_ext.cxx

@@ -22,14 +22,13 @@ struct InternalBufferData {
   string _format;
 };
 
-#if PY_VERSION_HEX >= 0x02060000
 /**
  * This is used to implement the buffer protocol, in order to allow efficient
  * access to the array data through a Python multiview object.
  */
 int Extension<GeomVertexArrayData>::
 __getbuffer__(PyObject *self, Py_buffer *view, int flags) {
-
+#if PY_VERSION_HEX >= 0x02060000
   PT(GeomVertexArrayDataHandle) handle = _this->modify_handle();
   CPT(GeomVertexArrayFormat) format = handle->get_array_format();
 
@@ -79,6 +78,9 @@ __getbuffer__(PyObject *self, Py_buffer *view, int flags) {
   view->suboffsets = NULL;
 
   return 0;
+#else
+  return -1;
+#endif
 }
 
 /**
@@ -86,7 +88,7 @@ __getbuffer__(PyObject *self, Py_buffer *view, int flags) {
  */
 int Extension<GeomVertexArrayData>::
 __getbuffer__(PyObject *self, Py_buffer *view, int flags) const {
-
+#if PY_VERSION_HEX >= 0x02060000
   if ((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE) {
       PyErr_SetString(PyExc_BufferError,
                       "Object is not writable.");
@@ -142,6 +144,9 @@ __getbuffer__(PyObject *self, Py_buffer *view, int flags) const {
   view->suboffsets = NULL;
 
   return 0;
+#else
+  return -1;
+#endif
 }
 
 /**
@@ -149,8 +154,8 @@ __getbuffer__(PyObject *self, Py_buffer *view, int flags) const {
  */
 void Extension<GeomVertexArrayData>::
 __releasebuffer__(PyObject *self, Py_buffer *view) const {
+#if PY_VERSION_HEX >= 0x02060000
   // Note: PyBuffer_Release automatically decrements view->obj.
-
   InternalBufferData *data;
   data = (InternalBufferData *) view->internal;
   if (data == NULL) {
@@ -158,10 +163,9 @@ __releasebuffer__(PyObject *self, Py_buffer *view) const {
   }
   delete data;
   view->internal = NULL;
+#endif
 }
 
-#endif  // PY_VERSION_HEX >= 0x02060000
-
 /**
  * Copies all data from the given buffer object.  The array is rescaled as
  * necessary.

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

@@ -29,11 +29,9 @@
 template<>
 class Extension<GeomVertexArrayData> : public ExtensionBase<GeomVertexArrayData> {
 public:
-#if PY_VERSION_HEX >= 0x02060000
   int __getbuffer__(PyObject *self, Py_buffer *view, int flags);
   int __getbuffer__(PyObject *self, Py_buffer *view, int flags) const;
   void __releasebuffer__(PyObject *self, Py_buffer *view) const;
-#endif
 };
 
 /**

+ 0 - 20
panda/src/nativenet/buffered_datagramconnection.cxx

@@ -13,10 +13,6 @@
 
 #include "buffered_datagramconnection.h"
 
-#ifdef HAVE_PYTHON
-#include "py_panda.h"
-#endif
-
 TypeHandle Buffered_DatagramConnection::_type_handle;
 
 /**
@@ -34,22 +30,6 @@ SendMessage(const Datagram &msg) {
 
     // Raise an exception to give us more information at the python level
     nativenet_cat.warning() << "Buffered_DatagramConnection::SendMessage->Error On Write--Out Buffer = " << _Writer.AmountBuffered() << "\n";
-#ifdef HAVE_PYTHON
-    ostringstream s;
-
-#if PY_VERSION_HEX >= 0x03030000
-    PyObject *exc_type = PyExc_ConnectionError;
-#else
-    PyObject *exc_type = PyExc_OSError;
-#endif
-
-    s << endl << "Error sending message: " << endl;
-    msg.dump_hex(s);
-    s << "Message data: " << msg.get_data() << endl;
-
-    string message = s.str();
-    PyErr_SetString(exc_type, message.c_str());
-#endif
 
     ClearAll();
   }

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

@@ -38,6 +38,10 @@ PUBLISHED:
   INLINE bool is_ready() const;
   INLINE PandaNode *get_model() const;
 
+  MAKE_PROPERTY(orig, get_orig);
+  MAKE_PROPERTY(ready, is_ready);
+  MAKE_PROPERTY(model, get_model);
+
 protected:
   virtual DoneStatus do_task();
 

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

@@ -45,6 +45,12 @@ PUBLISHED:
   INLINE bool is_ready() const;
   INLINE PandaNode *get_model() const;
 
+  MAKE_PROPERTY(filename, get_filename);
+  MAKE_PROPERTY(options, get_options);
+  MAKE_PROPERTY(loader, get_loader);
+  MAKE_PROPERTY(ready, is_ready);
+  MAKE_PROPERTY(model, get_model);
+
 protected:
   virtual DoneStatus do_task();
 

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

@@ -46,6 +46,13 @@ PUBLISHED:
   INLINE bool is_ready() const;
   INLINE bool get_success() const;
 
+  MAKE_PROPERTY(filename, get_filename);
+  MAKE_PROPERTY(options, get_options);
+  MAKE_PROPERTY(node, get_node);
+  MAKE_PROPERTY(loader, get_loader);
+  MAKE_PROPERTY(ready, is_ready);
+  MAKE_PROPERTY(success, get_success);
+
 protected:
   virtual DoneStatus do_task();
 

+ 12 - 8
panda/src/pgraph/nodePath.h

@@ -905,15 +905,19 @@ PUBLISHED:
   NodePath find_net_tag(const string &key) const;
 
   EXTENSION(INLINE PyObject *get_tag_keys() const);
-  EXTENSION(INLINE void set_python_tag(const string &key, PyObject *value));
-  EXTENSION(INLINE PyObject *get_python_tag(const string &key) const);
-  EXTENSION(INLINE void get_python_tag_keys(vector_string &keys) const);
+
+  EXTENSION(PyObject *get_python_tags());
+  EXTENSION(INLINE void set_python_tag(PyObject *keys, PyObject *value));
+  EXTENSION(INLINE PyObject *get_python_tag(PyObject *keys) const);
   EXTENSION(INLINE PyObject *get_python_tag_keys() const);
-  EXTENSION(INLINE bool has_python_tag(const string &key) const);
-  EXTENSION(INLINE void clear_python_tag(const string &key));
-  EXTENSION(INLINE PyObject *get_net_python_tag(const string &key) const);
-  EXTENSION(INLINE bool has_net_python_tag(const string &key) const);
-  EXTENSION(NodePath find_net_python_tag(const string &key) const);
+  EXTENSION(INLINE bool has_python_tag(PyObject *keys) const);
+  EXTENSION(INLINE void clear_python_tag(PyObject *keys));
+  EXTENSION(INLINE PyObject *get_net_python_tag(PyObject *keys) const);
+  EXTENSION(INLINE bool has_net_python_tag(PyObject *keys) const);
+  EXTENSION(NodePath find_net_python_tag(PyObject *keys) const);
+  MAKE_PROPERTY(python_tags, get_python_tags);
+
+  EXTENSION(int __traverse__(visitproc visit, void *arg));
 
   INLINE void list_tags() const;
 

+ 29 - 14
panda/src/pgraph/nodePath_ext.I

@@ -14,15 +14,19 @@
 #include "pandaNode_ext.h"
 
 /**
- * Fills the given vector up with the list of Python tags on this PandaNode.
- *
- * It is the user's responsibility to ensure that the keys vector is empty
- * before making this call; otherwise, the new files will be appended to it.
+ * Returns a dictionary of Python tags that is associated with the current
+ * node.  This is similar to the regular tags, except this can store any
+ * Python object instead of just a string.  However, the Python object is not
+ * recorded to a bam file.
  */
-INLINE void Extension<NodePath>::
-get_python_tag_keys(vector_string &keys) const {
-  nassertv_always(!_this->is_empty());
-  invoke_extension(_this->node()).get_python_tag_keys(keys);
+INLINE PyObject *Extension<NodePath>::
+get_python_tags() {
+  // An empty NodePath returns None
+  if (_this->is_empty()) {
+    Py_INCREF(Py_None);
+    return Py_None;
+  }
+  return invoke_extension(_this->node()).get_python_tags();
 }
 
 /**
@@ -63,7 +67,7 @@ get_python_tag_keys() const {
  * of any one key's object.
  */
 INLINE void Extension<NodePath>::
-set_python_tag(const string &key, PyObject *value) {
+set_python_tag(PyObject *key, PyObject *value) {
   nassertv_always(!_this->is_empty());
   invoke_extension(_this->node()).set_python_tag(key, value);
 }
@@ -74,7 +78,7 @@ set_python_tag(const string &key, PyObject *value) {
  * None.  See also get_net_python_tag().
  */
 INLINE PyObject *Extension<NodePath>::
-get_python_tag(const string &key) const {
+get_python_tag(PyObject *key) const {
   // An empty NodePath quietly returns no tags.  This makes
   // get_net_python_tag() easier to implement.
   if (_this->is_empty()) {
@@ -90,7 +94,7 @@ get_python_tag(const string &key) const {
  * value has been set.  See also has_net_python_tag().
  */
 INLINE bool Extension<NodePath>::
-has_python_tag(const string &key) const {
+has_python_tag(PyObject *key) const {
   // An empty NodePath quietly has no tags.  This makes has_net_python_tag()
   // easier to implement.
   if (_this->is_empty()) {
@@ -105,7 +109,7 @@ has_python_tag(const string &key) const {
  * the indicated key.
  */
 INLINE void Extension<NodePath>::
-clear_python_tag(const string &key) {
+clear_python_tag(PyObject *key) {
   nassertv_always(!_this->is_empty());
   invoke_extension(_this->node()).clear_python_tag(key);
 }
@@ -117,7 +121,7 @@ clear_python_tag(const string &key) {
  * get_python_tag().
  */
 INLINE PyObject *Extension<NodePath>::
-get_net_python_tag(const string &key) const {
+get_net_python_tag(PyObject *key) const {
   NodePath tag_np = find_net_python_tag(key);
   return invoke_extension(&tag_np).get_python_tag(key);
 }
@@ -127,6 +131,17 @@ get_net_python_tag(const string &key) const {
  * or on any ancestor node, or false otherwise.  See also has_python_tag().
  */
 INLINE bool Extension<NodePath>::
-has_net_python_tag(const string &key) const {
+has_net_python_tag(PyObject *key) const {
   return !find_net_python_tag(key).is_empty();
 }
+
+/**
+ * Called by Python to implement cycle detection.
+ */
+INLINE int Extension<NodePath>::
+__traverse__(visitproc visit, void *arg) {
+  if (_this->is_empty()) {
+    return 0;
+  }
+  return invoke_extension(_this->node()).__traverse__(visit, arg);
+}

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

@@ -172,7 +172,7 @@ __reduce_persist__(PyObject *self, PyObject *pickler) const {
  * node contains this tag definition.  See set_python_tag().
  */
 NodePath Extension<NodePath>::
-find_net_python_tag(const string &key) const {
+find_net_python_tag(PyObject *key) const {
   if (_this->is_empty()) {
     return NodePath::not_found();
   }

+ 12 - 8
panda/src/pgraph/nodePath_ext.h

@@ -35,15 +35,19 @@ public:
   PyObject *__reduce_persist__(PyObject *self, PyObject *pickler) const;
 
   INLINE PyObject *get_tag_keys() const;
-  INLINE void set_python_tag(const string &key, PyObject *value);
-  INLINE PyObject *get_python_tag(const string &key) const;
-  INLINE void get_python_tag_keys(vector_string &keys) const;
+
+  INLINE PyObject *get_python_tags();
+  INLINE void set_python_tag(PyObject *key, PyObject *value);
+  INLINE PyObject *get_python_tag(PyObject *key) const;
   INLINE PyObject *get_python_tag_keys() const;
-  INLINE bool has_python_tag(const string &key) const;
-  INLINE void clear_python_tag(const string &key);
-  INLINE PyObject *get_net_python_tag(const string &key) const;
-  INLINE bool has_net_python_tag(const string &key) const;
-  NodePath find_net_python_tag(const string &key) const;
+  INLINE bool has_python_tag(PyObject *key) const;
+  INLINE void clear_python_tag(PyObject *key);
+  INLINE PyObject *get_net_python_tag(PyObject *key) const;
+  INLINE bool has_net_python_tag(PyObject *key) const;
+  NodePath find_net_python_tag(PyObject *key) const;
+
+  // This is defined to implement cycle detection in Python tags.
+  INLINE int __traverse__(visitproc visit, void *arg);
 
   PyObject *get_tight_bounds(const NodePath &other = NodePath()) const;
 };

+ 4 - 1
panda/src/pgraph/pandaNode.I

@@ -380,7 +380,10 @@ has_tags() const {
     return true;
   }
 #ifdef HAVE_PYTHON
-  if (!cdata->_python_tag_data.empty()) {
+  // This is a bit awkward, since it doesn't catch cases where the Python tags
+  // are cleared.  Maybe we should make this behavior deprecated, so that
+  // has_tags will not consider the Python tags.
+  if (!_python_tag_data.is_null()) {
     return true;
   }
 #endif  // HAVE_PYTHON

+ 18 - 180
panda/src/pgraph/pandaNode.cxx

@@ -27,7 +27,6 @@
 #include "config_mathutil.h"
 #include "lightReMutexHolder.h"
 #include "graphicsStateGuardianBase.h"
-#include "py_panda.h"
 
 // This category is just temporary for debugging convenience.
 NotifyCategoryDecl(drawmask, EXPCL_PANDA_PGRAPH, EXPTP_PANDA_PGRAPH);
@@ -131,7 +130,8 @@ PandaNode(const PandaNode &copy) :
   TypedWritableReferenceCount(copy),
   Namable(copy),
   _paths_lock("PandaNode::_paths_lock"),
-  _dirty_prev_transform(false)
+  _dirty_prev_transform(false),
+  _python_tag_data(copy._python_tag_data)
 {
   if (pgraph_cat.is_debug()) {
     pgraph_cat.debug()
@@ -172,12 +172,6 @@ PandaNode(const PandaNode &copy) :
     ++cdata->_internal_bounds_mark;
     cdata->_final_bounds = copy_cdata->_final_bounds;
     cdata->_fancy_bits = copy_cdata->_fancy_bits;
-
-#ifdef HAVE_PYTHON
-    // Copy and increment all of the Python objects held by the other node.
-    cdata->_python_tag_data = copy_cdata->_python_tag_data;
-    cdata->inc_py_refs();
-#endif  // HAVE_PYTHON
   }
 }
 
@@ -1270,32 +1264,13 @@ copy_tags(PandaNode *other) {
       cdataw->_tag_data[(*ti).first] = (*ti).second;
     }
     cdataw->set_fancy_bit(FB_tag, !cdataw->_tag_data.empty());
-
-#ifdef HAVE_PYTHON
-    PythonTagData::const_iterator pti;
-    for (pti = cdatar->_python_tag_data.begin();
-         pti != cdatar->_python_tag_data.end();
-         ++pti) {
-      const string &key = (*pti).first;
-      PyObject *value = (*pti).second;
-      Py_XINCREF(value);
-
-      pair<PythonTagData::iterator, bool> result;
-      result = cdataw->_python_tag_data.insert(PythonTagData::value_type(key, value));
-
-      if (!result.second) {
-        // The insert was unsuccessful; that means the key was already present
-        // in the map.  In this case, we should decrement the original value's
-        // reference count and replace it with the new object.
-        PythonTagData::iterator wpti = result.first;
-        PyObject *old_value = (*wpti).second;
-        Py_XDECREF(old_value);
-        (*wpti).second = value;
-      }
-    }
-#endif // HAVE_PYTHON
   }
   CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
+
+  // It's okay to copy the tags by pointer, because get_python_tags does a
+  // copy-on-write.
+  _python_tag_data = other->_python_tag_data;
+
   mark_bam_modified();
 }
 
@@ -1321,20 +1296,9 @@ list_tags(ostream &out, const string &separator) const {
     }
   }
 
-#ifdef HAVE_PYTHON
-  if (!cdata->_python_tag_data.empty()) {
-    if (!cdata->_tag_data.empty()) {
-      out << separator;
-    }
-    PythonTagData::const_iterator ti = cdata->_python_tag_data.begin();
-    out << (*ti).first;
-    ++ti;
-    while (ti != cdata->_python_tag_data.end()) {
-      out << separator << (*ti).first;
-      ++ti;
-    }
-  }
-#endif  // HAVE_PYTHON
+  // We used to list the Python tags here.  That's a bit awkward, though,
+  // since that means calling up into Python code to print the keys.  If
+  // someone finds it useful, we can implement it in an extension method.
 }
 
 /**
@@ -1393,50 +1357,11 @@ compare_tags(const PandaNode *other) const {
     return -1;
   }
 
-#ifdef HAVE_PYTHON
-  PythonTagData::const_iterator api = cdata->_python_tag_data.begin();
-  PythonTagData::const_iterator bpi = cdata_other->_python_tag_data.begin();
-  while (api != cdata->_python_tag_data.end() &&
-         bpi != cdata_other->_python_tag_data.end()) {
-    int cmp = strcmp((*api).first.c_str(), (*bpi).first.c_str());
-    if (cmp != 0) {
-      return cmp;
-    }
-
-#if PY_MAJOR_VERSION >= 3
-    if (PyObject_RichCompareBool((*api).second, (*bpi).second, Py_LT) == 1) {
-      return -1;
-    } else if (PyObject_RichCompareBool((*api).second, (*bpi).second, Py_GT) == 1) {
-      return 1;
-    } else if (PyObject_RichCompareBool((*api).second, (*bpi).second, Py_EQ) == 1) {
-      cmp = 0;
-    } else {
-#else
-    if (PyObject_Cmp((*api).second, (*bpi).second, &cmp) == -1) {
-#endif
-      // Unable to compare objects; just compare pointers.
-      if ((*api).second != (*bpi).second) {
-        cmp = (*api).second < (*bpi).second ? -1 : 1;
-      } else {
-        cmp = 0;
-      }
-    }
-    if (cmp != 0) {
-      return cmp;
-    }
-
-    ++api;
-    ++bpi;
-  }
-  if (api != cdata->_python_tag_data.end()) {
-    // list A is longer.
-    return 1;
+  // We compare these by pointer, since it's problematic to call up into
+  // Python from arbitrary C++ code.
+  if (_python_tag_data != other->_python_tag_data) {
+    return (_python_tag_data < other->_python_tag_data) ? -1 : 1;
   }
-  if (bpi != cdata_other->_python_tag_data.end()) {
-    // list B is longer.
-    return -1;
-  }
-#endif  // HAVE_PYTHON
 
   return 0;
 }
@@ -1494,30 +1419,6 @@ copy_all_properties(PandaNode *other) {
       cdataw->_tag_data[(*ti).first] = (*ti).second;
     }
 
-#ifdef HAVE_PYTHON
-    PythonTagData::const_iterator pti;
-    for (pti = cdatar->_python_tag_data.begin();
-         pti != cdatar->_python_tag_data.end();
-         ++pti) {
-      const string &key = (*pti).first;
-      PyObject *value = (*pti).second;
-      Py_XINCREF(value);
-
-      pair<PythonTagData::iterator, bool> result;
-      result = cdataw->_python_tag_data.insert(PythonTagData::value_type(key, value));
-
-      if (!result.second) {
-        // The insert was unsuccessful; that means the key was already present
-        // in the map.  In this case, we should decrement the original value's
-        // reference count and replace it with the new object.
-        PythonTagData::iterator wpti = result.first;
-        PyObject *old_value = (*wpti).second;
-        Py_XDECREF(old_value);
-        (*wpti).second = value;
-      }
-    }
-#endif // HAVE_PYTHON
-
     static const int change_bits = (FB_transform | FB_state | FB_effects |
                                     FB_tag | FB_draw_mask);
     cdataw->_fancy_bits =
@@ -1532,6 +1433,10 @@ copy_all_properties(PandaNode *other) {
   }
   CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
 
+  // It's okay to copy the tags by pointer, because get_python_tags does a
+  // copy-on-write.
+  _python_tag_data = other->_python_tag_data;
+
   if (any_transform_changed || any_state_changed || any_draw_mask_changed) {
     mark_bounds_stale(current_thread);
 
@@ -3810,7 +3715,6 @@ CData(const PandaNode::CData &copy) :
 
   _effects(copy._effects),
   _tag_data(copy._tag_data),
-  // _python_tag_data appears below.
   _draw_control_mask(copy._draw_control_mask),
   _draw_show_mask(copy._draw_show_mask),
   _into_collide_mask(copy._into_collide_mask),
@@ -3836,12 +3740,6 @@ CData(const PandaNode::CData &copy) :
   // Note that this copy constructor is not used by the PandaNode copy
   // constructor!  Any elements that must be copied between nodes should also
   // be explicitly copied there.
-
-#ifdef HAVE_PYTHON
-  // Copy and increment all of the Python objects held by the other node.
-  _python_tag_data = copy._python_tag_data;
-  inc_py_refs();
-#endif  // HAVE_PYTHON
 }
 
 /**
@@ -3849,10 +3747,6 @@ CData(const PandaNode::CData &copy) :
  */
 PandaNode::CData::
 ~CData() {
-#ifdef HAVE_PYTHON
-  // Free all of the Python objects held by this node.
-  dec_py_refs();
-#endif  // HAVE_PYTHON
 }
 
 /**
@@ -4029,62 +3923,6 @@ fillin(DatagramIterator &scan, BamReader *manager) {
   fillin_down_list(*modify_stashed(), "stashed", scan, manager);
 }
 
-#ifdef HAVE_PYTHON
-/**
- * Increments the reference counts on all held Python objects.
- */
-void PandaNode::CData::
-inc_py_refs() {
-  if (!_python_tag_data.empty()) {
-#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
-    // This might happen at any time, so be sure the Python state is ready for
-    // it.
-    PyGILState_STATE gstate;
-    gstate = PyGILState_Ensure();
-#endif
-    PythonTagData::const_iterator ti;
-    for (ti = _python_tag_data.begin();
-         ti != _python_tag_data.end();
-         ++ti) {
-      PyObject *value = (*ti).second;
-      Py_XINCREF(value);
-    }
-#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
-    PyGILState_Release(gstate);
-#endif
-  }
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Decrements the reference counts on all held Python objects.
- */
-void PandaNode::CData::
-dec_py_refs() {
-  if (!_python_tag_data.empty()) {
-#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
-    // This might happen at any time, so be sure the Python state is ready for
-    // it.
-    PyGILState_STATE gstate;
-    gstate = PyGILState_Ensure();
-#endif
-
-    PythonTagData::const_iterator ti;
-    for (ti = _python_tag_data.begin();
-         ti != _python_tag_data.end();
-         ++ti) {
-      PyObject *value = (*ti).second;
-      Py_XDECREF(value);
-    }
-
-#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
-    PyGILState_Release(gstate);
-#endif
-  }
-}
-#endif  // HAVE_PYTHON
-
 /**
  * Writes the indicated list of parent node pointers to the datagram.
  */

+ 18 - 11
panda/src/pgraph/pandaNode.h

@@ -200,12 +200,15 @@ PUBLISHED:
 
   EXTENSION(PyObject *get_tag_keys() const);
 
-  EXTENSION(void set_python_tag(const string &key, PyObject *value));
-  EXTENSION(PyObject *get_python_tag(const string &key) const);
-  EXTENSION(bool has_python_tag(const string &key) const);
-  EXTENSION(void clear_python_tag(const string &key));
-  EXTENSION(void get_python_tag_keys(vector_string &keys) const);
+  EXTENSION(PyObject *get_python_tags());
+  EXTENSION(void set_python_tag(PyObject *key, PyObject *value));
+  EXTENSION(PyObject *get_python_tag(PyObject *key) const);
+  EXTENSION(bool has_python_tag(PyObject *key) const);
+  EXTENSION(void clear_python_tag(PyObject *key));
   EXTENSION(PyObject *get_python_tag_keys() const);
+  MAKE_PROPERTY(python_tags, get_python_tags);
+
+  EXTENSION(int __traverse__(visitproc visit, void *arg));
 
   INLINE bool has_tags() const;
   void copy_tags(PandaNode *other);
@@ -507,9 +510,16 @@ private:
   // This is used to maintain a table of keyed data on each node, for the
   // user's purposes.
   typedef phash_map<string, string, string_hash> TagData;
-#ifdef HAVE_PYTHON
-  typedef phash_map<string, PyObject *, string_hash> PythonTagData;
-#endif  // HAVE_PYTHON
+
+  // This is actually implemented in pandaNode_ext.h, but defined here so
+  // that we can destruct it from the C++ side.  Note that it isn't cycled,
+  // because I imagine it's rare to see a Python thread on a stage other than
+  // stage 0, and let's not make things unnecessarily complicated.
+  class PythonTagData : public ReferenceCount {
+  public:
+    virtual ~PythonTagData() {};
+  };
+  PT(PythonTagData) _python_tag_data;
 
 #ifndef NDEBUG
   unsigned int _unexpected_change_flags;
@@ -555,9 +565,6 @@ private:
     CPT(RenderEffects) _effects;
 
     TagData _tag_data;
-#ifdef HAVE_PYTHON
-    PythonTagData _python_tag_data;
-#endif  // HAVE_PYTHON
 
     // These two together determine the per-camera visibility of this node.
     // See adjust_draw_mask() for details.

+ 98 - 89
panda/src/pgraph/pandaNode_ext.cxx

@@ -70,6 +70,40 @@ __deepcopy__(PyObject *self, PyObject *memo) const {
   return dupe;
 }
 
+/**
+ * This variant on get_tag_keys returns a Python tuple of strings keys.
+ */
+PyObject *Extension<PandaNode>::
+get_tag_keys() const {
+  vector_string keys;
+  _this->get_tag_keys(keys);
+
+  PyObject *result = PyTuple_New(keys.size());
+  for (size_t i = 0; i < keys.size(); ++i) {
+    PyTuple_SET_ITEM(result, i, Dtool_WrapValue(keys[i]));
+  }
+
+  return result;
+}
+
+/**
+ * Returns a dictionary of Python tags that is associated with the current
+ * node.  This is similar to the regular tags, except this can store any
+ * Python object instead of just a string.  However, the Python object is not
+ * recorded to a bam file.
+ */
+PyObject *Extension<PandaNode>::
+get_python_tags() {
+  if (_this->_python_tag_data == NULL) {
+    _this->_python_tag_data = new PythonTagDataImpl;
+
+  } else if (_this->_python_tag_data->get_ref_count() > 1) {
+    // Copy-on-write.
+    _this->_python_tag_data = new PythonTagDataImpl(*(PythonTagDataImpl *)_this->_python_tag_data.p());
+  }
+  return ((PythonTagDataImpl *)_this->_python_tag_data.p())->_dict;
+}
+
 /**
  * Associates an arbitrary Python object with a user-defined key which is
  * stored on the node.  This is similar to set_tag(), except it can store any
@@ -80,30 +114,9 @@ __deepcopy__(PyObject *self, PyObject *memo) const {
  * limit on the number of different keys that may be stored or on the length
  * of any one key's value.
  */
-void Extension<PandaNode>::
-set_python_tag(const string &key, PyObject *value) {
-  Thread *current_thread = Thread::get_current_thread();
-  int pipeline_stage = current_thread->get_pipeline_stage();
-  nassertv(pipeline_stage == 0);
-
-  PandaNode::CDWriter cdata(_this->_cycler);
-  Py_XINCREF(value);
-
-  pair<PandaNode::PythonTagData::iterator, bool> result;
-  result = cdata->_python_tag_data.insert(PandaNode::PythonTagData::value_type(key, value));
-
-  if (!result.second) {
-    // The insert was unsuccessful; that means the key was already present in
-    // the map.  In this case, we should decrement the original value's
-    // reference count and replace it with the new object.
-    PandaNode::PythonTagData::iterator ti = result.first;
-    PyObject *old_value = (*ti).second;
-    Py_XDECREF(old_value);
-    (*ti).second = value;
-  }
-
-  // Even though the python tag isn't recorded in the bam stream?
-  _this->mark_bam_modified();
+int Extension<PandaNode>::
+set_python_tag(PyObject *key, PyObject *value) {
+  return PyDict_SetItem(get_python_tags(), key, value);
 }
 
 /**
@@ -111,17 +124,20 @@ set_python_tag(const string &key, PyObject *value) {
  * particular key, if any.  If no value has been previously set, returns None.
  */
 PyObject *Extension<PandaNode>::
-get_python_tag(const string &key) const {
-  PandaNode::CDReader cdata(_this->_cycler);
-  PandaNode::PythonTagData::const_iterator ti;
-  ti = cdata->_python_tag_data.find(key);
-  if (ti != cdata->_python_tag_data.end()) {
-    PyObject *result = (*ti).second;
-    Py_XINCREF(result);
-    return result;
+get_python_tag(PyObject *key) const {
+  if (_this->_python_tag_data == NULL) {
+    Py_INCREF(Py_None);
+    return Py_None;
   }
-  Py_INCREF(Py_None);
-  return Py_None;
+
+  PyObject *dict = ((PythonTagDataImpl *)_this->_python_tag_data.p())->_dict;
+  PyObject *value = PyDict_GetItem(dict, key);
+  if (value == NULL) {
+    value = Py_None;
+  }
+  // PyDict_GetItem returns a borrowed reference.
+  Py_INCREF(value);
+  return value;
 }
 
 /**
@@ -130,11 +146,13 @@ get_python_tag(const string &key) const {
  * been set.
  */
 bool Extension<PandaNode>::
-has_python_tag(const string &key) const {
-  PandaNode::CDReader cdata(_this->_cycler);
-  PandaNode::PythonTagData::const_iterator ti;
-  ti = cdata->_python_tag_data.find(key);
-  return (ti != cdata->_python_tag_data.end());
+has_python_tag(PyObject *key) const {
+  if (_this->_python_tag_data == NULL) {
+    return false;
+  }
+
+  PyObject *dict = ((PythonTagDataImpl *)_this->_python_tag_data.p())->_dict;
+  return (PyDict_GetItem(dict, key) != NULL);
 }
 
 /**
@@ -143,72 +161,63 @@ has_python_tag(const string &key) const {
  * the indicated key.
  */
 void Extension<PandaNode>::
-clear_python_tag(const string &key) {
-  Thread *current_thread = Thread::get_current_thread();
-  int pipeline_stage = current_thread->get_pipeline_stage();
-  nassertv(pipeline_stage == 0);
-
-  PandaNode::CDWriter cdata(_this->_cycler, current_thread);
-  PandaNode::PythonTagData::iterator ti;
-  ti = cdata->_python_tag_data.find(key);
-  if (ti != cdata->_python_tag_data.end()) {
-    PyObject *value = (*ti).second;
-    Py_XDECREF(value);
-    cdata->_python_tag_data.erase(ti);
+clear_python_tag(PyObject *key) {
+  if (_this->_python_tag_data == NULL) {
+    return;
   }
 
-  // Even though the python tag isn't recorded in the bam stream?
-  _this->mark_bam_modified();
-}
-
-/**
- * Fills the given vector up with the list of Python tags on this PandaNode.
- *
- * It is the user's responsibility to ensure that the keys vector is empty
- * before making this call; otherwise, the new files will be appended to it.
- */
-void Extension<PandaNode>::
-get_python_tag_keys(vector_string &keys) const {
-  PandaNode::CDReader cdata(_this->_cycler);
-  if (!cdata->_python_tag_data.empty()) {
-    PandaNode::PythonTagData::const_iterator ti = cdata->_python_tag_data.begin();
-    while (ti != cdata->_python_tag_data.end()) {
-      keys.push_back((*ti).first);
-      ++ti;
-    }
+  PyObject *dict = get_python_tags();
+  if (PyDict_GetItem(dict, key) != NULL) {
+    PyDict_DelItem(dict, key);
   }
 }
 
 /**
- * This variant on get_tag_keys returns a Python list of strings.
+ * This variant on get_python_tag_keys returns a Python list of strings.
  */
 PyObject *Extension<PandaNode>::
-get_tag_keys() const {
-  vector_string keys;
-  _this->get_tag_keys(keys);
-
-  PyObject *result = PyTuple_New(keys.size());
-  for (size_t i = 0; i < keys.size(); ++i) {
-    PyTuple_SET_ITEM(result, i, Dtool_WrapValue(keys[i]));
+get_python_tag_keys() const {
+  if (_this->_python_tag_data == NULL) {
+    return PyTuple_New(0);
   }
 
-  return result;
+  PyObject *dict = ((PythonTagDataImpl *)_this->_python_tag_data.p())->_dict;
+  return PyDict_Keys(dict);
 }
 
 /**
- * This variant on get_python_tag_keys returns a Python list of strings.
+ * Called by Python to implement cycle detection.
  */
-PyObject *Extension<PandaNode>::
-get_python_tag_keys() const {
-  vector_string keys;
-  get_python_tag_keys(keys);
-
-  PyObject *result = PyTuple_New(keys.size());
-  for (size_t i = 0; i < keys.size(); ++i) {
-    PyTuple_SET_ITEM(result, i, Dtool_WrapValue(keys[i]));
+int Extension<PandaNode>::
+__traverse__(visitproc visit, void *arg) {
+  // To fully implement cycle breaking, we also have to recurse into all of
+  // the node's children.  However, this seems like it would be potentially
+  // quite expensive, so I'd rather not do it unless we had some optimization
+  // that would allow us to quickly find out whether there are children with
+  // Python tags.
+  if (_this->_python_tag_data != NULL) {
+    Py_VISIT(((PythonTagDataImpl *)_this->_python_tag_data.p())->_dict);
   }
+  return 0;
+}
 
-  return result;
+/**
+ * Destroys the tags associated with the node.
+ */
+Extension<PandaNode>::PythonTagDataImpl::
+~PythonTagDataImpl() {
+#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
+  // This might happen at any time, so be sure the Python state is ready for
+  // it.
+  PyGILState_STATE gstate;
+  gstate = PyGILState_Ensure();
+#endif
+
+  Py_CLEAR(_dict);
+
+#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
+  PyGILState_Release(gstate);
+#endif
 }
 
 #endif  // HAVE_PYTHON

+ 19 - 5
panda/src/pgraph/pandaNode_ext.h

@@ -34,12 +34,26 @@ public:
 
   PyObject *get_tag_keys() const;
 
-  void set_python_tag(const string &key, PyObject *value);
-  PyObject *get_python_tag(const string &key) const;
-  bool has_python_tag(const string &key) const;
-  void clear_python_tag(const string &key);
-  void get_python_tag_keys(vector_string &keys) const;
+  PyObject *get_python_tags();
+  int set_python_tag(PyObject *keys, PyObject *value);
+  PyObject *get_python_tag(PyObject *keys) const;
+  bool has_python_tag(PyObject *keys) const;
+  void clear_python_tag(PyObject *keys);
   PyObject *get_python_tag_keys() const;
+
+  // This is defined to implement cycle detection in Python tags.
+  int __traverse__(visitproc visit, void *arg);
+
+private:
+  // This is what actually stores the Python tags.
+  class PythonTagDataImpl : public PandaNode::PythonTagData {
+  public:
+    PythonTagDataImpl() : _dict(PyDict_New()) {};
+    PythonTagDataImpl(const PythonTagDataImpl &copy) : _dict(PyDict_Copy(copy._dict)) {};
+    virtual ~PythonTagDataImpl();
+
+    PyObject *_dict;
+  };
 };
 
 #endif  // HAVE_PYTHON

+ 0 - 4
panda/src/pipeline/config_pipeline.cxx

@@ -17,7 +17,6 @@
 #include "externalThread.h"
 #include "genericThread.h"
 #include "thread.h"
-#include "pythonThread.h"
 #include "pandaSystem.h"
 
 #include "dconfig.h"
@@ -73,9 +72,6 @@ init_libpipeline() {
   ExternalThread::init_type();
   GenericThread::init_type();
   Thread::init_type();
-#ifdef HAVE_PYTHON
-  PythonThread::init_type();
-#endif  // HAVE_PYTHON
 
 #ifdef HAVE_THREADS
  {

+ 0 - 1
panda/src/pipeline/p3pipeline_composite2.cxx

@@ -10,7 +10,6 @@
 #include "pipelineCyclerTrueImpl.cxx"
 #include "pmutex.cxx"
 #include "psemaphore.cxx"
-#include "pythonThread.cxx"
 #include "reMutex.cxx"
 #include "reMutexDirect.cxx"
 #include "reMutexHolder.cxx"

+ 187 - 13
panda/src/pipeline/pythonThread.cxx

@@ -36,18 +36,7 @@ PythonThread(PyObject *function, PyObject *args,
     nassert_raise("Invalid function passed to PythonThread constructor");
   }
 
-  if (args == Py_None) {
-    // None means no arguments; create an empty tuple.
-    _args = PyTuple_New(0);
-  } else {
-    _args = NULL;
-    if (PySequence_Check(args)) {
-      _args = PySequence_Tuple(args);
-    }
-    if (_args == NULL) {
-      nassert_raise("Invalid args passed to PythonThread constructor");
-    }
-  }
+  set_args(args);
 }
 
 /**
@@ -73,13 +62,198 @@ join() {
 
   if (_result == NULL) {
     // No result; return None.
-    return Py_BuildValue("");
+    Py_INCREF(Py_None);
+    return Py_None;
   }
 
   Py_INCREF(_result);
   return _result;
 }
 
+/**
+ *
+ */
+PyObject *PythonThread::
+get_args() const {
+  return _args;
+}
+
+/**
+ *
+ */
+void PythonThread::
+set_args(PyObject *args) {
+  Py_XDECREF(_args);
+
+  if (args == Py_None) {
+    // None means no arguments; create an empty tuple.
+    _args = PyTuple_New(0);
+  } else {
+    _args = NULL;
+    if (PySequence_Check(args)) {
+      _args = PySequence_Tuple(args);
+    }
+    if (_args == NULL) {
+      Dtool_Raise_TypeError("PythonThread args must be a tuple");
+    }
+  }
+}
+
+#ifdef HAVE_PYTHON
+/**
+ * Internal function to safely call a Python function within a sub-thread,
+ * that might execute in parallel with existing Python code.  The return value
+ * is the return value of the Python function, or NULL if there was an
+ * exception.
+ */
+PyObject *PythonThread::
+call_python_func(PyObject *function, PyObject *args) {
+  Thread *current_thread = get_current_thread();
+
+  // Create a new Python thread state data structure, so Python can properly
+  // lock itself.
+  PyObject *result = NULL;
+
+  if (current_thread == get_main_thread()) {
+    // In the main thread, just call the function.
+    result = PyObject_Call(function, args, NULL);
+
+    if (result == (PyObject *)NULL) {
+      if (PyErr_Occurred() && PyErr_ExceptionMatches(PyExc_SystemExit)) {
+        // If we caught SystemExit, let it pass by without bothering to print
+        // a callback.
+
+      } else {
+        // Temporarily save and restore the exception state so we can print a
+        // callback on-the-spot.
+        PyObject *exc, *val, *tb;
+        PyErr_Fetch(&exc, &val, &tb);
+
+        Py_XINCREF(exc);
+        Py_XINCREF(val);
+        Py_XINCREF(tb);
+        PyErr_Restore(exc, val, tb);
+        PyErr_Print();
+
+        PyErr_Restore(exc, val, tb);
+      }
+    }
+
+  } else {
+#ifndef HAVE_THREADS
+    // Shouldn't be possible to come here without having some kind of
+    // threading support enabled.
+    nassertr(false, NULL);
+#else
+
+#ifdef SIMPLE_THREADS
+    // We can't use the PyGILState interface, which assumes we are using true
+    // OS-level threading.  PyGILState enforces policies like only one thread
+    // state per OS-level thread, which is not true in the case of
+    // SIMPLE_THREADS.
+
+    // For some reason I don't fully understand, I'm getting a crash when I
+    // clean up old PyThreadState objects with PyThreadState_Delete().  It
+    // appears that the thread state is still referenced somewhere at the time
+    // I call delete, and the crash occurs because I've deleted an active
+    // pointer.
+
+    // Storing these pointers in a vector for permanent recycling seems to
+    // avoid this problem.  I wish I understood better what's going wrong, but
+    // I guess this workaround will do.
+    static pvector<PyThreadState *> thread_states;
+
+    PyThreadState *orig_thread_state = PyThreadState_Get();
+    PyInterpreterState *istate = orig_thread_state->interp;
+    PyThreadState *new_thread_state;
+    if (thread_states.empty()) {
+      new_thread_state = PyThreadState_New(istate);
+    } else {
+      new_thread_state = thread_states.back();
+      thread_states.pop_back();
+    }
+    PyThreadState_Swap(new_thread_state);
+
+    // Call the user's function.
+    result = PyObject_Call(function, args, NULL);
+    if (result == (PyObject *)NULL && PyErr_Occurred()) {
+      // We got an exception.  Move the exception from the current thread into
+      // the main thread, so it can be handled there.
+      PyObject *exc, *val, *tb;
+      PyErr_Fetch(&exc, &val, &tb);
+
+      thread_cat.error()
+        << "Exception occurred within " << *current_thread << "\n";
+
+      // Temporarily restore the exception state so we can print a callback
+      // on-the-spot.
+      Py_XINCREF(exc);
+      Py_XINCREF(val);
+      Py_XINCREF(tb);
+      PyErr_Restore(exc, val, tb);
+      PyErr_Print();
+
+      PyThreadState_Swap(orig_thread_state);
+      thread_states.push_back(new_thread_state);
+      // PyThreadState_Clear(new_thread_state);
+      // PyThreadState_Delete(new_thread_state);
+
+      PyErr_Restore(exc, val, tb);
+
+      // Now attempt to force the main thread to the head of the ready queue,
+      // so it can respond to the exception immediately.  This only works if
+      // the main thread is not blocked, of course.
+      Thread::get_main_thread()->preempt();
+
+    } else {
+      // No exception.  Restore the thread state normally.
+      PyThreadState *state = PyThreadState_Swap(orig_thread_state);
+      thread_states.push_back(new_thread_state);
+      // PyThreadState_Clear(new_thread_state);
+      // PyThreadState_Delete(new_thread_state);
+    }
+
+#else  // SIMPLE_THREADS
+    // With true threading enabled, we're better off using PyGILState.
+    PyGILState_STATE gstate;
+    gstate = PyGILState_Ensure();
+
+    // Call the user's function.
+    result = PyObject_Call(function, args, NULL);
+    if (result == (PyObject *)NULL && PyErr_Occurred()) {
+      // We got an exception.  Move the exception from the current thread into
+      // the main thread, so it can be handled there.
+      PyObject *exc, *val, *tb;
+      PyErr_Fetch(&exc, &val, &tb);
+
+      thread_cat.error()
+        << "Exception occurred within " << *current_thread << "\n";
+
+      // Temporarily restore the exception state so we can print a callback
+      // on-the-spot.
+      Py_XINCREF(exc);
+      Py_XINCREF(val);
+      Py_XINCREF(tb);
+      PyErr_Restore(exc, val, tb);
+      PyErr_Print();
+
+      PyGILState_Release(gstate);
+
+      PyErr_Restore(exc, val, tb);
+    } else {
+      // No exception.  Restore the thread state normally.
+      PyGILState_Release(gstate);
+    }
+
+
+#endif  // SIMPLE_THREADS
+#endif  // HAVE_THREADS
+  }
+
+  return result;
+}
+#endif  // HAVE_PYTHON
+
 /**
  *
  */

+ 10 - 1
panda/src/pipeline/pythonThread.h

@@ -24,7 +24,7 @@
  * the Python level.  It will spawn a thread that executes an arbitrary Python
  * functor.
  */
-class EXPCL_PANDA_PIPELINE PythonThread : public Thread {
+class PythonThread : public Thread {
 PUBLISHED:
   PythonThread(PyObject *function, PyObject *args,
                const string &name, const string &sync_name);
@@ -32,6 +32,15 @@ PUBLISHED:
 
   BLOCKING PyObject *join();
 
+public:
+  PyObject *get_args() const;
+  void set_args(PyObject *);
+
+  static PyObject *call_python_func(PyObject *function, PyObject *args);
+
+PUBLISHED:
+  MAKE_PROPERTY(args, get_args, set_args);
+
 protected:
   virtual void thread_main();
 

+ 19 - 0
panda/src/pipeline/thread.I

@@ -48,6 +48,16 @@ get_pstats_index() const {
   return _pstats_index;
 }
 
+/**
+ * Returns the Python index associated with this thread, or -1 if no index has
+ * yet been associated with this thread.  This is used internally by the
+ * direct.stdpy.thread module; you should not need to call this directly.
+ */
+INLINE int Thread::
+get_python_index() const {
+  return _python_index;
+}
+
 /**
  * Returns a string that is guaranteed to be unique to this thread, across all
  * processes on the machine, during at least the lifetime of this process.
@@ -263,6 +273,15 @@ get_current_task() const {
   return _current_task;
 }
 
+/**
+ * Stores a Python index to be associated with this thread.  This is used
+ * internally by the thread module; you should not need to call this directly.
+ */
+INLINE void Thread::
+set_python_index(int python_index) {
+  _python_index = python_index;
+}
+
 /**
  * Should be called by the main thread just before exiting the program, this
  * blocks until any remaining thread cleanup has finished.

+ 1 - 261
panda/src/pipeline/thread.cxx

@@ -19,10 +19,6 @@
 #include "conditionVarDebug.h"
 #include "conditionVarFullDebug.h"
 
-#ifdef HAVE_PYTHON
-#include "py_panda.h"
-#endif
-
 Thread *Thread::_main_thread;
 Thread *Thread::_external_thread;
 TypeHandle Thread::_type_handle;
@@ -47,33 +43,17 @@ Thread(const string &name, const string &sync_name) :
 {
   _started = false;
   _pstats_index = -1;
+  _python_index = -1;
   _pstats_callback = NULL;
   _pipeline_stage = 0;
   _joinable = false;
   _current_task = NULL;
 
-#ifdef HAVE_PYTHON
-  _python_data = Py_None;
-  Py_INCREF(_python_data);
-#endif
-
 #ifdef DEBUG_THREADS
   _blocked_on_mutex = NULL;
   _waiting_on_cvar = NULL;
   _waiting_on_cvar_full = NULL;
 #endif
-
-#if defined(HAVE_PYTHON) && !defined(SIMPLE_THREADS)
-  // Ensure that the Python threading system is initialized and ready to go.
-#ifdef WITH_THREAD  // This symbol defined within Python.h
-
-#if PY_VERSION_HEX >= 0x03020000
-  Py_Initialize();
-#endif
-
-  PyEval_InitThreads();
-#endif
-#endif
 }
 
 /**
@@ -81,10 +61,6 @@ Thread(const string &name, const string &sync_name) :
  */
 Thread::
 ~Thread() {
-#ifdef HAVE_PYTHON
-  Py_DECREF(_python_data);
-#endif
-
 #ifdef DEBUG_THREADS
   nassertv(_blocked_on_mutex == NULL &&
            _waiting_on_cvar == NULL &&
@@ -225,242 +201,6 @@ start(ThreadPriority priority, bool joinable) {
   return _started;
 }
 
-#ifdef HAVE_PYTHON
-/**
- * Sets an arbitrary Python object that may be associated with this thread
- * object.  This is just for the purposes of associated arbitrary Python data
- * with the C++ object; other than managing the reference count, the C++ code
- * does nothing with this object.
- */
-void Thread::
-set_python_data(PyObject *python_data) {
-  Py_DECREF(_python_data);
-  _python_data = python_data;
-  Py_INCREF(_python_data);
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Returns the Python object that was set with set_python_data().
- */
-PyObject *Thread::
-get_python_data() const {
-  Py_INCREF(_python_data);
-  return _python_data;
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Internal function to safely call a Python function within a sub-thread,
- * that might execute in parallel with existing Python code.  The return value
- * is the return value of the Python function, or NULL if there was an
- * exception.
- */
-PyObject *Thread::
-call_python_func(PyObject *function, PyObject *args) {
-  nassertr(this == get_current_thread(), NULL);
-
-  // Create a new Python thread state data structure, so Python can properly
-  // lock itself.
-  PyObject *result = NULL;
-
-  if (this == get_main_thread()) {
-    // In the main thread, just call the function.
-    result = PyObject_Call(function, args, NULL);
-
-    if (result == (PyObject *)NULL) {
-      if (PyErr_Occurred() && PyErr_ExceptionMatches(PyExc_SystemExit)) {
-        // If we caught SystemExit, let it pass by without bothering to print
-        // a callback.
-
-      } else {
-        // Temporarily save and restore the exception state so we can print a
-        // callback on-the-spot.
-        PyObject *exc, *val, *tb;
-        PyErr_Fetch(&exc, &val, &tb);
-
-        Py_XINCREF(exc);
-        Py_XINCREF(val);
-        Py_XINCREF(tb);
-        PyErr_Restore(exc, val, tb);
-        PyErr_Print();
-
-        PyErr_Restore(exc, val, tb);
-      }
-    }
-
-  } else {
-#ifndef HAVE_THREADS
-    // Shouldn't be possible to come here without having some kind of
-    // threading support enabled.
-    nassertr(false, NULL);
-#else
-
-#ifdef SIMPLE_THREADS
-    // We can't use the PyGILState interface, which assumes we are using true
-    // OS-level threading.  PyGILState enforces policies like only one thread
-    // state per OS-level thread, which is not true in the case of
-    // SIMPLE_THREADS.
-
-    // For some reason I don't fully understand, I'm getting a crash when I
-    // clean up old PyThreadState objects with PyThreadState_Delete().  It
-    // appears that the thread state is still referenced somewhere at the time
-    // I call delete, and the crash occurs because I've deleted an active
-    // pointer.
-
-    // Storing these pointers in a vector for permanent recycling seems to
-    // avoid this problem.  I wish I understood better what's going wrong, but
-    // I guess this workaround will do.
-    static pvector<PyThreadState *> thread_states;
-
-    PyThreadState *orig_thread_state = PyThreadState_Get();
-    PyInterpreterState *istate = orig_thread_state->interp;
-    PyThreadState *new_thread_state;
-    if (thread_states.empty()) {
-      new_thread_state = PyThreadState_New(istate);
-    } else {
-      new_thread_state = thread_states.back();
-      thread_states.pop_back();
-    }
-    PyThreadState_Swap(new_thread_state);
-
-    // Call the user's function.
-    result = PyObject_Call(function, args, NULL);
-    if (result == (PyObject *)NULL && PyErr_Occurred()) {
-      // We got an exception.  Move the exception from the current thread into
-      // the main thread, so it can be handled there.
-      PyObject *exc, *val, *tb;
-      PyErr_Fetch(&exc, &val, &tb);
-
-      thread_cat.error()
-        << "Exception occurred within " << *this << "\n";
-
-      // Temporarily restore the exception state so we can print a callback
-      // on-the-spot.
-      Py_XINCREF(exc);
-      Py_XINCREF(val);
-      Py_XINCREF(tb);
-      PyErr_Restore(exc, val, tb);
-      PyErr_Print();
-
-      PyThreadState_Swap(orig_thread_state);
-      thread_states.push_back(new_thread_state);
-      // PyThreadState_Clear(new_thread_state);
-      // PyThreadState_Delete(new_thread_state);
-
-      PyErr_Restore(exc, val, tb);
-
-      // Now attempt to force the main thread to the head of the ready queue,
-      // so it can respond to the exception immediately.  This only works if
-      // the main thread is not blocked, of course.
-      Thread::get_main_thread()->preempt();
-
-    } else {
-      // No exception.  Restore the thread state normally.
-      PyThreadState *state = PyThreadState_Swap(orig_thread_state);
-      thread_states.push_back(new_thread_state);
-      // PyThreadState_Clear(new_thread_state);
-      // PyThreadState_Delete(new_thread_state);
-    }
-
-#else  // SIMPLE_THREADS
-    // With true threading enabled, we're better off using PyGILState.
-    PyGILState_STATE gstate;
-    gstate = PyGILState_Ensure();
-
-    // Call the user's function.
-    result = PyObject_Call(function, args, NULL);
-    if (result == (PyObject *)NULL && PyErr_Occurred()) {
-      // We got an exception.  Move the exception from the current thread into
-      // the main thread, so it can be handled there.
-      PyObject *exc, *val, *tb;
-      PyErr_Fetch(&exc, &val, &tb);
-
-      thread_cat.error()
-        << "Exception occurred within " << *this << "\n";
-
-      // Temporarily restore the exception state so we can print a callback
-      // on-the-spot.
-      Py_XINCREF(exc);
-      Py_XINCREF(val);
-      Py_XINCREF(tb);
-      PyErr_Restore(exc, val, tb);
-      PyErr_Print();
-
-      PyGILState_Release(gstate);
-
-      PyErr_Restore(exc, val, tb);
-    } else {
-      // No exception.  Restore the thread state normally.
-      PyGILState_Release(gstate);
-    }
-
-
-#endif  // SIMPLE_THREADS
-#endif  // HAVE_THREADS
-  }
-
-  return result;
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Called when a Python exception is raised during processing of a thread.
- * Gets the error string and passes it back to the calling Python process in a
- * sensible way.
- */
-void Thread::
-handle_python_exception() {
-  /*
-  PyObject *exc, *val, *tb;
-  PyErr_Fetch(&exc, &val, &tb);
-
-  ostringstream strm;
-  strm << "\n";
-
-  if (PyObject_HasAttrString(exc, "__name__")) {
-    PyObject *exc_name = PyObject_GetAttrString(exc, "__name__");
-    PyObject *exc_str = PyObject_Str(exc_name);
-    strm << PyString_AsString(exc_str);
-    Py_DECREF(exc_str);
-    Py_DECREF(exc_name);
-  } else {
-    PyObject *exc_str = PyObject_Str(exc);
-    strm << PyString_AsString(exc_str);
-    Py_DECREF(exc_str);
-  }
-  Py_DECREF(exc);
-
-  if (val != (PyObject *)NULL) {
-    PyObject *val_str = PyObject_Str(val);
-    strm << ": " << PyString_AsString(val_str);
-    Py_DECREF(val_str);
-    Py_DECREF(val);
-  }
-  if (tb != (PyObject *)NULL) {
-    Py_DECREF(tb);
-  }
-
-  strm << "\nException occurred within thread " << get_name();
-  string message = strm.str();
-  nout << message << "\n";
-
-  nassert_raise(message);
-  */
-
-  thread_cat.error()
-    << "Exception occurred within " << *this << "\n";
-
-  // Now attempt to force the main thread to the head of the ready queue, so
-  // it will be the one to receive the above assertion.  This mainly only has
-  // an effect if SIMPLE_THREADS is in use.
-  Thread::get_main_thread()->preempt();
-}
-#endif  // HAVE_PYTHON
-
 /**
  * Creates the Thread object that represents the main thread.
  */

+ 13 - 14
panda/src/pipeline/thread.h

@@ -59,6 +59,7 @@ PUBLISHED:
   INLINE const string &get_sync_name() const;
 
   INLINE int get_pstats_index() const;
+  INLINE int get_python_index() const;
   INLINE string get_unique_id() const;
 
   INLINE int get_pipeline_stage() const;
@@ -88,15 +89,21 @@ PUBLISHED:
   BLOCKING INLINE void join();
   INLINE void preempt();
 
-#ifdef HAVE_PYTHON
-  void set_python_data(PyObject *python_data);
-  PyObject *get_python_data() const;
-#endif
-
   INLINE AsyncTaskBase *get_current_task() const;
 
+  INLINE void set_python_index(int index);
+
   INLINE static void prepare_for_exit();
 
+  MAKE_PROPERTY(sync_name, get_sync_name);
+  MAKE_PROPERTY(pstats_index, get_pstats_index);
+  MAKE_PROPERTY(python_index, get_python_index);
+  MAKE_PROPERTY(unique_id, get_unique_id);
+  MAKE_PROPERTY(pipeline_stage, get_pipeline_stage, set_pipeline_stage);
+  MAKE_PROPERTY(started, is_started);
+  MAKE_PROPERTY(joinable, is_joinable);
+  MAKE_PROPERTY(current_task, get_current_task);
+
 public:
   // This class allows integration with PStats, particularly in the
   // SIMPLE_THREADS case.
@@ -111,12 +118,6 @@ public:
   INLINE void set_pstats_callback(PStatsCallback *pstats_callback);
   INLINE PStatsCallback *get_pstats_callback() const;
 
-#ifdef HAVE_PYTHON
-  // Integration with Python.
-  PyObject *call_python_func(PyObject *function, PyObject *args);
-  void handle_python_exception();
-#endif  // HAVE_PYTHON
-
 private:
   static void init_main_thread();
   static void init_external_thread();
@@ -133,9 +134,7 @@ private:
   bool _joinable;
   AsyncTaskBase *_current_task;
 
-#ifdef HAVE_PYTHON
-  PyObject *_python_data;
-#endif
+  int _python_index;
 
 #ifdef DEBUG_THREADS
   MutexDebug *_blocked_on_mutex;

+ 1 - 1
panda/src/pnmimage/pfmFile.h

@@ -170,9 +170,9 @@ PUBLISHED:
 
   void output(ostream &out) const;
 
+#ifdef HAVE_PYTHON
   EXTENSION(PyObject *get_points() const);
 
-#if PY_VERSION_HEX >= 0x02060000
   EXTENSION(int __getbuffer__(PyObject *self, Py_buffer *view, int flags) const);
 #endif
 

+ 4 - 4
panda/src/pnmimage/pfmFile_ext.cxx

@@ -70,7 +70,6 @@ get_points() const {
   return list;
 }
 
-#if PY_VERSION_HEX >= 0x02060000
 /**
  * This is a very low-level function that returns a read-only multiview into
  * the internal table of floating-point numbers.  Use this method at your own
@@ -78,7 +77,7 @@ get_points() const {
  */
 int Extension<PfmFile>::
 __getbuffer__(PyObject *self, Py_buffer *view, int flags) const {
-
+#if PY_VERSION_HEX >= 0x02060000
   if ((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE) {
       PyErr_SetString(PyExc_BufferError,
                       "Object is not writable.");
@@ -118,8 +117,9 @@ __getbuffer__(PyObject *self, Py_buffer *view, int flags) const {
   view->suboffsets = NULL;
 
   return 0;
+#else
+  return -1;
+#endif
 }
 
-#endif  // PY_VERSION_HEX >= 0x02060000
-
 #endif  // HAVE_PYTHON

+ 0 - 2
panda/src/pnmimage/pfmFile_ext.h

@@ -31,9 +31,7 @@ class Extension<PfmFile> : public ExtensionBase<PfmFile> {
 public:
   PyObject *get_points() const;
 
-#if PY_VERSION_HEX >= 0x02060000
   int __getbuffer__(PyObject *self, Py_buffer *view, int flags) const;
-#endif
 };
 
 #endif  // HAVE_PYTHON

+ 2 - 2
panda/src/putil/bamReader_ext.cxx

@@ -13,6 +13,7 @@
 
 #include "bamReader_ext.h"
 #include "config_util.h"
+#include "pythonThread.h"
 
 #ifdef HAVE_PYTHON
 
@@ -45,8 +46,7 @@ static TypedWritable *factory_callback(const FactoryParams &params){
   PyObject *args = PyTuple_Pack(2, py_scan, py_manager);
 
   // Now call the Python function.
-  Thread *current_thread = Thread::get_current_thread();
-  PyObject *result = current_thread->call_python_func(func, args);
+  PyObject *result = PythonThread::call_python_func(func, args);
   Py_DECREF(args);
   Py_DECREF(py_scan);
   Py_DECREF(py_manager);

+ 2 - 3
panda/src/putil/pythonCallbackObject.cxx

@@ -16,7 +16,7 @@
 #ifdef HAVE_PYTHON
 
 #include "py_panda.h"
-#include "thread.h"
+#include "pythonThread.h"
 #include "callbackData.h"
 #include "config_util.h"
 
@@ -121,8 +121,7 @@ do_python_callback(CallbackData *cbdata) {
   PyObject *args = Py_BuildValue("(O)", pycbdata);
   Py_DECREF(pycbdata);
 
-  PyObject *result =
-    Thread::get_current_thread()->call_python_func(_function, args);
+  PyObject *result = PythonThread::call_python_func(_function, args);
   Py_DECREF(args);
 
   if (result == (PyObject *)NULL) {

+ 0 - 4
panda/src/testbed/pgrid.cxx

@@ -12,7 +12,6 @@
  */
 
 #include "pandaFramework.h"
-#include "pystub.h"
 #include "pandaNode.h"
 #include "transformState.h"
 #include "clockObject.h"
@@ -385,9 +384,6 @@ load_gridded_models(WindowFramework *window,
 
 int
 main(int argc, char **argv) {
-  // A call to pystub() to force libpystub.so to be linked in.
-  pystub();
-
   preprocess_argv(argc, argv);
   PandaFramework framework;
   framework.open_framework(argc, argv);

+ 0 - 4
panda/src/testbed/pview.cxx

@@ -13,7 +13,6 @@
 
 #include "pandaFramework.h"
 #include "pandaSystem.h"
-#include "pystub.h"
 #include "textNode.h"
 #include "configVariableBool.h"
 #include "texturePool.h"
@@ -234,9 +233,6 @@ report_version() {
 
 int
 main(int argc, char **argv) {
-  // A call to pystub() to force libpystub.so to be linked in.
-  pystub();
-
   preprocess_argv(argc, argv);
   framework.open_framework(argc, argv);
   framework.set_window_title("Panda Viewer");

+ 0 - 4
pandatool/src/bam/bamInfo.cxx

@@ -24,7 +24,6 @@
 #include "pvector.h"
 #include "bamCacheRecord.h"
 #include "bamCacheIndex.h"
-#include "pystub.h"
 
 /**
  *
@@ -322,9 +321,6 @@ list_hierarchy(PandaNode *node, int indent_level) {
 }
 
 int main(int argc, char *argv[]) {
-  // A call to pystub() to force libpystub.so to be linked in.
-  pystub();
-
   BamInfo prog;
   prog.parse_command_line(argc, argv);
   prog.run();

+ 0 - 4
pandatool/src/bam/bamToEgg.cxx

@@ -13,7 +13,6 @@
 
 #include "bamToEgg.h"
 #include "save_egg_file.h"
-#include "pystub.h"
 #include "string_utils.h"
 #include "bamFile.h"
 #include "bamCacheRecord.h"
@@ -98,9 +97,6 @@ run() {
 }
 
 int main(int argc, char *argv[]) {
-  // A call to pystub() to force libpystub.so to be linked in.
-  pystub();
-
   BamToEgg prog;
   prog.parse_command_line(argc, argv);
   prog.run();

+ 0 - 4
pandatool/src/bam/eggToBam.cxx

@@ -31,7 +31,6 @@
 #include "load_prc_file.h"
 #include "windowProperties.h"
 #include "frameBufferProperties.h"
-#include "pystub.h"
 
 /**
  *
@@ -485,9 +484,6 @@ make_buffer() {
 
 
 int main(int argc, char *argv[]) {
-  // A call to pystub() to force libpystub.so to be linked in.
-  pystub();
-
   EggToBam prog;
   prog.parse_command_line(argc, argv);
   prog.run();

+ 0 - 4
pandatool/src/bam/ptsToBam.cxx

@@ -19,7 +19,6 @@
 #include "pandaNode.h"
 #include "geomNode.h"
 #include "dcast.h"
-#include "pystub.h"
 #include "string_utils.h"
 #include "config_egg2pg.h"
 
@@ -230,9 +229,6 @@ close_vertex_data() {
 }
 
 int main(int argc, char *argv[]) {
-  // A call to pystub() to force libpystub.so to be linked in.
-  pystub();
-
   PtsToBam prog;
   prog.parse_command_line(argc, argv);
   prog.run();

+ 0 - 4
pandatool/src/cvscopy/testCopy.cxx

@@ -13,7 +13,6 @@
 
 #include "testCopy.h"
 #include "cvsSourceDirectory.h"
-#include "pystub.h"
 
 /**
  *
@@ -59,9 +58,6 @@ copy_file(const Filename &source, const Filename &dest,
 
 
 int main(int argc, char *argv[]) {
-  // A call to pystub() to force libpystub.so to be linked in.
-  pystub();
-
   TestCopy prog;
   prog.parse_command_line(argc, argv);
   prog.run();

+ 0 - 4
pandatool/src/daeprogs/daeToEgg.cxx

@@ -14,7 +14,6 @@
 #include "daeToEgg.h"
 
 #include "daeToEggConverter.h"
-#include "pystub.h"
 
 /**
  *
@@ -76,9 +75,6 @@ run() {
 
 
 int main(int argc, char *argv[]) {
-  // A call to pystub() to force libpystub.so to be linked in.
-  pystub();
-
   DAEToEgg prog;
   prog.parse_command_line(argc, argv);
   prog.run();

+ 0 - 4
pandatool/src/daeprogs/eggToDAE.cxx

@@ -13,7 +13,6 @@
 
 #include "eggToDAE.h"
 #include "dcast.h"
-#include "pystub.h"
 #include "pandaVersion.h"
 
 #include "FCDocument/FCDocument.h"
@@ -165,9 +164,6 @@ void EggToDAE::apply_transform(FCDSceneNode* to, const PT(EggGroup) from) {
 }
 
 int main(int argc, char *argv[]) {
-  // A call to pystub() to force libpystub.so to be linked in.
-  pystub();
-
   EggToDAE prog;
   prog.parse_command_line(argc, argv);
   prog.run();

+ 0 - 4
pandatool/src/dxfprogs/dxfPoints.cxx

@@ -12,7 +12,6 @@
  */
 
 #include "dxfPoints.h"
-#include "pystub.h"
 
 /**
  *
@@ -83,9 +82,6 @@ handle_args(ProgramBase::Args &args) {
 
 
 int main(int argc, char *argv[]) {
-  // A call to pystub() to force libpystub.so to be linked in.
-  pystub();
-
   DXFPoints prog;
   prog.parse_command_line(argc, argv);
   prog.run();

+ 0 - 4
pandatool/src/dxfprogs/dxfToEgg.cxx

@@ -14,7 +14,6 @@
 #include "dxfToEgg.h"
 
 #include "dxfToEggConverter.h"
-#include "pystub.h"
 
 /**
  *
@@ -68,9 +67,6 @@ run() {
 
 
 int main(int argc, char *argv[]) {
-  // A call to pystub() to force libpystub.so to be linked in.
-  pystub();
-
   DXFToEgg prog;
   prog.parse_command_line(argc, argv);
   prog.run();

+ 0 - 4
pandatool/src/dxfprogs/eggToDXF.cxx

@@ -14,7 +14,6 @@
 #include "eggToDXF.h"
 #include "eggPolygon.h"
 #include "dcast.h"
-#include "pystub.h"
 
 /**
  *
@@ -143,9 +142,6 @@ write_entities(ostream &out) {
 
 
 int main(int argc, char *argv[]) {
-  // A call to pystub() to force libpystub.so to be linked in.
-  pystub();
-
   EggToDXF prog;
   prog.parse_command_line(argc, argv);
   prog.run();

+ 0 - 4
pandatool/src/egg-mkfont/eggMakeFont.cxx

@@ -29,7 +29,6 @@
 #include "eggVertex.h"
 #include "string_utils.h"
 #include "dcast.h"
-#include "pystub.h"
 
 #include <ctype.h>
 
@@ -737,9 +736,6 @@ is_numeric(const string &str) {
 }
 
 int main(int argc, char *argv[]) {
-  // A call to pystub() to force libpystub.so to be linked in.
-  pystub();
-
   EggMakeFont prog;
   prog.parse_command_line(argc, argv);
   prog.run();

+ 0 - 4
pandatool/src/egg-optchar/eggOptchar.cxx

@@ -31,7 +31,6 @@
 #include "pset.h"
 #include "compose_matrix.h"
 #include "fftCompressor.h"
-#include "pystub.h"
 
 #include <algorithm>
 
@@ -1512,9 +1511,6 @@ do_defpose() {
 }
 
 int main(int argc, char *argv[]) {
-  // A call to pystub() to force libpystub.so to be linked in.
-  pystub();
-
   EggOptchar prog;
   prog.parse_command_line(argc, argv);
   prog.run();

+ 0 - 4
pandatool/src/egg-palettize/eggPalettize.cxx

@@ -23,7 +23,6 @@
 #include "pnotify.h"
 #include "notifyCategory.h"
 #include "notifySeverity.h"
-#include "pystub.h"
 
 #include <stdio.h>
 
@@ -866,9 +865,6 @@ run() {
 
 int
 main(int argc, char *argv[]) {
-  // A call to pystub() to force libpystub.so to be linked in.
-  pystub();
-
   EggPalettize prog;
   prog.parse_command_line(argc, argv);
   prog.run();

+ 0 - 4
pandatool/src/egg-qtess/eggQtess.cxx

@@ -14,7 +14,6 @@
 #include "eggQtess.h"
 #include "qtessGlobals.h"
 #include "dcast.h"
-#include "pystub.h"
 
 /**
  *
@@ -331,9 +330,6 @@ find_surfaces(EggNode *egg_node) {
 }
 
 int main(int argc, char *argv[]) {
-  // A call to pystub() to force libpystub.so to be linked in.
-  pystub();
-
   EggQtess prog;
   prog.parse_command_line(argc, argv);
   prog.run();

+ 0 - 4
pandatool/src/eggprogs/eggCrop.cxx

@@ -17,7 +17,6 @@
 #include "eggPrimitive.h"
 #include "eggVertex.h"
 #include "dcast.h"
-#include "pystub.h"
 
 /**
  *
@@ -118,9 +117,6 @@ strip_prims(EggGroupNode *group) {
 
 
 int main(int argc, char *argv[]) {
-  // A call to pystub() to force libpystub.so to be linked in.
-  pystub();
-
   EggCrop prog;
   prog.parse_command_line(argc, argv);
   prog.run();

+ 0 - 4
pandatool/src/eggprogs/eggListTextures.cxx

@@ -14,7 +14,6 @@
 #include "eggListTextures.h"
 #include "eggTextureCollection.h"
 #include "pnmImageHeader.h"
-#include "pystub.h"
 
 /**
  *
@@ -59,9 +58,6 @@ run() {
 
 
 int main(int argc, char *argv[]) {
-  // A call to pystub() to force libpystub.so to be linked in.
-  pystub();
-
   EggListTextures prog;
   prog.parse_command_line(argc, argv);
   prog.run();

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