Browse Source

AsyncTaskManager, threaded sound loader

David Rose 19 years ago
parent
commit
3fe0ad1080

+ 2 - 0
direct/src/showbase/EventManager.py

@@ -49,6 +49,8 @@ class EventManager:
             return eventParameter.getStringValue()
         elif (eventParameter.isWstring()):
             return eventParameter.getWstringValue()
+        elif (eventParameter.isTypedRefCount()):
+            return eventParameter.getTypedRefCountValue()
         else:
             # Must be some user defined type, return the ptr
             # which will be downcast to that type

+ 98 - 47
direct/src/showbase/Loader.py

@@ -20,28 +20,27 @@ class Loader(DirectObject):
     loaderIndex = 0
 
     class Callback:
-        def __init__(self, numModels, callback, extraArgs):
-            self.models = [None] * numModels
+        def __init__(self, numObjects, callback, extraArgs):
+            self.objects = [None] * numObjects
             self.callback = callback
             self.extraArgs = extraArgs
-            self.numRemaining = numModels
+            self.numRemaining = numObjects
 
-        def gotModel(self, index, model):
-            self.models[index] = model
+        def gotObject(self, index, object):
+            self.objects[index] = object
             self.numRemaining -= 1
 
             if self.numRemaining == 0:
-                self.callback(*(self.models + self.extraArgs))
+                self.callback(*(self.objects + self.extraArgs))
 
     # special methods
     def __init__(self, base):
         self.base = base
         self.loader = PandaLoader()
-        self.pendingCallbacks = {}
 
         self.hook = "async_loader_%s" % (Loader.loaderIndex)
         Loader.loaderIndex += 1
-        self.accept(self.hook, self.__gotAsyncModel)
+        self.accept(self.hook, self.__gotAsyncObject)
 
     def destroy(self):
         self.ignore(self.hook)
@@ -146,13 +145,14 @@ class Loader(DirectObject):
                     # immediately.
                     node = ModelPool.loadModel(modelPath)
                     nodePath = NodePath(node)
-                    cb.gotModel(i, nodePath)
+                    cb.gotObject(i, nodePath)
 
                 else:
                     # We do need to go to the thread to load this model.
-                    id = self.loader.beginRequest(self.hook)
-                    self.pendingCallbacks[id] = (cb, i)
-                    self.loader.requestLoad(id, Filename(modelPath), loaderOptions)
+                    request = ModelLoadRequest(Filename(modelPath), loaderOptions)
+                    request.setDoneEvent(self.hook)
+                    request.setPythonObject((cb, i))
+                    self.loader.loadAsync(request)
 
     def loadModelOnce(self, modelPath):
         """
@@ -166,7 +166,7 @@ class Loader(DirectObject):
 
         return self.loadModel(modelPath, noCache = False)
 
-    def loadModelCopy(self, modelPath, callback = None):
+    def loadModelCopy(self, modelPath):
         """loadModelCopy(self, string)
         Attempt to load a model from modelPool, if not present
         then attempt to load it from disk. Return a nodepath to
@@ -333,28 +333,76 @@ class Loader(DirectObject):
         TexturePool.releaseTexture(texture)
 
     # sound loading funcs
-    def loadSfx(self, name):
-        assert Loader.notify.debug("Loading sound: %s" % (name))
-        if phaseChecker:
-            phaseChecker(name)
-        # should return a valid sound obj even if soundMgr is invalid
-        sound = None
-        if (name):
-            # showbase-created sfxManager should always be at front of list
-            sound=base.sfxManagerList[0].getSound(name)
-        if sound == None:
-            Loader.notify.warning("Could not load sound file %s." % name)
-        return sound
-
-    def loadMusic(self, name):
-        assert Loader.notify.debug("Loading sound: %s" % (name))
-        # should return a valid sound obj even if musicMgr is invalid
-        sound = None
-        if (name):
-            sound=base.musicManager.getSound(name)
-        if sound == None:
-            Loader.notify.warning("Could not load music file %s." % name)
-        return sound
+    def loadSfx(self, *args, **kw):
+        """Loads one or more sound files, specifically designated as a
+        "sound effect" file (that is, uses the sfxManager to load the
+        sound).  There is no distinction between sound effect files
+        and music files other than the particular AudioManager used to
+        load the sound file, but this distinction allows the sound
+        effects and/or the music files to be adjusted as a group,
+        independently of the other group."""
+        
+        # showbase-created sfxManager should always be at front of list
+        return self.loadSound(base.sfxManagerList[0], *args, **kw)
+    
+    def loadMusic(self, *args, **kw):
+        """Loads one or more sound files, specifically designated as a
+        "music" file (that is, uses the musicManager to load the
+        sound).  There is no distinction between sound effect files
+        and music files other than the particular AudioManager used to
+        load the sound file, but this distinction allows the sound
+        effects and/or the music files to be adjusted as a group,
+        independently of the other group."""
+
+        return self.loadSound(base.musicManager, *args, **kw)
+
+    def loadSound(self, manager, soundPath, positional = False,
+                  callback = None, extraArgs = []):
+
+        """Loads one or more sound files, specifying the particular
+        AudioManager that should be used to load them.  The soundPath
+        may be either a single filename, or a list of filenames.  If a
+        callback is specified, the loading happens in the background,
+        just as in loadModel(); otherwise, the loading happens before
+        loadSound() returns."""
+    
+        if isinstance(soundPath, types.StringTypes) or \
+           isinstance(soundPath, Filename):
+            # We were given a single sound pathname.
+            soundList = [soundPath]
+            gotList = False
+        else:
+            # Assume we were given a list of sound pathnames.
+            soundList = soundPath
+            gotList = True
+
+        if callback is None:
+            # We got no callback, so it's a synchronous load.
+
+            result = []
+            for soundPath in soundList:
+                # should return a valid sound obj even if musicMgr is invalid
+                sound = manager.getSound(soundPath)
+                result.append(sound)
+
+            if gotList:
+                return result
+            else:
+                return result[0]
+
+        else:
+            # We got a callback, so we want an asynchronous (threaded)
+            # load.  We'll return immediately, but when all of the
+            # requested sounds have been loaded, we'll invoke the
+            # callback (passing it the sounds on the parameter list).
+            
+            cb = Loader.Callback(len(soundList), callback, extraArgs)
+            for i in range(len(soundList)):
+                soundPath = soundList[i]
+                request = AudioLoadRequest(manager, soundPath, positional)
+                request.setDoneEvent(self.hook)
+                request.setPythonObject((cb, i))
+                self.loader.loadAsync(request)
 
 ##     def makeNodeNamesUnique(self, nodePath, nodeCount):
 ##         if nodeCount == 0:
@@ -375,18 +423,21 @@ class Loader(DirectObject):
         if (shaderPath != None):
             ShaderPool.releaseShader(shaderPath)
 
-    def __gotAsyncModel(self, id):
-        """A model has just been loaded asynchronously by the
-        sub-thread.  Add it to the list of loaded models, and call the
-        appropriate callback when it's time."""
+    def __gotAsyncObject(self, request):
+        """A model or sound file or some such thing has just been
+        loaded asynchronously by the sub-thread.  Add it to the list
+        of loaded objects, and call the appropriate callback when it's
+        time."""
 
-        cb, i = self.pendingCallbacks[id]
-        del self.pendingCallbacks[id]
+        cb, i = request.getPythonObject()
 
-        node = self.loader.fetchLoad(id)
-        if (node != None):
-            nodePath = NodePath(node)
-        else:
-            nodePath = None
+        object = None
+        if hasattr(request, "getModel"):
+            node = request.getModel()
+            if (node != None):
+                object = NodePath(node)
+
+        elif hasattr(request, "getSound"):
+            object = request.getSound()
 
-        cb.gotModel(i, nodePath)
+        cb.gotObject(i, object)

+ 5 - 2
panda/src/audio/Sources.pp

@@ -4,13 +4,14 @@
 
 #begin lib_target
   #define TARGET audio
-  #define LOCAL_LIBS putil
+  #define LOCAL_LIBS putil event
   
   #define COMBINED_SOURCES $[TARGET]_composite1.cxx
 
   #define SOURCES \
     config_audio.h \
     audioDSP.h \
+    audioLoadRequest.h audioLoadRequest.I \
     audioManager.h \
     audioSound.h \
     nullAudioDSP.h \
@@ -20,6 +21,7 @@
   #define INCLUDED_SOURCES \
     config_audio.cxx \
     audioDSP.cxx \
+    audioLoadRequest.cxx \
     audioManager.cxx \
     audioSound.cxx \
     nullAudioDSP.cxx \
@@ -29,13 +31,14 @@
   #define INSTALL_HEADERS \
     config_audio.h \
     audioDSP.h \
+    audioLoadRequest.h audioLoadRequest.I \
     audioManager.h \
     audioSound.h \
     nullAudioDSP.h \
     nullAudioManager.h \
     nullAudioSound.h
 
-  #define IGATESCAN audio.h
+  #define IGATESCAN all
 #end lib_target
 
 #begin test_bin_target

+ 93 - 0
panda/src/audio/audioLoadRequest.I

@@ -0,0 +1,93 @@
+// Filename: audioLoadRequest.I
+// Created by:  drose (29Aug06)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: AudioLoadRequest::Constructor
+//       Access: Published
+//  Description: Create a new AudioLoadRequest, and add it to the loader
+//               via load_async(), to begin an asynchronous load.
+////////////////////////////////////////////////////////////////////
+INLINE AudioLoadRequest::
+AudioLoadRequest(AudioManager *audio_manager, const string &filename, 
+                 bool positional) :
+  AsyncTask(filename),
+  _audio_manager(audio_manager),
+  _filename(filename),
+  _positional(positional),
+  _is_ready(false)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: AudioLoadRequest::get_audio_manager
+//       Access: Published
+//  Description: Returns the AudioManager that will serve this
+//               asynchronous AudioLoadRequest.
+////////////////////////////////////////////////////////////////////
+INLINE AudioManager *AudioLoadRequest::
+get_audio_manager() const {
+  return _audio_manager;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: AudioLoadRequest::get_filename
+//       Access: Published
+//  Description: Returns the filename associated with this
+//               asynchronous AudioLoadRequest.
+////////////////////////////////////////////////////////////////////
+INLINE const string &AudioLoadRequest::
+get_filename() const {
+  return _filename;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: AudioLoadRequest::get_positional
+//       Access: Published
+//  Description: Returns the positional flag associated with this
+//               asynchronous AudioLoadRequest.
+////////////////////////////////////////////////////////////////////
+INLINE bool AudioLoadRequest::
+get_positional() const {
+  return _positional;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: AudioLoadRequest::is_ready
+//       Access: Published
+//  Description: Returns true if this request has completed, false if
+//               it is still pending.  When this returns true, you may
+//               retrieve the sound loaded by calling get_sound().
+////////////////////////////////////////////////////////////////////
+INLINE bool AudioLoadRequest::
+is_ready() const {
+  return _is_ready;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: AudioLoadRequest::get_sound
+//       Access: Published
+//  Description: Returns the sound that was loaded asynchronously, if
+//               any, or NULL if there was an error.  It is an error
+//               to call this unless is_ready() returns true.
+////////////////////////////////////////////////////////////////////
+INLINE AudioSound *AudioLoadRequest::
+get_sound() const {
+  nassertr(_is_ready, NULL);
+  return _sound;
+}

+ 36 - 0
panda/src/audio/audioLoadRequest.cxx

@@ -0,0 +1,36 @@
+// Filename: audioLoadRequest.cxx
+// Created by:  drose (29Aug06)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "audioLoadRequest.h"
+#include "audioManager.h"
+
+TypeHandle AudioLoadRequest::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: AudioLoadRequest::do_task
+//       Access: Protected, Virtual
+//  Description: Performs the task: that is, loads the one sound file.
+////////////////////////////////////////////////////////////////////
+bool AudioLoadRequest::
+do_task() {
+  _sound = _audio_manager->get_sound(_filename, _positional);
+  _is_ready = true;
+
+  // Don't continue the task; we're done.
+  return false;
+}

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

@@ -0,0 +1,82 @@
+// Filename: audioLoadRequest.h
+// Created by:  drose (29Aug06)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef AUDIOLOADREQUEST_H
+#define AUDIOLOADREQUEST_H
+
+#include "pandabase.h"
+
+#include "asyncTask.h"
+#include "audioManager.h"
+#include "audioSound.h"
+#include "pointerTo.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : AudioLoadRequest
+// Description : A class object that manages a single asynchronous
+//               audio load request.  This works in conjunction with
+//               the Loader class defined in pgraph, or really with
+//               any AsyncTaskManager.  Create a new AudioLoadRequest,
+//               and add it to the loader via load_async(), to begin
+//               an asynchronous load.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA AudioLoadRequest : public AsyncTask {
+PUBLISHED:
+  INLINE AudioLoadRequest(AudioManager *audio_manager, const string &filename, 
+                          bool positional);
+
+  INLINE AudioManager *get_audio_manager() const;
+  INLINE const string &get_filename() const;
+  INLINE bool get_positional() const;
+
+  INLINE bool is_ready() const;
+  INLINE AudioSound *get_sound() const;
+
+protected:
+  virtual bool do_task();
+
+private:
+  PT(AudioManager) _audio_manager;
+  string _filename;
+  bool _positional;
+
+  bool _is_ready;
+  PT(AudioSound) _sound;
+  
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    AsyncTask::init_type();
+    register_type(_type_handle, "AudioLoadRequest",
+                  AsyncTask::get_class_type());
+    }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+  
+private:
+  static TypeHandle _type_handle;
+};
+
+#include "audioLoadRequest.I"
+
+#endif
+

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

@@ -1,8 +1,9 @@
-
 #include "config_audio.cxx"
+#include "audioLoadRequest.cxx"
 #include "audioManager.cxx"
 #include "audioSound.cxx"
 #include "audioDSP.cxx"
+#include "nullAudioDSP.cxx"
 #include "nullAudioManager.cxx"
 #include "nullAudioSound.cxx"
 

+ 4 - 0
panda/src/audio/config_audio.cxx

@@ -18,8 +18,10 @@
 
 #include "config_audio.h"
 #include "dconfig.h"
+#include "audioLoadRequest.h"
 #include "audioManager.h"
 #include "audioSound.h"
+#include "nullAudioDSP.h"
 #include "nullAudioManager.h"
 #include "nullAudioSound.h"
 
@@ -88,9 +90,11 @@ ConfigVariableInt audio_output_channels
 
 
 ConfigureFn(config_audio) {
+  AudioLoadRequest::init_type();
   AudioManager::init_type();
   AudioSound::init_type();
   AudioDSP::init_type();
+  nullAudioDSP::init_type();
   NullAudioManager::init_type();
   NullAudioSound::init_type();
 }

+ 7 - 1
panda/src/event/Sources.pp

@@ -5,9 +5,11 @@
 #begin lib_target
   #define TARGET event
   
-  #define COMBINED_SOURCES $[TARGET]_composite1.cxx
+  #define COMBINED_SOURCES $[TARGET]_composite1.cxx $[TARGET]_composite2.cxx
   
   #define SOURCES \
+    asyncTask.h asyncTask.I \
+    asyncTaskManager.h asyncTaskManager.I \
     config_event.h \
     buttonEvent.I buttonEvent.h \
     buttonEventList.I buttonEventList.h \
@@ -17,6 +19,8 @@
     pt_Event.h throw_event.I throw_event.h 
     
   #define INCLUDED_SOURCES \
+    asyncTask.cxx \
+    asyncTaskManager.cxx \
     buttonEvent.cxx \
     buttonEventList.cxx \
     config_event.cxx event.cxx eventHandler.cxx \ 
@@ -24,6 +28,8 @@
     pt_Event.cxx
 
   #define INSTALL_HEADERS \
+    asyncTask.h asyncTask.I \
+    asyncTaskManager.h asyncTaskManager.I \
     buttonEvent.I buttonEvent.h \
     buttonEventList.I buttonEventList.h \
     event.I event.h eventHandler.h eventHandler.I \

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

@@ -0,0 +1,104 @@
+// Filename: asyncTask.I
+// Created by:  drose (23Aug06)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: AsyncTask::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE AsyncTask::
+AsyncTask(const string &name) : 
+  Namable(name),
+  _state(S_inactive),
+  _manager(NULL)
+{
+#ifdef HAVE_PYTHON
+  _python_object = NULL;
+#endif  // HAVE_PYTHON
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: AsyncTask::get_state
+//       Access: Published
+//  Description: Returns the current state of the task.
+////////////////////////////////////////////////////////////////////
+INLINE AsyncTask::State AsyncTask::
+get_state() const {
+  return _state;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: AsyncTask::set_done_event
+//       Access: Published
+//  Description: Sets the event name that will be triggered
+//               when the task finishes.  This should only be called
+//               before the task has been started, or after it has
+//               finished and before it is about to be restarted
+//               (i.e. when get_state() returns S_inactive).
+////////////////////////////////////////////////////////////////////
+INLINE void AsyncTask::
+set_done_event(const string &done_event) {
+  nassertv(_state == S_inactive);
+  _done_event = done_event;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: AsyncTask::get_done_event
+//       Access: Published
+//  Description: Returns the event name that will be triggered
+//               when the task finishes.  See set_done_event().
+////////////////////////////////////////////////////////////////////
+INLINE const string &AsyncTask::
+get_done_event() const {
+  return _done_event;
+}
+
+#ifdef HAVE_PYTHON
+////////////////////////////////////////////////////////////////////
+//     Function: AsyncTask::set_python_object
+//       Access: Published
+//  Description: Specifies an arbitrary Python object that will be
+//               piggybacked on the task object.
+////////////////////////////////////////////////////////////////////
+INLINE 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
+////////////////////////////////////////////////////////////////////
+//     Function: AsyncTask::get_python_object
+//       Access: Published
+//  Description: Returns the Python object that was specified to
+//               set_python_object(), if any, or None if no object was
+//               specified.
+////////////////////////////////////////////////////////////////////
+INLINE PyObject *AsyncTask::
+get_python_object() const {
+  if (_python_object != (PyObject *)NULL) {
+    Py_XINCREF(_python_object);
+    return _python_object;
+  }
+  Py_RETURN_NONE;
+}
+#endif  // HAVE_PYTHON
+

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

@@ -0,0 +1,41 @@
+// Filename: asyncTask.cxx
+// Created by:  drose (23Aug06)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "asyncTask.h"
+
+TypeHandle AsyncTask::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: AsyncTask::Destructor
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+AsyncTask::
+~AsyncTask() {
+  nassertv(_state != S_active && _manager == NULL);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: AsyncTask::output
+//       Access: Published, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void AsyncTask::
+output(ostream &out) const {
+  out << get_type() << " " << get_name();
+}

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

@@ -0,0 +1,110 @@
+// Filename: asyncTask.h
+// Created by:  drose (23Aug06)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef ASYNCTASK_H
+#define ASYNCTASK_H
+
+#include "pandabase.h"
+
+#include "typedReferenceCount.h"
+#include "namable.h"
+#include "pmutex.h"
+#include "conditionVar.h"
+
+#ifdef HAVE_PYTHON
+
+#undef HAVE_LONG_LONG  // NSPR and Python both define this.
+#undef _POSIX_C_SOURCE
+#include <Python.h>
+
+#endif  // HAVE_PYTHON
+
+class AsyncTaskManager;
+
+////////////////////////////////////////////////////////////////////
+//       Class : AsyncTask
+// Description : This class represents a concrete task performed by an
+//               AsyncManager.  Normally, you would subclass from this
+//               class, and override do_task(), to define the
+//               functionality you wish to have the task perform.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA AsyncTask : public TypedReferenceCount, public Namable {
+public:
+  INLINE AsyncTask(const string &name);
+  virtual ~AsyncTask();
+
+PUBLISHED:
+  enum State {
+    S_inactive,
+    S_active,
+    S_servicing,
+  };
+
+  INLINE State get_state() const;
+
+  INLINE void set_done_event(const string &done_event);
+  INLINE const string &get_done_event() const;
+
+#ifdef HAVE_PYTHON
+  INLINE void set_python_object(PyObject *python_object);
+  INLINE PyObject *get_python_object() const;
+#endif  // HAVE_PYTHON
+
+  virtual void output(ostream &out) const;
+
+protected:
+  virtual bool do_task()=0;
+
+protected:
+  string _done_event;
+  State _state;
+  AsyncTaskManager *_manager;
+
+private:
+#ifdef HAVE_PYTHON
+  PyObject *_python_object;
+#endif  // HAVE_PYTHON
+  
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    TypedReferenceCount::init_type();
+    register_type(_type_handle, "AsyncTask",
+                  TypedReferenceCount::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+
+  friend class AsyncTaskManager;
+};
+
+INLINE ostream &operator << (ostream &out, const AsyncTask &task) {
+  task.output(out);
+  return out;
+};
+
+#include "asyncTask.I"
+
+#endif

+ 45 - 0
panda/src/event/asyncTaskManager.I

@@ -0,0 +1,45 @@
+// Filename: asyncTaskManager.I
+// Created by:  drose (23Aug06)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: AsyncTaskManager::get_num_threads
+//       Access: Published
+//  Description: Returns the number of threads that have been created
+//               to service the tasks within this task manager.
+////////////////////////////////////////////////////////////////////
+INLINE int AsyncTaskManager::
+get_num_threads() const {
+#ifdef HAVE_THREADS
+  return _num_threads;
+#else
+  // Without threading support, this is always 0 threads.
+  return 0;
+#endif
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: AsyncTaskManager::get_num_tasks
+//       Access: Published
+//  Description: Returns the number of tasks that are currently active
+//               within the task manager.
+////////////////////////////////////////////////////////////////////
+INLINE int AsyncTaskManager::
+get_num_tasks() const {
+  return _num_tasks;
+}

+ 415 - 0
panda/src/event/asyncTaskManager.cxx

@@ -0,0 +1,415 @@
+// Filename: asyncTaskManager.cxx
+// Created by:  drose (23Aug06)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "asyncTaskManager.h"
+#include "event.h"
+#include "pt_Event.h"
+#include "throw_event.h"
+#include "eventParameter.h"
+#include "mutexHolder.h"
+#include "indent.h"
+
+TypeHandle AsyncTaskManager::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: AsyncTaskManager::Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+AsyncTaskManager::
+AsyncTaskManager(const string &name, int num_threads) :
+  Namable(name),
+  _num_threads(num_threads),
+  _cvar(_lock),
+  _state(S_initial),
+  _num_tasks(0)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: AsyncTaskManager::Destructor
+//       Access: Published, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+AsyncTaskManager::
+~AsyncTaskManager() {
+  if (_state == S_started) {
+    // Clean up all of the threads.
+    MutexHolder holder(_lock);
+    _state = S_shutdown;
+    _cvar.signal_all();
+
+    Threads::iterator ti;
+    for (ti = _threads.begin(); ti != _threads.end(); ++ti) {
+      (*ti)->join();
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: AsyncTaskManager::add
+//       Access: Published
+//  Description: Adds the indicated task to the active queue.  It is
+//               an error if the task is already added to this or any
+//               other active queue.
+////////////////////////////////////////////////////////////////////
+void AsyncTaskManager::
+add(AsyncTask *task) {
+  MutexHolder holder(_lock);
+
+  nassertv(task->_manager == NULL &&
+           task->_state == AsyncTask::S_inactive);
+  nassertv(find_task(task) == -1);
+
+  // Attempt to start the threads, if we haven't already.
+  if (_state == S_initial) {
+    _state = S_started;
+    if (Thread::is_threading_supported()) {
+      _threads.reserve(_num_threads);
+      for (int i = 0; i < _num_threads; ++i) {
+        PT(AsyncTaskManagerThread) thread = new AsyncTaskManagerThread(this);
+        if (thread->start(TP_low, true, true)) {
+          _threads.push_back(thread);
+        }
+      }
+    }
+  }
+
+  task->_manager = this;
+  task->_state = AsyncTask::S_active;
+
+  _active.push_back(task);
+  ++_num_tasks;
+  _cvar.signal_all();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: AsyncTaskManager::add_and_do
+//       Access: Published
+//  Description: Adds the indicated task to the active queue, and
+//               executes it immediately if this is a non-threaded
+//               task manager.
+//
+//               The only difference between this method and add() is
+//               in the case of a non-threaded task manager: in this
+//               case, the method will execute the task inline, at
+//               least one frame, before returning.  If the task
+//               completes in one frame, this means it will completely
+//               execute the task before returning in the non-threaded
+//               case.  In the threaded case, this method behaves
+//               exactly the same as add().
+
+//               The return value is true if the task has been added
+//               and is still pending, false if it has completed.
+////////////////////////////////////////////////////////////////////
+bool AsyncTaskManager::
+add_and_do(AsyncTask *task) {
+  MutexHolder holder(_lock);
+
+  nassertr(task->_manager == NULL &&
+           task->_state == AsyncTask::S_inactive, false);
+  nassertr(find_task(task) == -1, false);
+
+  // Attempt to start the threads, if we haven't already.
+  if (_state == S_initial) {
+    _state = S_started;
+    if (Thread::is_threading_supported()) {
+      _threads.reserve(_num_threads);
+      for (int i = 0; i < _num_threads; ++i) {
+        PT(AsyncTaskManagerThread) thread = new AsyncTaskManagerThread(this);
+        if (thread->start(TP_low, true, true)) {
+          _threads.push_back(thread);
+        }
+      }
+    }
+  }
+
+  task->_manager = this;
+  task->_state = AsyncTask::S_active;
+
+  if (_threads.empty()) {
+    // Got no threads.  Try to execute the task immediately.
+    if (!task->do_task()) {
+      // The task has finished in one frame.  Don't add it to the
+      // queue.
+      task_done(task);
+      return false;
+    }
+  }
+
+  _active.push_back(task);
+  ++_num_tasks;
+  _cvar.signal_all();
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: AsyncTaskManager::remove
+//       Access: Published
+//  Description: Removes the indicated task from the active queue.
+//               Returns true if the task is successfully removed, or
+//               false if it wasn't there.
+////////////////////////////////////////////////////////////////////
+bool AsyncTaskManager::
+remove(AsyncTask *task) {
+  MutexHolder holder(_lock);
+
+  // It's just possible this particular task is currently being
+  // serviced.  Wait for it to finish.
+  while (task->_manager == this && 
+         task->_state == AsyncTask::S_servicing) {
+    _cvar.wait();
+  }
+
+  if (task->_manager != this) {
+    nassertr(find_task(task) == -1, false);
+    return false;
+  }
+
+  nassertr(task->_state == AsyncTask::S_active, false);
+
+  int index = find_task(task);
+  nassertr(index != -1, false);
+  _active.erase(_active.begin() + index);
+  --_num_tasks;
+
+  task->_state = AsyncTask::S_inactive;
+  task->_manager = NULL;
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: AsyncTaskManager::has_task
+//       Access: Published
+//  Description: Returns true if the indicated task is currently in
+//               this manager's active queue, or false otherwise.
+////////////////////////////////////////////////////////////////////
+bool AsyncTaskManager::
+has_task(AsyncTask *task) const {
+  MutexHolder holder(_lock);
+
+  if (task->_manager != this) {
+    nassertr(find_task(task) == -1, false);
+    return false;
+  }
+
+  // The task might not actually be in the active queue, since it
+  // might be being serviced right now.  That's OK.
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: AsyncTaskManager::poll
+//       Access: Published
+//  Description: Runs through all the tasks in the task list, once, if
+//               the task manager is running in single-threaded mode
+//               (no threads available).  This method does nothing in
+//               threaded mode, so it may safely be called in either
+//               case.
+////////////////////////////////////////////////////////////////////
+void AsyncTaskManager::
+poll() {
+  MutexHolder holder(_lock);
+  if (_threads.empty()) {
+    return;
+  }
+
+  Tasks new_active;
+  int new_num_tasks = 0;
+  Tasks::iterator ti;
+  for (ti = _active.begin(); ti != _active.end(); ++ti) {
+    AsyncTask *task = (*ti);
+    nassertv(task->_state == AsyncTask::S_active);
+    task->_state = AsyncTask::S_servicing;
+
+    // Here we keep the manager lock held while we are servicing each
+    // task.  This is the single-threaded implementation, after all,
+    // so what difference should it make?
+    if (task->do_task()) {
+      new_active.push_back(task);
+      ++new_num_tasks;
+      task->_state = AsyncTask::S_active;
+
+    } else {
+      // The task has finished.
+      task_done(task);
+    }
+  }
+
+  _active.swap(new_active);
+  _num_tasks = new_num_tasks;
+  _cvar.signal_all();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: AsyncTaskManager::output
+//       Access: Published, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void AsyncTaskManager::
+output(ostream &out) const {
+  MutexHolder holder(_lock);
+
+  out << get_type() << " " << get_name()
+      << "; " << get_num_tasks() << " tasks";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: AsyncTaskManager::write
+//       Access: Published, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void AsyncTaskManager::
+write(ostream &out, int indent_level) const {
+  MutexHolder holder(_lock);
+  indent(out, indent_level)
+    << get_type() << " " << get_name() << "\n";
+
+  Threads::const_iterator thi;
+  for (thi = _threads.begin(); thi != _threads.end(); ++thi) {
+    AsyncTask *task = (*thi)->_servicing;
+    if (task != (AsyncTask *)NULL) {
+      indent(out, indent_level + 1)
+        << "*" << *task << "\n";
+    }
+  }
+
+  Tasks::const_iterator ti;
+  for (ti = _active.begin(); ti != _active.end(); ++ti) {
+    AsyncTask *task = (*ti);
+    indent(out, indent_level + 2)
+      << *task << "\n";
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: AsyncTaskManager::find_task
+//       Access: Protected
+//  Description: Returns the index number within the given request
+//               queue of the indicated task, or -1 if the task is not
+//               found in the active queue (this may mean that it is
+//               currently being serviced).  Assumes that the lock is
+//               currently held.
+////////////////////////////////////////////////////////////////////
+int AsyncTaskManager::
+find_task(AsyncTask *task) const {
+  for (int i = 0; i < (int)_active.size(); ++i) {
+    if (_active[i] == task) {
+      return i;
+    }
+  }
+
+  return -1;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: AsyncTaskManager::service_one_task
+//       Access: Protected
+//  Description: Pops a single task off the active queue, services it,
+//               and restores it to the end of the queue.  This is
+//               called internally only within one of the task
+//               threads.  Assumes the lock is already held.
+////////////////////////////////////////////////////////////////////
+void AsyncTaskManager::
+service_one_task(AsyncTaskManager::AsyncTaskManagerThread *thread) {
+  if (!_active.empty()) {
+    AsyncTask *task = _active.front();
+    _active.pop_front();
+    thread->_servicing = task;
+
+    nassertv(task->_state == AsyncTask::S_active);
+    task->_state = AsyncTask::S_servicing;
+
+    // Now release the manager lock while we actually service the
+    // task.
+    _lock.release();
+    bool keep = task->do_task();
+
+    // Now we have to re-acquire the manager lock, so we can put the
+    // task back on the queue (and so we can return with the lock
+    // still held).
+    _lock.lock();
+
+    thread->_servicing = NULL;
+    if (task->_manager == this) {
+      if (keep) {
+        // The task is still alive; put it on the end of the active
+        // queue.
+        _active.push_back(task);
+        task->_state = AsyncTask::S_active;
+        
+      } else {
+        // The task has finished.
+        task_done(task);
+      }
+      _cvar.signal_all();
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: AsyncTaskManager::task_done
+//       Access: Protected
+//  Description: Called internally when a task has completed and is
+//               about to be removed from the active queue.  Assumes
+//               the lock is held.
+////////////////////////////////////////////////////////////////////
+void AsyncTaskManager::
+task_done(AsyncTask *task) {
+  task->_state = AsyncTask::S_inactive;
+  task->_manager = NULL;
+
+  if (!task->_done_event.empty()) {
+    PT_Event event = new Event(task->_done_event);
+    event->add_parameter(EventParameter(task));
+    throw_event(event);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: AsyncTaskManager::AsyncTaskManagerThread::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+AsyncTaskManager::AsyncTaskManagerThread::
+AsyncTaskManagerThread(AsyncTaskManager *manager) :
+  Thread(manager->get_name(), manager->get_name()),
+  _manager(manager),
+  _servicing(NULL)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: AsyncTaskManager::AsyncTaskManagerThread::thread_main
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void AsyncTaskManager::AsyncTaskManagerThread::
+thread_main() {
+  MutexHolder holder(_manager->_lock);
+  while (_manager->_state != AsyncTaskManager::S_shutdown) {
+    _manager->service_one_task(this);
+    if (_manager->_active.empty() &&
+        _manager->_state != AsyncTaskManager::S_shutdown) {
+      _manager->_cvar.wait();
+    }
+  }
+}
+

+ 125 - 0
panda/src/event/asyncTaskManager.h

@@ -0,0 +1,125 @@
+// Filename: asyncTaskManager.h
+// Created by:  drose (23Aug06)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef ASYNCTASKMANAGER_H
+#define ASYNCTASKMANAGER_H
+
+#include "pandabase.h"
+
+#include "asyncTask.h"
+#include "typedReferenceCount.h"
+#include "thread.h"
+#include "pmutex.h"
+#include "conditionVarFull.h"
+#include "pvector.h"
+#include "pdeque.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : AsyncTaskManager
+// Description : A class to manage a loose queue of isolated tasks,
+//               which can be performed by a background thread (in
+//               particular, for instance, loading a model file).
+//
+//               The AsyncTaskManager will spawn a specified number of
+//               threads (possibly 0) to serve the tasks.  If there
+//               are no threads, you must call poll() from time to
+//               time to serve the tasks in the main thread.
+//
+//               Each task, once added to the FIFO queue, will
+//               eventually be executed by one of the threads; if the
+//               task then indicates it has more work to do, it will
+//               be replaced at the end of the queue to go around
+//               again.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA AsyncTaskManager : public TypedReferenceCount, public Namable {
+PUBLISHED:
+  AsyncTaskManager(const string &name, int num_threads);
+  virtual ~AsyncTaskManager();
+
+  INLINE int get_num_threads() const;
+
+  void add(AsyncTask *task);
+  bool add_and_do(AsyncTask *task);
+  bool remove(AsyncTask *task);
+  bool has_task(AsyncTask *task) const;
+
+  INLINE int get_num_tasks() const;
+
+  void poll();
+
+  virtual void output(ostream &out) const;
+  virtual void write(ostream &out, int indent_level = 0) const;
+
+protected:
+  class AsyncTaskManagerThread;
+
+  int find_task(AsyncTask *task) const;
+  void service_one_task(AsyncTaskManagerThread *thread);
+  void task_done(AsyncTask *task);
+
+  class AsyncTaskManagerThread : public Thread {
+  public:
+    AsyncTaskManagerThread(AsyncTaskManager *manager);
+    virtual void thread_main();
+
+    AsyncTaskManager *_manager;
+    AsyncTask *_servicing;
+  };
+
+  typedef pvector< PT(AsyncTaskManagerThread) > Threads;
+  typedef pdeque< PT(AsyncTask) > Tasks;
+
+  int _num_threads;
+
+  Mutex _lock;  // Protects all the following members.
+  ConditionVarFull _cvar;  // singaled when _active or _state changes, or a task finishes.
+
+  enum State {
+    S_initial,  // no threads yet
+    S_started,  // threads have been started
+    S_shutdown  // waiting for thread shutdown
+  };
+
+  Threads _threads;
+  Tasks _active;
+  int _num_tasks;
+  State _state;
+  
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    TypedReferenceCount::init_type();
+    register_type(_type_handle, "AsyncTaskManager",
+                  TypedReferenceCount::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+
+  friend class AsyncTaskManagerThread;
+};
+
+#include "asyncTaskManager.I"
+
+#endif

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

@@ -17,6 +17,8 @@
 ////////////////////////////////////////////////////////////////////
 
 #include "config_event.h"
+#include "asyncTask.h"
+#include "asyncTaskManager.h"
 #include "buttonEventList.h"
 #include "event.h"
 #include "eventHandler.h"
@@ -28,6 +30,8 @@ Configure(config_event);
 NotifyCategoryDef(event, "");
 
 ConfigureFn(config_event) {
+  AsyncTask::init_type();
+  AsyncTaskManager::init_type();
   ButtonEventList::init_type();
   Event::init_type();
   EventHandler::init_type();
@@ -36,6 +40,7 @@ ConfigureFn(config_event) {
   EventStoreDouble::init_type("EventStoreDouble");
   EventStoreString::init_type("EventStoreString");
   EventStoreWstring::init_type("EventStoreWstring");
+  EventStoreTypedRefCount::init_type("EventStoreTypedRefCount");
 
   ButtonEventList::register_with_read_factory();
   EventStoreInt::register_with_read_factory();

+ 53 - 0
panda/src/event/eventParameter.cxx

@@ -26,6 +26,59 @@
 
 TypeHandle EventStoreValueBase::_type_handle;
 
+////////////////////////////////////////////////////////////////////
+//     Function: EventParameter::Pointer constructor
+//       Access: Public
+//  Description: Defines an EventParameter that stores a pointer to
+//               a TypedReferenceCount object.  Note that a
+//               TypedReferenceCount is not the same kind of pointer
+//               as a TypedWritableReferenceCount, hence we require
+//               both constructors.
+//
+//               This accepts a const pointer, even though it stores
+//               (and eventually returns) a non-const pointer.  This
+//               is just the simplest way to allow both const and
+//               non-const pointers to be stored, but it does lose the
+//               constness.  Be careful.
+//
+//               This constructor, and the accessors for this type of
+//               event parameter, are declared non-inline so we don't
+//               have to worry about exporting this template class
+//               from this DLL.
+////////////////////////////////////////////////////////////////////
+EventParameter::
+EventParameter(const TypedReferenceCount *ptr) : _ptr(new EventStoreTypedRefCount((TypedReferenceCount *)ptr)) { }
+
+////////////////////////////////////////////////////////////////////
+//     Function: EventParameter::is_typed_ref_count
+//       Access: Public
+//  Description: Returns true if the EventParameter stores a
+//               TypedReferenceCount pointer, false otherwise.  Note
+//               that a TypedReferenceCount is not exactly the same
+//               kind of pointer as a TypedWritableReferenceCount,
+//               hence the need for this separate call.
+////////////////////////////////////////////////////////////////////
+bool EventParameter::
+is_typed_ref_count() const {
+  if (is_empty()) {
+    return false;
+  }
+  return _ptr->is_of_type(EventStoreTypedRefCount::get_class_type());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EventParameter::get_typed_ref_count_value
+//       Access: Public
+//  Description: Retrieves the value stored in the EventParameter.  It
+//               is only valid to call this if is_typed_ref_count()
+//               has already returned true.
+////////////////////////////////////////////////////////////////////
+TypedReferenceCount *EventParameter::
+get_typed_ref_count_value() const {
+  nassertr(is_typed_ref_count(), NULL);
+  return ((const EventStoreTypedRefCount *)_ptr.p())->get_value();
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: EventParameter::output
 //       Access: Public

+ 5 - 0
panda/src/event/eventParameter.h

@@ -43,6 +43,7 @@ class EXPCL_PANDA EventParameter {
 PUBLISHED:
   INLINE EventParameter();
   INLINE EventParameter(const TypedWritableReferenceCount *ptr);
+  EventParameter(const TypedReferenceCount *ptr);
   INLINE EventParameter(int value);
   INLINE EventParameter(double value);
   INLINE EventParameter(const string &value);
@@ -67,6 +68,9 @@ PUBLISHED:
   INLINE bool is_wstring() const;
   INLINE wstring get_wstring_value() const;
 
+  bool is_typed_ref_count() const;
+  TypedReferenceCount *get_typed_ref_count_value() const;
+
   INLINE TypedWritableReferenceCount *get_ptr() const;
 
   void output(ostream &out) const;
@@ -170,6 +174,7 @@ typedef EventStoreValue<int> EventStoreInt;
 typedef EventStoreValue<double> EventStoreDouble;
 typedef EventStoreValue<string> EventStoreString;
 typedef EventStoreValue<wstring> EventStoreWstring;
+typedef EventStoreValue< PT(TypedReferenceCount) > EventStoreTypedRefCount;
 
 #include "eventParameter.I"
 

+ 2 - 7
panda/src/event/event_composite1.cxx

@@ -1,10 +1,5 @@
+#include "asyncTask.cxx"
+#include "asyncTaskManager.cxx"
 #include "buttonEvent.cxx"
 #include "buttonEventList.cxx"
 #include "config_event.cxx"
-#include "event.cxx"
-#include "eventHandler.cxx"
-#include "eventParameter.cxx"
-#include "eventQueue.cxx"
-#include "eventReceiver.cxx"
-#include "pt_Event.cxx"
-

+ 7 - 0
panda/src/event/event_composite2.cxx

@@ -0,0 +1,7 @@
+#include "event.cxx"
+#include "eventHandler.cxx"
+#include "eventParameter.cxx"
+#include "eventQueue.cxx"
+#include "eventReceiver.cxx"
+#include "pt_Event.cxx"
+

+ 3 - 0
panda/src/pgraph/Sources.pp

@@ -66,6 +66,7 @@
     lodNode.I lodNode.h \
     materialAttrib.I materialAttrib.h \
     materialCollection.I materialCollection.h \
+    modelLoadRequest.I modelLoadRequest.h \
     modelNode.I modelNode.h \
     modelPool.I modelPool.h \
     modelRoot.I modelRoot.h \
@@ -170,6 +171,7 @@
     lodNode.cxx \
     materialAttrib.cxx \
     materialCollection.cxx \
+    modelLoadRequest.cxx \
     modelNode.cxx \
     modelPool.cxx \
     modelRoot.cxx \
@@ -269,6 +271,7 @@
     lodNode.I lodNode.h \
     materialAttrib.I materialAttrib.h \
     materialCollection.I materialCollection.h \
+    modelLoadRequest.I modelLoadRequest.h \
     modelNode.I modelNode.h \
     modelPool.I modelPool.h \
     modelRoot.I modelRoot.h \

+ 4 - 0
panda/src/pgraph/config_pgraph.cxx

@@ -51,11 +51,13 @@
 #include "lightAttrib.h"
 #include "lightLensNode.h"
 #include "lightNode.h"
+#include "loader.h"
 #include "loaderFileType.h"
 #include "loaderFileTypeBam.h"
 #include "loaderFileTypeRegistry.h"
 #include "lodNode.h"
 #include "materialAttrib.h"
+#include "modelLoadRequest.h"
 #include "modelNode.h"
 #include "modelRoot.h"
 #include "nodePath.h"
@@ -313,10 +315,12 @@ init_libpgraph() {
   LightAttrib::init_type();
   LightLensNode::init_type();
   LightNode::init_type();
+  Loader::init_type();
   LODNode::init_type();
   LoaderFileType::init_type();
   LoaderFileTypeBam::init_type();
   MaterialAttrib::init_type();
+  ModelLoadRequest::init_type();
   ModelNode::init_type();
 
   ModelRoot::init_type();

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

@@ -129,3 +129,31 @@ load_sync(const Filename &filename, const LoaderOptions &options) const {
   }
   return load_file(filename, options);
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: Loader::load_async
+//       Access: Published
+//  Description: Begins an asynchronous load request.  To use this
+//               call, first create a new ModelLoadRequest object with
+//               the filename you wish to load, and then add that
+//               object to the Loader with load_async.  This function
+//               will return immediately, and the model will be loaded
+//               in the background.
+//
+//               To determine when the model has completely loaded,
+//               you may poll request->is_ready() from time to time,
+//               or set the done_event on the request object and
+//               listen for that event.  When the model is ready, you
+//               may retrieve it via request->get_model().
+//
+//               If threading support is not enabled, or the Loader
+//               was created with 0 threads (that is,
+//               get_num_threads() returns 0), then this will be the
+//               same as a load_sync() call: the model will be loaded
+//               within the current thread, and this method will not
+//               return until the model has fully loaded.
+////////////////////////////////////////////////////////////////////
+INLINE void Loader::
+load_async(AsyncTask *request) {
+  add_and_do(request);
+}

+ 5 - 251
panda/src/pgraph/loader.cxx

@@ -24,21 +24,14 @@
 #include "config_express.h"
 #include "config_util.h"
 #include "virtualFileSystem.h"
-#include "event.h"
-#include "pt_Event.h"
-#include "throw_event.h"
-#include "eventParameter.h"
 #include "filename.h"
 #include "load_dso.h"
 #include "string_utils.h"
 #include "bamCache.h"
 #include "bamCacheRecord.h"
-#include "mutexHolder.h"
-
-#include <algorithm>
-
 
 bool Loader::_file_types_loaded = false;
+TypeHandle Loader::_type_handle;
 
 ////////////////////////////////////////////////////////////////////
 //     Function: Loader::Constructor
@@ -47,10 +40,8 @@ bool Loader::_file_types_loaded = false;
 ////////////////////////////////////////////////////////////////////
 Loader::
 Loader(const string &name, int num_threads) :
-  Namable(name),
-  _cvar(_lock) 
+  AsyncTaskManager(name, num_threads)
 {
-  _num_threads = num_threads;
   if (_num_threads < 0) {
     // -1 means the default number of threads.
 
@@ -67,29 +58,6 @@ Loader(const string &name, int num_threads) :
 
     _num_threads = loader_num_threads;
   }
-
-  _next_id = 1;
-  _state = S_initial;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: Loader::Destructor
-//       Access: Published, Virtual
-//  Description:
-////////////////////////////////////////////////////////////////////
-Loader::
-~Loader() {
-  if (_state == S_started) {
-    // Clean up all of the threads.
-    MutexHolder holder(_lock);
-    _state = S_shutdown;
-    _cvar.signal();
-
-    Threads::iterator ti;
-    for (ti = _threads.begin(); ti != _threads.end(); ++ti) {
-      (*ti)->join();
-    }
-  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -194,144 +162,6 @@ find_all_files(const Filename &filename, const DSearchPath &search_path,
   return num_added;
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: Loader::begin_request
-//       Access: Published
-//  Description: The first step of an asynchronous load request.  This
-//               method slots a record to hold a new request for a
-//               model load, and returns a unique ID corresponding to
-//               that record.
-//
-//               The parameter is the event name that is to be
-//               generated when the model is successfully loaded.
-//
-//               The caller should record the new ID, and then call
-//               request_load() with the same ID.
-////////////////////////////////////////////////////////////////////
-int Loader::
-begin_request(const string &event_name) {
-  if (!_file_types_loaded) {
-    load_file_types();
-  }
-
-  LoaderRequest *request = new LoaderRequest;
-  request->_event_name = event_name;
-
-  {
-    MutexHolder holder(_lock);
-    request->_id = _next_id;
-    ++_next_id;
-    _initial.push_back(request);
-  }
-
-  return request->_id;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: Loader::request_load
-//       Access: Published
-//  Description: The second step of an asychronous load request.  Once
-//               the caller has obtained a unique ID with
-//               begin_request(), it should then call request_load()
-//               to specify the actual filename that should be loaded.
-//
-//               The model will be loaded in the background.  When it
-//               is successfully loaded, the event name (specified to
-//               begin_request) will be generated, with one parameter:
-//               the ID of this request.  At any point after that, the
-//               caller must then call fetch_load() to retrieve the
-//               requested model.
-//
-//               Note that it is possible that the loaded event will
-//               be generated even before this function returns.
-//               Thus, it is necessary for the caller to save the ID
-//               and prepare its to receive the loaded event before
-//               making this call.
-////////////////////////////////////////////////////////////////////
-void Loader::
-request_load(int id, const Filename &filename, 
-             const LoaderOptions &options) {
-  MutexHolder holder(_lock);
-
-  int index = find_id(_initial, id);
-  if (index == -1) {
-    nassert_raise("No such loader ID.");
-    return;
-  }
-  
-  LoaderRequest *request = _initial[index];
-  _initial.erase(_initial.begin() + index);
-  
-  request->_filename = filename;
-  request->_options = options;
-  
-  _pending.push_back(request);
-  _cvar.signal();
-  
-  // Now try to start the thread(s).
-  if (_state == S_initial) {
-    _state = S_started;
-    if (Thread::is_threading_supported()) {
-      for (int i = 0; i < _num_threads; ++i) {
-        PT(LoaderThread) thread = new LoaderThread(this);
-        if (thread->start(TP_low, true, true)) {
-          _threads.push_back(thread);
-        }
-      }
-    }
-  }
-   
-  if (_threads.empty()) {
-    // For some reason, we still have no threads--maybe we don't
-    // even have threading available.  In that case, load the model
-    // by hand.
-    poll_loader();
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: Loader::check_load
-//       Access: Published
-//  Description: Returns true if the indicated load-request has
-//               completed and not yet been fetched, false otherwise.
-////////////////////////////////////////////////////////////////////
-bool Loader::
-check_load(int id) {
-  MutexHolder holder(_lock);
-
-  int index = find_id(_finished, id);
-  return (index != -1);
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: Loader::fetch_load
-//       Access: Published
-//  Description: Returns the node associated with the indicated id
-//               number (returned by a previous call to request_load),
-//               or NULL if there was an error loading the model.  It
-//               is illegal to call this if check_load() does not
-//               return true (and the caller has not received the
-//               finished event for this id).
-////////////////////////////////////////////////////////////////////
-PT(PandaNode) Loader::
-fetch_load(int id) {
-  MutexHolder holder(_lock);
-
-  int index = find_id(_finished, id);
-  if (index == -1) {
-    nassert_raise("No such loader ID.");
-    return NULL;
-  }
-
-  LoaderRequest *request = _finished[index];
-  _finished.erase(_finished.begin() + index);
-
-  PT(PandaNode) model = request->_model;
-  delete request;
-
-  return model;
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: Loader::output
 //       Access: Published, Virtual
@@ -339,39 +169,10 @@ fetch_load(int id) {
 ////////////////////////////////////////////////////////////////////
 void Loader::
 output(ostream &out) const {
-  out << "Loader " << get_name();
+  out << get_type() << " " << get_name();
 
-  if (_state == S_started) {
-    MutexHolder holder(_lock);
-    out << " " << _pending.size() << " pending, "
-        << " " << _finished.size() << " finished.";
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: Loader::poll_loader
-//       Access: Private
-//  Description: Called internally to empty the requests from the
-//               _pending queue.  Assumes the lock is already held.
-////////////////////////////////////////////////////////////////////
-void Loader::
-poll_loader() {
-  while (!_pending.empty()) {
-    LoaderRequest *request = _pending[0];
-    _pending.pop_front();
-    
-    // Now release the lock while we load the model.
-    _lock.release();
-    
-    request->_model = load_file(request->_filename, request->_options);
-    
-    // Grab the lock again.
-    _lock.lock();
-    _finished.push_back(request);
-    
-    PT_Event event = new Event(request->_event_name);
-    event->add_parameter(EventParameter(request->_id));
-    throw_event(event);
+  if (!_threads.empty()) {
+    out << " (" << get_num_tasks() << " models pending)";
   }
 }
 
@@ -555,50 +356,3 @@ load_file_types() {
   }
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: Loader::find_id
-//       Access: Private, Static
-//  Description: Returns the index number within the given request
-//               queue of the request with the indicated ID, or -1 if
-//               no request in the queue has this ID.
-////////////////////////////////////////////////////////////////////
-int Loader::
-find_id(const Requests &requests, int id) {
-  for (int i = 0; i < (int)requests.size(); ++i) {
-    if (requests[i]->_id == id) {
-      return i;
-    }
-  }
-
-  return -1;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: Loader::LoaderThread::Constructor
-//       Access: Public
-//  Description: 
-////////////////////////////////////////////////////////////////////
-Loader::LoaderThread::
-LoaderThread(Loader *loader) :
-  Thread(loader->get_name(), loader->get_name()),
-  _loader(loader)
-{
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: Loader::LoaderThread::thread_main
-//       Access: Public, Virtual
-//  Description: 
-////////////////////////////////////////////////////////////////////
-void Loader::LoaderThread::
-thread_main() {
-  MutexHolder holder(_loader->_lock);
-  while (_loader->_state != Loader::S_shutdown) {
-    _loader->poll_loader();
-    _loader->_cvar.wait();
-  }
-
-  // We're going down.  Signal the next thread, if there is one.
-  _loader->_cvar.signal();
-}
-

+ 21 - 55
panda/src/pgraph/loader.h

@@ -27,11 +27,9 @@
 #include "pandaNode.h"
 #include "filename.h"
 #include "dSearchPath.h"
-#include "thread.h"
-#include "pmutex.h"
-#include "conditionVar.h"
 #include "pvector.h"
-#include "pdeque.h"
+#include "asyncTaskManager.h"
+#include "asyncTask.h"
 
 class LoaderFileType;
 
@@ -50,7 +48,7 @@ class LoaderFileType;
 //               loading interface may be used, but it loads
 //               synchronously.
 ////////////////////////////////////////////////////////////////////
-class EXPCL_PANDA Loader : public Namable {
+class EXPCL_PANDA Loader : public AsyncTaskManager {
 private:
   class ConsiderFile {
   public:
@@ -79,9 +77,7 @@ PUBLISHED:
     Files _files;
   };
 
-  Loader(const string &name = "loader",
-         int num_threads = -1);
-  virtual ~Loader();
+  Loader(const string &name = "loader", int num_threads = -1);
 
   int find_all_files(const Filename &filename, const DSearchPath &search_path,
                      Results &results) const;
@@ -89,64 +85,34 @@ PUBLISHED:
   INLINE PT(PandaNode) load_sync(const Filename &filename, 
                                  const LoaderOptions &options = LoaderOptions()) const;
 
-  int begin_request(const string &event_name);
-  void request_load(int id, const Filename &filename,
-                    const LoaderOptions &options = LoaderOptions());
-  bool check_load(int id);
-  PT(PandaNode) fetch_load(int id);
+  INLINE void load_async(AsyncTask *request);
 
   virtual void output(ostream &out) const;
 
 private:
-  void poll_loader();
   PT(PandaNode) load_file(const Filename &filename, const LoaderOptions &options) const;
 
   static void load_file_types();
   static bool _file_types_loaded;
 
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    AsyncTaskManager::init_type();
+    register_type(_type_handle, "Loader",
+                  AsyncTaskManager::get_class_type());
+    }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+  
 private:
-  class LoaderThread : public Thread {
-  public:
-    LoaderThread(Loader *loader);
-    virtual void thread_main();
-    Loader *_loader;
-  };
-
-  typedef pvector< PT(LoaderThread) > Threads;
-
-  class LoaderRequest {
-  public:
-    int _id;
-    string _event_name;
-    Filename _filename;
-    LoaderOptions _options;
-    PT(PandaNode) _model;
-  };
-
-  // We declare this a deque rather than a vector, on the assumption
-  // that we will usually be popping requests from the front (although
-  // the interface does not require this).
-  typedef pdeque<LoaderRequest *> Requests;
-
-  static int find_id(const Requests &requests, int id);
-
-  int _num_threads;
-
-  Mutex _lock;  // Protects all the following members.
-  ConditionVar _cvar;  // condition: _pending.empty()
-
-  enum State {
-    S_initial,
-    S_started,
-    S_shutdown
-  };
-
-  Requests _initial, _pending, _finished;
-  int _next_id;
-  Threads _threads;
-  State _state;
+  static TypeHandle _type_handle;
 
-  friend class LoaderThread;
+  friend class ModelLoadRequest;
 };
 
 #include "loader.I"

+ 80 - 0
panda/src/pgraph/modelLoadRequest.I

@@ -0,0 +1,80 @@
+// Filename: modelLoadRequest.I
+// Created by:  drose (29Aug06)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: ModelLoadRequest::Constructor
+//       Access: Published
+//  Description: Create a new ModelLoadRequest, and add it to the loader
+//               via load_async(), to begin an asynchronous load.
+////////////////////////////////////////////////////////////////////
+INLINE ModelLoadRequest::
+ModelLoadRequest(const Filename &filename, const LoaderOptions &options) :
+  AsyncTask(filename),
+  _filename(filename),
+  _options(options),
+  _is_ready(false)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ModelLoadRequest::get_filename
+//       Access: Published
+//  Description: Returns the filename associated with this
+//               asynchronous ModelLoadRequest.
+////////////////////////////////////////////////////////////////////
+INLINE const Filename &ModelLoadRequest::
+get_filename() const {
+  return _filename;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ModelLoadRequest::get_options
+//       Access: Published
+//  Description: Returns the LoaderOptions associated with this
+//               asynchronous ModelLoadRequest.
+////////////////////////////////////////////////////////////////////
+INLINE const LoaderOptions &ModelLoadRequest::
+get_options() const {
+  return _options;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ModelLoadRequest::is_ready
+//       Access: Published
+//  Description: Returns true if this request has completed, false if
+//               it is still pending.  When this returns true, you may
+//               retrieve the model loaded by calling get_model().
+////////////////////////////////////////////////////////////////////
+INLINE bool ModelLoadRequest::
+is_ready() const {
+  return _is_ready;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ModelLoadRequest::get_model
+//       Access: Published
+//  Description: Returns the model that was loaded asynchronously, if
+//               any, or NULL if there was an error.  It is an error
+//               to call this unless is_ready() returns true.
+////////////////////////////////////////////////////////////////////
+INLINE PandaNode *ModelLoadRequest::
+get_model() const {
+  nassertr(_is_ready, NULL);
+  return _model;
+}

+ 39 - 0
panda/src/pgraph/modelLoadRequest.cxx

@@ -0,0 +1,39 @@
+// Filename: modelLoadRequest.cxx
+// Created by:  drose (29Aug06)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "modelLoadRequest.h"
+#include "loader.h"
+
+TypeHandle ModelLoadRequest::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: ModelLoadRequest::do_task
+//       Access: Protected, Virtual
+//  Description: Performs the task: that is, loads the one model.
+////////////////////////////////////////////////////////////////////
+bool ModelLoadRequest::
+do_task() {
+  Loader *loader;
+  DCAST_INTO_R(loader, _manager, false);
+
+  _model = loader->load_sync(_filename, _options);
+  _is_ready = true;
+
+  // Don't continue the task; we're done.
+  return false;
+}

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

@@ -0,0 +1,77 @@
+// Filename: modelLoadRequest.h
+// Created by:  drose (29Aug06)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef MODELLOADREQUEST
+#define MODELLOADREQUEST
+
+#include "pandabase.h"
+
+#include "asyncTask.h"
+#include "filename.h"
+#include "loaderOptions.h"
+#include "pandaNode.h"
+#include "pointerTo.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : ModelLoadRequest
+// Description : A class object that manages a single asynchronous
+//               model load request.  Create a new ModelLoadRequest,
+//               and add it to the loader via load_async(), to begin
+//               an asynchronous load.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA ModelLoadRequest : public AsyncTask {
+PUBLISHED:
+  INLINE ModelLoadRequest(const Filename &filename, 
+                          const LoaderOptions &options);
+  
+  INLINE const Filename &get_filename() const;
+  INLINE const LoaderOptions &get_options() const;
+  
+  INLINE bool is_ready() const;
+  INLINE PandaNode *get_model() const;
+  
+protected:
+  virtual bool do_task();
+  
+private:
+  Filename _filename;
+  LoaderOptions _options;
+  bool _is_ready;
+  PT(PandaNode) _model;
+  
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    AsyncTask::init_type();
+    register_type(_type_handle, "ModelLoadRequest",
+                  AsyncTask::get_class_type());
+    }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+  
+private:
+  static TypeHandle _type_handle;
+};
+
+#include "modelLoadRequest.I"
+
+#endif

+ 1 - 1
panda/src/pgraph/nodePath.I

@@ -2239,7 +2239,7 @@ get_python_tag(const string &key) const {
   // An empty NodePath quietly returns no tags.  This makes
   // get_net_python_tag() easier to implement.
   if (is_empty()) {
-    return Py_None;
+    Py_RETURN_NONE;
   }
   return node()->get_python_tag(key);
 }

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

@@ -1337,7 +1337,7 @@ get_python_tag(const string &key) const {
     Py_XINCREF(result);
     return result;
   }
-  return Py_None;
+  Py_RETURN_NONE;
 }
 #endif  // HAVE_PYTHON
 

+ 1 - 0
panda/src/pgraph/pgraph_composite3.cxx

@@ -12,6 +12,7 @@
 #include "lodNode.cxx"
 #include "materialAttrib.cxx"
 #include "materialCollection.cxx"
+#include "modelLoadRequest.cxx"
 #include "modelNode.cxx"
 #include "modelPool.cxx"
 #include "modelRoot.cxx"

+ 0 - 5
panda/src/putil/putil_composite1.cxx

@@ -25,8 +25,3 @@
 #include "ioPtaDatagramFloat.cxx"
 #include "ioPtaDatagramInt.cxx"
 #include "ioPtaDatagramShort.cxx"
-#include "keyboardButton.cxx"
-#include "lineStream.cxx"
-#include "lineStreamBuf.cxx"
-#include "linkedListNode.cxx"
-#include "load_prc_file.cxx"

+ 5 - 0
panda/src/putil/putil_composite2.cxx

@@ -1,3 +1,8 @@
+#include "keyboardButton.cxx"
+#include "lineStream.cxx"
+#include "lineStreamBuf.cxx"
+#include "linkedListNode.cxx"
+#include "load_prc_file.cxx"
 #include "modifierButtons.cxx"
 #include "mouseButton.cxx"
 #include "mouseData.cxx"