浏览代码

threaded model loads

David Rose 19 年之前
父节点
当前提交
1f1761da76

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

@@ -10,7 +10,6 @@
 
   #define SOURCES \
     config_downloader.h \
-    asyncUtility.I asyncUtility.h \
     bioPtr.I bioPtr.h \
     bioStreamPtr.I bioStreamPtr.h \
     bioStream.I bioStream.h bioStreamBuf.h \
@@ -39,7 +38,6 @@
     
   #define INCLUDED_SOURCES                 \
     config_downloader.cxx \
-    asyncUtility.cxx \
     bioPtr.cxx \
     bioStreamPtr.cxx \
     bioStream.cxx bioStreamBuf.cxx \
@@ -66,7 +64,6 @@
     urlSpec.cxx
 
   #define INSTALL_HEADERS \
-    asyncUtility.h asyncUtility.I \
     bioPtr.I bioPtr.h \
     bioStreamPtr.I bioStreamPtr.h \
     bioStream.I bioStream.h bioStreamBuf.h \

+ 0 - 37
panda/src/downloader/asyncUtility.I

@@ -1,37 +0,0 @@
-// Filename: asyncUtility.I
-// Created by:  mike (09Jan97)
-//
-////////////////////////////////////////////////////////////////////
-//
-// 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: AsyncUtility::set_frequency
-//       Access: Public
-//  Description: Fraction of a second.
-////////////////////////////////////////////////////////////////////
-INLINE void AsyncUtility::
-set_frequency(float frequency) {
-  _frequency = frequency;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: AsyncUtility::get_frequency
-//       Access: Public
-//  Description:
-////////////////////////////////////////////////////////////////////
-INLINE float AsyncUtility::
-get_frequency() const {
-  return _frequency;
-}

+ 0 - 161
panda/src/downloader/asyncUtility.cxx

@@ -1,161 +0,0 @@
-// Filename: asyncUtility.cxx
-// Created by:  mike (09Jan97)
-//
-////////////////////////////////////////////////////////////////////
-//
-// 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 "config_downloader.h"
-#include "asyncUtility.h"
-
-#if defined(WIN32)
-  #define WINDOWS_LEAN_AND_MEAN
-  #include <wtypes.h>
-  #undef WINDOWS_LEAN_AND_MEAN  
-#else
-  #include <sys/time.h>
-#endif
-
-////////////////////////////////////////////////////////////////////
-// Defines
-////////////////////////////////////////////////////////////////////
-
-////////////////////////////////////////////////////////////////////
-//     Function: AsyncUtility::Constructor
-//       Access: Public
-//  Description:
-////////////////////////////////////////////////////////////////////
-AsyncUtility::
-AsyncUtility(float frequency) : _frequency(frequency) {
-  _next_token = 1;
-  _shutdown = false;
-  _threaded = false;
-  _threads_enabled = true;
-
-#ifdef OLD_HAVE_IPC
-  _request_cond = new condition_variable(_lock);
-#endif
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: AsyncUtility::Destructor
-//       Access: Public
-//  Description:
-////////////////////////////////////////////////////////////////////
-AsyncUtility::
-~AsyncUtility() {
-#ifdef OLD_HAVE_IPC
-  delete _request_cond;
-#endif
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: AsyncUtility::create_thread
-//       Access: Public
-//  Description:
-////////////////////////////////////////////////////////////////////
-void AsyncUtility::
-create_thread() {
-#ifdef OLD_HAVE_IPC
-  if (_threaded == false && _threads_enabled == true) {
-    downloader_cat.debug()
-      << "AsyncUtility::create_thread()" << endl;
-    _thread = thread::create(&st_callback, this, thread::PRIORITY_NORMAL);
-    _threaded = true;
-  }
-#endif
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: AsyncUtility::destroy_thread
-//       Access: Protected
-//  Description:
-////////////////////////////////////////////////////////////////////
-void AsyncUtility::
-destroy_thread() {
-#ifdef OLD_HAVE_IPC
-  if (_threaded == false)
-    return;
-
-  // Tell the thread to shut itself down.
-  // We need to grab the lock in order to signal the condition variable
-  _lock.lock();
-    _shutdown = true;
-    _request_cond->signal();
-  _lock.unlock();
-
-  // Join the loader thread - calling process blocks until the loader
-  // thread returns.
-  void *ret;
-  _thread->join(&ret);
-#endif
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: AsyncUtility::st_callback
-//       Access: Protected, Static
-//  Description: This is a static wrapper around the callback()
-//               method, below.  It's static just so we can pass it to
-//               the thread-creation function.  In addition, the
-//               function has a void* return type even though we
-//               don't actually return anything.  This is necessary
-//               because ipc assumes a function that does not return
-//               anything indicates that the associated thread should
-//               be created as unjoinable (detached).
-////////////////////////////////////////////////////////////////////
-void* AsyncUtility::
-st_callback(void *arg) {
-#ifdef OLD_HAVE_IPC
-  nassertr(arg != NULL, NULL);
-  ((AsyncUtility *)arg)->callback();
-#endif
-  return NULL;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: AsyncUtility::callback
-//       Access: Protected
-//  Description: This is the main body of the sub-thread.  It waits
-//               forever for a request to show up, and then serves it.
-////////////////////////////////////////////////////////////////////
-void AsyncUtility::
-callback() {
-#ifdef OLD_HAVE_IPC
-  while (process_request()) {
-    // Sleep until a signal arrives
-    _lock.lock();
-      _request_cond->wait();
-    _lock.unlock();
-  }
-#endif
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: AsyncUtility::nap
-//       Access: Protected
-//  Description:
-////////////////////////////////////////////////////////////////////
-void AsyncUtility::
-nap() const {
-#ifdef OLD_HAVE_IPC
-#ifdef WIN32
-  _sleep((DWORD)(1000 * _frequency));
-#else
-  struct timeval tv;
-  tv.tv_sec = 0;
-  tv.tv_usec = (long)(1000000 * _frequency);
-  select(0, NULL, NULL, NULL, &tv);
-#endif
-#endif  // OLD_HAVE_IPC
-}

+ 0 - 73
panda/src/downloader/asyncUtility.h

@@ -1,73 +0,0 @@
-// Filename: asyncUtility.h
-// Created by:  mike (09Jan97)
-//
-////////////////////////////////////////////////////////////////////
-//
-// 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 ASYNCUTILITY_H
-#define ASYNCUTILITY_H
-//
-////////////////////////////////////////////////////////////////////
-// Includes
-////////////////////////////////////////////////////////////////////
-#include "pandabase.h"
-#include "pnotify.h"
-#include "typedef.h"
-
-#ifdef OLD_HAVE_IPC
-#include <ipc_mutex.h>
-#include <ipc_condition.h>
-#include <ipc_thread.h>
-#endif
-
-
-////////////////////////////////////////////////////////////////////
-//       Class : AsyncUtility
-// Description :
-////////////////////////////////////////////////////////////////////
-class EXPCL_PANDAEXPRESS AsyncUtility {
-PUBLISHED:
-  INLINE void set_frequency(float frequency);
-  INLINE float get_frequency() const;
-
-  void create_thread();
-
-public:
-  AsyncUtility(float frequency = 0.2);
-  virtual ~AsyncUtility();
-
-protected:
-  void destroy_thread();
-  static void* st_callback(void *arg);
-  void callback();
-  virtual bool process_request() = 0;
-  void nap() const;
-
-protected:
-  int _next_token;
-  bool _shutdown;
-  bool _threaded;
-  float _frequency;
-  bool _threads_enabled;
-
-#ifdef OLD_HAVE_IPC
-  mutex _lock;
-  condition_variable *_request_cond;
-  thread *_thread;
-#endif
-};
-
-#include "asyncUtility.I"
-
-#endif

+ 0 - 1
panda/src/downloader/downloader_composite1.cxx

@@ -1,4 +1,3 @@
-#include "asyncUtility.cxx"
 #include "bioPtr.cxx"
 #include "bioStream.cxx"
 #include "bioStreamBuf.cxx"

+ 2 - 4
panda/src/express/Sources.pp

@@ -52,8 +52,7 @@
     textEncoder.h textEncoder.I \
     threadSafePointerTo.I threadSafePointerTo.h \
     threadSafePointerToBase.I threadSafePointerToBase.h \
-    tokenBoard.I \
-    tokenBoard.h trueClock.I trueClock.h \
+    trueClock.I trueClock.h \
     typedReferenceCount.I typedReferenceCount.h typedef.h \
     unicodeLatinMap.h \
     vector_uchar.h \
@@ -164,8 +163,7 @@
     textEncoder.h textEncoder.I \
     threadSafePointerTo.I threadSafePointerTo.h \
     threadSafePointerToBase.I threadSafePointerToBase.h \
-    tokenBoard.I \
-    tokenBoard.h trueClock.I trueClock.h \
+    trueClock.I trueClock.h \
     typedReferenceCount.I typedReferenceCount.h typedef.h \
     unicodeLatinMap.h \
     vector_uchar.h \

+ 0 - 78
panda/src/express/tokenBoard.I

@@ -1,78 +0,0 @@
-// Filename: tokenBoard.I
-// Created by:  mike (09Jan97)
-//
-////////////////////////////////////////////////////////////////////
-//
-// 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: TokenBoard::is_done_token()
-//       Access: Public
-//  Description: Returns true if the indicated token id is on the
-//               done queue, false otherwise.
-////////////////////////////////////////////////////////////////////
-template<class TokenType>
-bool TokenBoard<TokenType>::
-is_done_token(int id) {
-  // First, empty the done list, copying them to really_done.  We have
-  // to do this since we can only examine tokens on the head of the
-  // done list, and the token we're looking for might not be at the
-  // head.
-  while (!_done.empty()) {
-    _really_done.push_back(_done.front());
-    _done.pop_front();
-  }
-
-  // Now we can search really_done for our desired id.
-  TYPENAME plist< PT(TokenType) >::iterator found;
-  found = find_if(_really_done.begin(), _really_done.end(),
-                  TokenMatch<TokenType>(id));
-
-  return (found != _really_done.end());
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: TokenBoard::get_done_token
-//       Access: Public
-//  Description: Locates the token by the given id in the list of done
-//               tokens, removes it from the list, and returns its
-//               pointer (which should be deleted by the calling
-//               function).  Returns NULL if the token was not on the
-//               done list.
-////////////////////////////////////////////////////////////////////
-template<class TokenType>
-PT(TokenType) TokenBoard<TokenType>::
-get_done_token(int id) {
-  // First, empty the done list, copying them to really_done.  We have
-  // to do this since we can only examine tokens on the head of the
-  // done list, and the token we're looking for might not be at the
-  // head.
-  while (!_done.empty()) {
-    _really_done.push_back(_done.front());
-    _done.pop_front();
-  }
-
-  // Now we can search really_done for our desired id.
-  TYPENAME plist< PT(TokenType) >::iterator found;
-  found = find_if(_really_done.begin(), _really_done.end(),
-                  TokenMatch<TokenType>(id));
-
-  if (found == _really_done.end()) {
-    return NULL;
-  } else {
-    PT(TokenType) tok = *found;
-    _really_done.erase(found);
-    return tok;
-  }
-}

+ 0 - 76
panda/src/express/tokenBoard.h

@@ -1,76 +0,0 @@
-// Filename: tokenBoard.h
-// Created by:  mike (09Jan97)
-//
-////////////////////////////////////////////////////////////////////
-//
-// 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 TOKENBOARD_H
-#define TOKENBOARD_H
-
-#include "pandabase.h"
-#include "plist.h"
-#include "circBuffer.h"
-#include "pointerTo.h"
-
-////////////////////////////////////////////////////////////////////
-//      Struct : TokenMatch
-// Description : This is an STL predicate function object that is used
-//               to find a particular id in a list of token pointers.
-//               It returns true when the token's id matches the
-//               numeric id supplied to the constructor.
-////////////////////////////////////////////////////////////////////
-template<class TokenType>
-class TokenMatch {
-public:
-  TokenMatch(int id) {
-    _want_id = id;
-  }
-  bool operator()(PT(TokenType) tok) const {
-    return (int)tok->_id == _want_id;
-  }
-  int _want_id;
-};
-
-const int MAX_TOKENBOARD_REQUESTS = 100;
-
-////////////////////////////////////////////////////////////////////
-//       Class : TokenBoard
-// Description :
-////////////////////////////////////////////////////////////////////
-template<class TokenType>
-class TokenBoard {
-public:
-  bool is_done_token(int id);
-  PT(TokenType) get_done_token(int id);
-
-  // waiting holds the list of requests sent to the DBASE process, not
-  // yet handled.
-  CircBuffer<PT(TokenType), MAX_TOKENBOARD_REQUESTS> _waiting;
-
-  // done holds the list of requests handled by the DBASE process, but
-  // not yet discovered by APP.  Probably this queue will only have
-  // one item at a time on it.
-  CircBuffer<PT(TokenType), MAX_TOKENBOARD_REQUESTS> _done;
-
-  // really_done holds the requests extracted from done.  These are
-  // extracted into the local list so we can safely search for and
-  // remove a particular token from the middle of the list (we can
-  // only remove from the head of a circular buffer).
-  plist< PT(TokenType) > _really_done;
-};
-
-#include "tokenBoard.I"
-
-#endif

+ 7 - 1
panda/src/gobj/materialPool.cxx

@@ -18,7 +18,7 @@
 
 #include "materialPool.h"
 #include "config_gobj.h"
-
+#include "mutexHolder.h"
 
 MaterialPool *MaterialPool::_global_ptr = (MaterialPool *)NULL;
 
@@ -41,6 +41,8 @@ write(ostream &out) {
 ////////////////////////////////////////////////////////////////////
 Material *MaterialPool::
 ns_get_material(Material *temp) {
+  MutexHolder holder(_lock);
+
   CPT(Material) cpttemp = temp;
   Materials::iterator mi = _materials.find(cpttemp);
   if (mi == _materials.end()) {
@@ -62,6 +64,8 @@ ns_get_material(Material *temp) {
 ////////////////////////////////////////////////////////////////////
 int MaterialPool::
 ns_garbage_collect() {
+  MutexHolder holder(_lock);
+
   int num_released = 0;
   Materials new_set;
 
@@ -91,6 +95,8 @@ ns_garbage_collect() {
 ////////////////////////////////////////////////////////////////////
 void MaterialPool::
 ns_list_contents(ostream &out) const {
+  MutexHolder holder(_lock);
+
   out << _materials.size() << " materials:\n";
   Materials::const_iterator mi;
   for (mi = _materials.begin(); mi != _materials.end(); ++mi) {

+ 3 - 3
panda/src/gobj/materialPool.h

@@ -20,11 +20,9 @@
 #define MATERIALPOOL_H
 
 #include "pandabase.h"
-
 #include "material.h"
-
 #include "pointerTo.h"
-
+#include "pmutex.h"
 #include "pset.h"
 
 ////////////////////////////////////////////////////////////////////
@@ -63,6 +61,8 @@ private:
 
   static MaterialPool *_global_ptr;
 
+  Mutex _lock;
+
   // We store a map of CPT(Material) to PT(Material).  These are two
   // equivalent structures, but different pointers.  The first pointer
   // never leaves this class.  If the second pointer changes value,

+ 129 - 28
panda/src/gobj/texturePool.cxx

@@ -28,6 +28,7 @@
 #include "texturePoolFilter.h"
 #include "configVariableList.h"
 #include "load_dso.h"
+#include "mutexHolder.h"
 
 TexturePool *TexturePool::_global_ptr;
 
@@ -54,6 +55,8 @@ write(ostream &out) {
 ////////////////////////////////////////////////////////////////////
 void TexturePool::
 register_texture_type(MakeTextureFunc *func, const string &extensions) {
+  MutexHolder holder(_lock);
+
   vector_string words;
   extract_words(downcase(extensions), words);
 
@@ -71,6 +74,8 @@ register_texture_type(MakeTextureFunc *func, const string &extensions) {
 ////////////////////////////////////////////////////////////////////
 void TexturePool::
 register_filter(TexturePoolFilter *filter) {
+  MutexHolder holder(_lock);
+
   gobj_cat.info()
     << "Registering Texture filter " << *filter << "\n";
   _filter_registry.push_back(filter);
@@ -86,6 +91,8 @@ register_filter(TexturePoolFilter *filter) {
 ////////////////////////////////////////////////////////////////////
 TexturePool::MakeTextureFunc *TexturePool::
 get_texture_type(const string &extension) const {
+  MutexHolder holder(_lock);
+
   string c = downcase(extension);
   TypeRegistry::const_iterator ti;
   ti = _type_registry.find(extension);
@@ -136,6 +143,8 @@ make_texture(const string &extension) const {
 ////////////////////////////////////////////////////////////////////
 void TexturePool::
 write_texture_types(ostream &out, int indent_level) const {
+  MutexHolder holder(_lock);
+
   PNMFileTypeRegistry *pnm_reg = PNMFileTypeRegistry::get_global_ptr();
   pnm_reg->write(out, indent_level);
 
@@ -201,6 +210,8 @@ TexturePool() {
 ////////////////////////////////////////////////////////////////////
 bool TexturePool::
 ns_has_texture(const Filename &orig_filename) {
+  MutexHolder holder(_lock);
+
   Filename filename(orig_filename);
 
   if (!_fake_texture_image.empty()) {
@@ -239,13 +250,16 @@ ns_load_texture(const Filename &orig_filename, int primary_file_num_channels,
   vfs->resolve_filename(filename, get_texture_path()) ||
     vfs->resolve_filename(filename, get_model_path());
 
-  Textures::const_iterator ti;
-  ti = _textures.find(filename);
-  if (ti != _textures.end()) {
-    // This texture was previously loaded.
-    Texture *tex = (*ti).second;
-    nassertr(!tex->get_fullpath().empty(), tex);
-    return tex;
+  {
+    MutexHolder holder(_lock);
+    Textures::const_iterator ti;
+    ti = _textures.find(filename);
+    if (ti != _textures.end()) {
+      // This texture was previously loaded.
+      Texture *tex = (*ti).second;
+      nassertr(!tex->get_fullpath().empty(), tex);
+      return tex;
+    }
   }
 
   // The texture was not found in the pool.
@@ -293,7 +307,23 @@ ns_load_texture(const Filename &orig_filename, int primary_file_num_channels,
   tex->set_filename(orig_filename);
   tex->set_fullpath(filename);
   tex->_texture_pool_key = filename;
-  _textures[filename] = tex;
+
+  {
+    MutexHolder holder(_lock);
+
+    // Now look again--someone may have just loaded this texture in
+    // another thread.
+    Textures::const_iterator ti;
+    ti = _textures.find(filename);
+    if (ti != _textures.end()) {
+      // This texture was previously loaded.
+      Texture *tex = (*ti).second;
+      nassertr(!tex->get_fullpath().empty(), tex);
+      return tex;
+    }
+
+    _textures[filename] = tex;
+  }
 
   if (store_record) {
     // Store the on-disk cache record for next time.
@@ -335,13 +365,17 @@ ns_load_texture(const Filename &orig_filename,
   vfs->resolve_filename(alpha_filename, get_texture_path()) ||
     vfs->resolve_filename(alpha_filename, get_model_path());
 
-  Textures::const_iterator ti;
-  ti = _textures.find(filename);
-  if (ti != _textures.end()) {
-    // This texture was previously loaded.
-    Texture *tex = (*ti).second;
-    nassertr(!tex->get_fullpath().empty(), tex);
-    return tex;
+  {
+    MutexHolder holder(_lock);
+
+    Textures::const_iterator ti;
+    ti = _textures.find(filename);
+    if (ti != _textures.end()) {
+      // This texture was previously loaded.
+      Texture *tex = (*ti).second;
+      nassertr(!tex->get_fullpath().empty(), tex);
+      return tex;
+    }
   }
 
   PT(Texture) tex;
@@ -393,7 +427,22 @@ ns_load_texture(const Filename &orig_filename,
   tex->set_alpha_filename(orig_alpha_filename);
   tex->set_alpha_fullpath(alpha_filename);
   tex->_texture_pool_key = filename;
-  _textures[filename] = tex;
+
+  {
+    MutexHolder holder(_lock);
+
+    // Now look again.
+    Textures::const_iterator ti;
+    ti = _textures.find(filename);
+    if (ti != _textures.end()) {
+      // This texture was previously loaded.
+      Texture *tex = (*ti).second;
+      nassertr(!tex->get_fullpath().empty(), tex);
+      return tex;
+    }
+    
+    _textures[filename] = tex;
+  }
 
   if (store_record) {
     // Store the on-disk cache record for next time.
@@ -424,11 +473,15 @@ ns_load_3d_texture(const Filename &filename_pattern,
   vfs->resolve_filename(filename, get_texture_path()) ||
     vfs->resolve_filename(filename, get_model_path());
 
-  Textures::const_iterator ti;
-  ti = _textures.find(filename);
-  if (ti != _textures.end()) {
-    // This texture was previously loaded.
-    return (*ti).second;
+  {
+    MutexHolder holder(_lock);
+
+    Textures::const_iterator ti;
+    ti = _textures.find(filename);
+    if (ti != _textures.end()) {
+      // This texture was previously loaded.
+      return (*ti).second;
+    }
   }
 
   PT(Texture) tex;
@@ -470,7 +523,20 @@ ns_load_3d_texture(const Filename &filename_pattern,
   tex->set_filename(filename_pattern);
   tex->set_fullpath(filename);
   tex->_texture_pool_key = filename;
-  _textures[filename] = tex;
+
+  {
+    MutexHolder holder(_lock);
+
+    // Now look again.
+    Textures::const_iterator ti;
+    ti = _textures.find(filename);
+    if (ti != _textures.end()) {
+      // This texture was previously loaded.
+      return (*ti).second;
+    }
+
+    _textures[filename] = tex;
+  }
 
   if (store_record) {
     // Store the on-disk cache record for next time.
@@ -496,11 +562,15 @@ ns_load_cube_map(const Filename &filename_pattern, bool read_mipmaps) {
   vfs->resolve_filename(filename, get_texture_path()) ||
     vfs->resolve_filename(filename, get_model_path());
 
-  Textures::const_iterator ti;
-  ti = _textures.find(filename);
-  if (ti != _textures.end()) {
-    // This texture was previously loaded.
-    return (*ti).second;
+  {
+    MutexHolder holder(_lock);
+
+    Textures::const_iterator ti;
+    ti = _textures.find(filename);
+    if (ti != _textures.end()) {
+      // This texture was previously loaded.
+      return (*ti).second;
+    }
   }
 
   PT(Texture) tex;
@@ -542,7 +612,20 @@ ns_load_cube_map(const Filename &filename_pattern, bool read_mipmaps) {
   tex->set_filename(filename_pattern);
   tex->set_fullpath(filename);
   tex->_texture_pool_key = filename;
-  _textures[filename] = tex;
+
+  {
+    MutexHolder holder(_lock);
+
+    // Now look again.
+    Textures::const_iterator ti;
+    ti = _textures.find(filename);
+    if (ti != _textures.end()) {
+      // This texture was previously loaded.
+      return (*ti).second;
+    }
+
+    _textures[filename] = tex;
+  }
 
   if (store_record) {
     // Store the on-disk cache record for next time.
@@ -561,6 +644,8 @@ ns_load_cube_map(const Filename &filename_pattern, bool read_mipmaps) {
 ////////////////////////////////////////////////////////////////////
 Texture *TexturePool::
 ns_get_normalization_cube_map(int size) {
+  MutexHolder holder(_lock);
+
   if (_normalization_cube_map == (Texture *)NULL) {
     _normalization_cube_map = new Texture("normalization_cube_map");
   }
@@ -580,6 +665,8 @@ ns_get_normalization_cube_map(int size) {
 void TexturePool::
 ns_add_texture(Texture *tex) {
   PT(Texture) keep = tex;
+  MutexHolder holder(_lock);
+
   if (!tex->_texture_pool_key.empty()) {
     ns_release_texture(tex);
   }
@@ -601,6 +688,8 @@ ns_add_texture(Texture *tex) {
 ////////////////////////////////////////////////////////////////////
 void TexturePool::
 ns_release_texture(Texture *tex) {
+  MutexHolder holder(_lock);
+
   if (!tex->_texture_pool_key.empty()) {
     Textures::iterator ti;
     ti = _textures.find(tex->_texture_pool_key);
@@ -618,6 +707,8 @@ ns_release_texture(Texture *tex) {
 ////////////////////////////////////////////////////////////////////
 void TexturePool::
 ns_release_all_textures() {
+  MutexHolder holder(_lock);
+
   Textures::iterator ti;
   for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
     Texture *tex = (*ti).second;
@@ -635,6 +726,8 @@ ns_release_all_textures() {
 ////////////////////////////////////////////////////////////////////
 int TexturePool::
 ns_garbage_collect() {
+  MutexHolder holder(_lock);
+
   int num_released = 0;
   Textures new_set;
 
@@ -675,6 +768,8 @@ ns_garbage_collect() {
 ////////////////////////////////////////////////////////////////////
 void TexturePool::
 ns_list_contents(ostream &out) const {
+  MutexHolder holder(_lock);
+
   out << _textures.size() << " textures:\n";
   Textures::const_iterator ti;
   for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
@@ -741,6 +836,8 @@ pre_load(const Filename &orig_filename, const Filename &orig_alpha_filename,
          bool read_mipmaps) {
   PT(Texture) tex;
 
+  MutexHolder holder(_lock);
+
   FilterRegistry::iterator fi;
   for (fi = _filter_registry.begin();
        fi != _filter_registry.end();
@@ -765,6 +862,8 @@ PT(Texture) TexturePool::
 post_load(Texture *tex) {
   PT(Texture) result = tex;
 
+  MutexHolder holder(_lock);
+
   FilterRegistry::iterator fi;
   for (fi = _filter_registry.begin();
        fi != _filter_registry.end();
@@ -784,6 +883,8 @@ post_load(Texture *tex) {
 ////////////////////////////////////////////////////////////////////
 void TexturePool::
 load_filters() {
+  MutexHolder holder(_lock);
+
   ConfigVariableList texture_filter
     ("texture-filter",
      PRC_DESC("Names one or more external libraries that should be loaded for the "

+ 3 - 1
panda/src/gobj/texturePool.h

@@ -23,7 +23,7 @@
 #include "texture.h"
 #include "filename.h"
 #include "config_gobj.h"
-
+#include "pmutex.h"
 #include "pmap.h"
 
 class TexturePoolFilter;
@@ -121,6 +121,8 @@ private:
   void load_filters();
 
   static TexturePool *_global_ptr;
+
+  Mutex _lock;
   typedef phash_map<string,  PT(Texture), string_hash> Textures;
   Textures _textures;
   string _fake_texture_image;

+ 285 - 193
panda/src/pgraph/loader.cxx

@@ -20,7 +20,7 @@
 #include "loaderFileType.h"
 #include "loaderFileTypeRegistry.h"
 #include "config_pgraph.h"
-
+#include "modelPool.h"
 #include "config_express.h"
 #include "config_util.h"
 #include "virtualFileSystem.h"
@@ -28,62 +28,68 @@
 #include "pt_Event.h"
 #include "throw_event.h"
 #include "eventParameter.h"
-#include "circBuffer.h"
 #include "filename.h"
 #include "load_dso.h"
 #include "string_utils.h"
-#include "plist.h"
-#include "pvector.h"
 #include "bamCache.h"
 #include "bamCacheRecord.h"
+#include "mutexHolder.h"
 
 #include <algorithm>
 
 
 bool Loader::_file_types_loaded = false;
 
-////////////////////////////////////////////////////////////////////
-//      Struct : LoaderToken
-// Description : Holds a request for the loader (load or delete), as
-//               well as the return information after the request has
-//               completed.
-////////////////////////////////////////////////////////////////////
-class LoaderToken : public ReferenceCount {
-public:
-  INLINE LoaderToken(uint id, const string &event_name, const Filename &path, 
-                     bool search, PandaNode *node=NULL) : 
-    _id(id), 
-    _event_name(event_name), 
-    _path(path),
-    _search(search),
-    _node(node) 
-  { }
-  uint _id;
-  string _event_name;
-  Filename _path;
-  bool _search;
-  PT(PandaNode) _node;
-};
-
 ////////////////////////////////////////////////////////////////////
 //     Function: Loader::Constructor
 //       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
 Loader::
-Loader() : AsyncUtility() {
-  _token_board = new LoaderTokenBoard;
+Loader(const string &name, int num_threads) :
+  Namable(name),
+  _cvar(_lock) 
+{
+  _num_threads = num_threads;
+  if (_num_threads < 0) {
+    // -1 means the default number of threads.
+
+    ConfigVariableInt loader_num_threads
+      ("loader-num-threads", 1,
+       PRC_DESC("The number of threads that will be started by the Loader class "
+                "to load models asynchronously.  These threads will only be "
+                "started if the asynchronous interface is used, and if threading "
+                "support is compiled into Panda.  The default is one thread, "
+                "which allows models to be loaded one at a time in a single "
+                "asychronous thread.  You can set this higher, particularly if "
+                "you have many CPU's available, to allow loading multiple models "
+                "simultaneously."));
+
+    _num_threads = loader_num_threads;
+  }
+
+  _next_id = 1;
+  _state = S_initial;
 }
 
 ////////////////////////////////////////////////////////////////////
 //     Function: Loader::Destructor
-//       Access: Published
+//       Access: Published, Virtual
 //  Description:
 ////////////////////////////////////////////////////////////////////
 Loader::
 ~Loader() {
-  destroy_thread();
-  delete _token_board;
+  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();
+    }
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -189,83 +195,98 @@ find_all_files(const Filename &filename, const DSearchPath &search_path,
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: Loader::request_load
+//     Function: Loader::begin_request
 //       Access: Published
-//  Description: Requests an asynchronous load of a file.  The request
-//               will be queued and served by the asynchronous thread.
-//               If event_name is nonempty, it is the name of the
-//               event that will be thrown (with the uint id as its
-//               single parameter) when the loading is completed later.
+//  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 return value is an integer which can be used to
-//               identify this particular request later to
-//               fetch_load(), or 0 if there has been an error.
+//               The parameter is the event name that is to be
+//               generated when the model is successfully loaded.
 //
-//               If search is true, the file is searched for along the
-//               model path; otherwise, only the exact filename is
-//               loaded.
+//               The caller should record the new ID, and then call
+//               request_load() with the same ID.
 ////////////////////////////////////////////////////////////////////
-uint Loader::
-request_load(const string &event_name, const Filename &filename, bool search) {
+int Loader::
+begin_request(const string &event_name) {
   if (!_file_types_loaded) {
     load_file_types();
   }
 
-  PT(LoaderToken) tok;
-  if (asynchronous_loads) {
-
-    // Make sure we actually are threaded
-    if (!_threaded) {
-      loader_cat.info()
-        << "Loader::request_load() - create_thread() was "
-        << "never called!  Calling it now..." << endl;
-      create_thread();
-    }
+  LoaderRequest *request = new LoaderRequest;
+  request->_event_name = event_name;
 
-    // We need to grab the lock in order to signal the condition variable
-#ifdef OLD_HAVE_IPC
-    _lock.lock();
-#endif
+  {
+    MutexHolder holder(_lock);
+    request->_id = _next_id;
+    ++_next_id;
+    _initial.push_back(request);
+  }
 
-      if (_token_board->_waiting.full()) {
-        loader_cat.error()
-          << "Loader::request_load() - Too many pending requests\n";
-        return 0;
-      }
+  return request->_id;
+}
 
-      if (loader_cat.is_debug()) {
-        loader_cat.debug()
-          << "Load requested for file: " << filename << "\n";
+////////////////////////////////////////////////////////////////////
+//     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);
+        }
       }
-
-      tok = new LoaderToken(_next_token++, event_name, filename, search);
-      _token_board->_waiting.push_back(tok);
-
-#ifdef OLD_HAVE_IPC
-      _request_cond->signal();
-    _lock.unlock();
-#endif
-
-  } else {
-    // If we're not running asynchronously, process the load request
-    // directly now.
-    if (_token_board->_waiting.full()) {
-      loader_cat.error()
-        << "Loader::request_load() - Too many pending requests\n";
-      return 0;
     }
-
-    if (loader_cat.is_debug()) {
-      loader_cat.debug()
-        << "Load requested for file: " << filename << "\n";
-    }
-
-    tok = new LoaderToken(_next_token++, event_name, filename, search);
-    _token_board->_waiting.push_back(tok);
-    process_request();
   }
-
-  return tok->_id;
+   
+  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();
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -275,126 +296,83 @@ request_load(const string &event_name, const Filename &filename, bool search) {
 //               completed and not yet been fetched, false otherwise.
 ////////////////////////////////////////////////////////////////////
 bool Loader::
-check_load(uint id) {
-  return _token_board->is_done_token(id);
+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
+//  Description: Returns the node associated with the indicated id
 //               number (returned by a previous call to request_load),
-//               or NULL if the request has not yet completed.
+//               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(uint id) {
-  PT(LoaderToken) tok = _token_board->get_done_token(id);
-  if (tok.is_null()) {
-    loader_cat.debug()
-      << "Request to fetch id " << id << " which has not yet completed.\n";
+fetch_load(int id) {
+  MutexHolder holder(_lock);
+
+  int index = find_id(_finished, id);
+  if (index == -1) {
+    nassert_raise("No such loader ID.");
     return NULL;
   }
-  PT(PandaNode) node = tok->_node;
-  return node;
+
+  LoaderRequest *request = _finished[index];
+  _finished.erase(_finished.begin() + index);
+
+  PT(PandaNode) model = request->_model;
+  delete request;
+
+  return model;
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: Loader::load_file_types
-//       Access: Private, Static
-//  Description: Loads up all of the dynamic libraries named in a
-//               load-file-type Configure variable.  Presumably this
-//               will make the various file types available for
-//               runtime loading.
+//     Function: Loader::output
+//       Access: Published, Virtual
+//  Description: 
 ////////////////////////////////////////////////////////////////////
 void Loader::
-load_file_types() {
-  if (!_file_types_loaded) {
-    int num_unique_values = load_file_type.get_num_unique_values();
-
-    for (int i = 0; i < num_unique_values; i++) {
-      string param = load_file_type.get_unique_value(i);
-
-      vector_string words;
-      extract_words(param, words);
-
-      if (words.size() == 1) {
-        // Exactly one word: load the named library immediately.
-        string name = words[0];
-        Filename dlname = Filename::dso_filename("lib" + name + ".so");
-        loader_cat.info()
-          << "loading file type module: " << name << endl;
-        void *tmp = load_dso(dlname);
-        if (tmp == (void *)NULL) {
-          loader_cat.warning()
-            << "Unable to load " << dlname.to_os_specific()
-            << ": " << load_dso_error() << endl;
-        }
-        
-      } else if (words.size() > 1) {
-        // Multiple words: the first n words are filename extensions,
-        // and the last word is the name of the library to load should
-        // any of those filename extensions be encountered.
-        LoaderFileTypeRegistry *registry = LoaderFileTypeRegistry::get_global_ptr();
-        size_t num_extensions = words.size() - 1;
-        string library_name = words[num_extensions];
-        
-        for (size_t i = 0; i < num_extensions; i++) {
-          string extension = words[i];
-          if (extension[0] == '.') {
-            extension = extension.substr(1);
-          }
-          
-          registry->register_deferred_type(extension, library_name);
-        }
-      }
-    }
+output(ostream &out) const {
+  out << "Loader " << get_name();
 
-    _file_types_loaded = true;
+  if (_state == S_started) {
+    MutexHolder holder(_lock);
+    out << " " << _pending.size() << " pending, "
+        << " " << _finished.size() << " finished.";
   }
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: Loader::process_request
+//     Function: Loader::poll_loader
 //       Access: Private
-//  Description: Serves any requests on the token board, moving them
-//               to the done queue.
+//  Description: Called internally to empty the requests from the
+//               _pending queue.  Assumes the lock is already held.
 ////////////////////////////////////////////////////////////////////
-bool Loader::
-process_request() {
-  if (_shutdown) {
-    if (loader_cat.is_debug())
-      loader_cat.debug()
-          << "Loader shutting down...\n";
-    return false;
-  }
-
-  // If there is actually a request token - process it
-  while (!_token_board->_waiting.empty()) {
-    PT(LoaderToken) tok = _token_board->_waiting.front();
-    _token_board->_waiting.pop_front();
-    tok->_node = load_file(tok->_path, tok->_search);
-    if (tok->_node == (PandaNode *)NULL) {
-      loader_cat.error()
-        << "Loader::callback() - couldn't find file: "
-        << tok->_path << "\n";
-    } else {
-      _token_board->_done.push_back(tok);
-
-      // Throw a "done" event now.
-      if (!tok->_event_name.empty()) {
-        PT_Event done = new Event(tok->_event_name);
-        done->add_parameter(EventParameter((int)tok->_id));
-        throw_event(done);
-      }
-    }
-
-    if (loader_cat.is_debug()) {
-      loader_cat.debug()
-        << "loading complete for " << tok->_path << "\n";
-    }
+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);
   }
-
-  return true;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -410,6 +388,17 @@ process_request() {
 ////////////////////////////////////////////////////////////////////
 PT(PandaNode) Loader::
 load_file(const Filename &filename, const LoaderOptions &options) const {
+  if (options.allow_ram_cache()) {
+    // If we're allowing a RAM cache (and we don't have any other
+    // funny options), use the ModelPool to load the file.
+    PT(PandaNode) node = ModelPool::load_model(filename, options);
+    if (node != (PandaNode *)NULL) {
+      // But return a deep copy of the shared model.
+      node = node->copy_subgraph();
+    }
+    return node;
+  }
+
   Results results;
   int num_files;
 
@@ -471,7 +460,7 @@ load_file(const Filename &filename, const LoaderOptions &options) const {
 
     PT(BamCacheRecord) record;
 
-    if (cache->get_active()) {
+    if (cache->get_active() && options.allow_disk_cache()) {
       // See if the texture can be found in the on-disk cache, if it is
       // active.
       record = cache->lookup(path, "bam");
@@ -510,3 +499,106 @@ load_file(const Filename &filename, const LoaderOptions &options) const {
   return NULL;
 }
 
+
+////////////////////////////////////////////////////////////////////
+//     Function: Loader::load_file_types
+//       Access: Private, Static
+//  Description: Loads up all of the dynamic libraries named in a
+//               load-file-type Configure variable.  Presumably this
+//               will make the various file types available for
+//               runtime loading.
+////////////////////////////////////////////////////////////////////
+void Loader::
+load_file_types() {
+  if (!_file_types_loaded) {
+    int num_unique_values = load_file_type.get_num_unique_values();
+
+    for (int i = 0; i < num_unique_values; i++) {
+      string param = load_file_type.get_unique_value(i);
+
+      vector_string words;
+      extract_words(param, words);
+
+      if (words.size() == 1) {
+        // Exactly one word: load the named library immediately.
+        string name = words[0];
+        Filename dlname = Filename::dso_filename("lib" + name + ".so");
+        loader_cat.info()
+          << "loading file type module: " << name << endl;
+        void *tmp = load_dso(dlname);
+        if (tmp == (void *)NULL) {
+          loader_cat.warning()
+            << "Unable to load " << dlname.to_os_specific()
+            << ": " << load_dso_error() << endl;
+        }
+        
+      } else if (words.size() > 1) {
+        // Multiple words: the first n words are filename extensions,
+        // and the last word is the name of the library to load should
+        // any of those filename extensions be encountered.
+        LoaderFileTypeRegistry *registry = LoaderFileTypeRegistry::get_global_ptr();
+        size_t num_extensions = words.size() - 1;
+        string library_name = words[num_extensions];
+        
+        for (size_t i = 0; i < num_extensions; i++) {
+          string extension = words[i];
+          if (extension[0] == '.') {
+            extension = extension.substr(1);
+          }
+          
+          registry->register_deferred_type(extension, library_name);
+        }
+      }
+    }
+
+    _file_types_loaded = true;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     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();
+}
+

+ 74 - 15
panda/src/pgraph/loader.h

@@ -21,23 +21,36 @@
 
 #include "pandabase.h"
 
+#include "namable.h"
 #include "loaderOptions.h"
 #include "pnotify.h"
 #include "pandaNode.h"
 #include "filename.h"
-#include "tokenBoard.h"
-#include "asyncUtility.h"
 #include "dSearchPath.h"
+#include "thread.h"
+#include "pmutex.h"
+#include "conditionVar.h"
+#include "pvector.h"
+#include "pdeque.h"
 
-class LoaderToken;
 class LoaderFileType;
 
 ////////////////////////////////////////////////////////////////////
 //       Class : Loader
-// Description : Handles database loading through asynchronous
-//               threading
+// Description : A convenient class for loading models from disk, in
+//               bam or egg format (or any of a number of other
+//               formats implemented by a LoaderFileType, such as
+//               ptloader).
+//
+//               This class supports synchronous as well as
+//               asynchronous loading.  In asynchronous loading, the
+//               model is loaded in the background by a thread, and an
+//               event will be generated when the model is available.
+//               If threading is not available, the asynchronous
+//               loading interface may be used, but it loads
+//               synchronously.
 ////////////////////////////////////////////////////////////////////
-class EXPCL_PANDA Loader : public AsyncUtility {
+class EXPCL_PANDA Loader : public Namable {
 private:
   class ConsiderFile {
   public:
@@ -66,8 +79,9 @@ PUBLISHED:
     Files _files;
   };
 
-  Loader();
-  ~Loader();
+  Loader(const string &name = "loader",
+         int num_threads = -1);
+  virtual ~Loader();
 
   int find_all_files(const Filename &filename, const DSearchPath &search_path,
                      Results &results) const;
@@ -75,19 +89,64 @@ PUBLISHED:
   INLINE PT(PandaNode) load_sync(const Filename &filename, 
                                  const LoaderOptions &options = LoaderOptions()) const;
 
-  uint request_load(const string &event_name, const Filename &filename, bool search = true);
-  bool check_load(uint id);
-  PT(PandaNode) fetch_load(uint id);
+  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);
+
+  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;
 
-  virtual bool process_request();
-  PT(PandaNode) load_file(const Filename &filename, const LoaderOptions &options) const;
+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;
 
-  typedef TokenBoard<LoaderToken> LoaderTokenBoard;
-  LoaderTokenBoard *_token_board;
+  friend class LoaderThread;
 };
 
 #include "loader.I"

+ 95 - 71
panda/src/pgraph/loaderOptions.I

@@ -1,71 +1,95 @@
-// Filename: loaderOptions.I
-// Created by:  drose (05Oct05)
-//
-////////////////////////////////////////////////////////////////////
-//
-// 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: LoaderOptions::Constructor
-//       Access: Published
-//  Description:
-////////////////////////////////////////////////////////////////////
-INLINE LoaderOptions::
-LoaderOptions(int flags) : 
-  _flags(flags)
-{
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: LoaderOptions::Copy Constructor
-//       Access: Published
-//  Description:
-////////////////////////////////////////////////////////////////////
-INLINE LoaderOptions::
-LoaderOptions(const LoaderOptions &copy) :
-  _flags(copy._flags)
-{
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: LoaderOptions::Copy Assignment Operator
-//       Access: Published
-//  Description:
-////////////////////////////////////////////////////////////////////
-INLINE void LoaderOptions::
-operator = (const LoaderOptions &copy) {
-  _flags = copy._flags;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: LoaderOptions::set_flags
-//       Access: Published
-//  Description:
-////////////////////////////////////////////////////////////////////
-INLINE void LoaderOptions::
-set_flags(int flags) {
-  _flags = flags;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: LoaderOptions::get_flags
-//       Access: Published
-//  Description:
-////////////////////////////////////////////////////////////////////
-INLINE int LoaderOptions::
-get_flags() const {
-  return _flags;
-}
-
+// Filename: loaderOptions.I
+// Created by:  drose (05Oct05)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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: LoaderOptions::Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE LoaderOptions::
+LoaderOptions(int flags) : 
+  _flags(flags)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: LoaderOptions::Copy Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE LoaderOptions::
+LoaderOptions(const LoaderOptions &copy) :
+  _flags(copy._flags)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: LoaderOptions::Copy Assignment Operator
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void LoaderOptions::
+operator = (const LoaderOptions &copy) {
+  _flags = copy._flags;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: LoaderOptions::set_flags
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void LoaderOptions::
+set_flags(int flags) {
+  _flags = flags;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: LoaderOptions::get_flags
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE int LoaderOptions::
+get_flags() const {
+  return _flags;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: LoaderOptions::allow_disk_cache
+//       Access: Published
+//  Description: Returns true if the loader flags allow retrieving the
+//               model from the on-disk bam cache (if it is enabled),
+//               false otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE bool LoaderOptions::
+allow_disk_cache() const {
+  return (_flags & LF_no_disk_cache) == 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: LoaderOptions::allow_ram_cache
+//       Access: Published
+//  Description: Returns true if the loader flags allow retrieving the
+//               model from the in-memory ModelPool cache, false
+//               otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE bool LoaderOptions::
+allow_ram_cache() const {
+  return ((_flags & (LF_no_ram_cache | LF_convert_anim)) == 0 &&
+          (_flags & LF_search) != 0);
+}

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

@@ -36,6 +36,9 @@ PUBLISHED:
     LF_convert_skeleton  = 0x0004,
     LF_convert_channels  = 0x0008,
     LF_convert_anim      = 0x000c,  // skeleton + channels
+    LF_no_disk_cache     = 0x0010,  // disallow BamCache
+    LF_no_ram_cache      = 0x0020,  // disallow ModelPool
+    LF_no_cache          = 0x0030,  // no_disk + no_ram
   };
 
   INLINE LoaderOptions(int flags = LF_search | LF_report_errors);
@@ -45,6 +48,9 @@ PUBLISHED:
   INLINE void set_flags(int flags);
   INLINE int get_flags() const;
 
+  INLINE bool allow_disk_cache() const;
+  INLINE bool allow_ram_cache() const;
+
 private:  
   int _flags;
 };

+ 2 - 2
panda/src/pgraph/modelPool.I

@@ -53,8 +53,8 @@ verify_model(const string &filename) {
 //               file cannot be found, returns NULL.
 ////////////////////////////////////////////////////////////////////
 INLINE PandaNode *ModelPool::
-load_model(const string &filename) {
-  return get_ptr()->ns_load_model(filename);
+load_model(const string &filename, const LoaderOptions &options) {
+  return get_ptr()->ns_load_model(filename, options);
 }
 
 ////////////////////////////////////////////////////////////////////

+ 38 - 8
panda/src/pgraph/modelPool.cxx

@@ -19,6 +19,7 @@
 #include "modelPool.h"
 #include "loader.h"
 #include "config_pgraph.h"
+#include "mutexHolder.h"
 
 
 ModelPool *ModelPool::_global_ptr = (ModelPool *)NULL;
@@ -44,6 +45,7 @@ write(ostream &out) {
 ////////////////////////////////////////////////////////////////////
 bool ModelPool::
 ns_has_model(const string &filename) {
+  MutexHolder holder(_lock);
   Models::const_iterator ti;
   ti = _models.find(filename);
   if (ti != _models.end()) {
@@ -60,23 +62,44 @@ ns_has_model(const string &filename) {
 //  Description: The nonstatic implementation of load_model().
 ////////////////////////////////////////////////////////////////////
 PandaNode *ModelPool::
-ns_load_model(const string &filename) {
-  Models::const_iterator ti;
-  ti = _models.find(filename);
-  if (ti != _models.end()) {
-    // This model was previously loaded.
-    return (*ti).second;
+ns_load_model(const string &filename, const LoaderOptions &options) {
+  {
+    MutexHolder holder(_lock);
+    Models::const_iterator ti;
+    ti = _models.find(filename);
+    if (ti != _models.end()) {
+      // This model was previously loaded.
+      return (*ti).second;
+    }
   }
 
   loader_cat.info()
     << "Loading model " << filename << "\n";
-  PT(PandaNode) node = model_loader.load_sync(filename);
+  LoaderOptions new_options(options);
+  new_options.set_flags(new_options.get_flags() | LoaderOptions::LF_no_ram_cache);
+
+  PT(PandaNode) node = model_loader.load_sync(filename, new_options);
+
   if (node.is_null()) {
     // This model was not found.
     return (PandaNode *)NULL;
   }
 
-  _models[filename] = node;
+  {
+    MutexHolder holder(_lock);
+
+    // Look again, in case someone has just loaded the model in
+    // another thread.
+    Models::const_iterator ti;
+    ti = _models.find(filename);
+    if (ti != _models.end()) {
+      // This model was previously loaded.
+      return (*ti).second;
+    }
+
+    _models[filename] = node;
+  }
+
   return node;
 }
 
@@ -87,6 +110,7 @@ ns_load_model(const string &filename) {
 ////////////////////////////////////////////////////////////////////
 void ModelPool::
 ns_add_model(const string &filename, PandaNode *model) {
+  MutexHolder holder(_lock);
   // We blow away whatever model was there previously, if any.
   _models[filename] = model;
 }
@@ -98,6 +122,7 @@ ns_add_model(const string &filename, PandaNode *model) {
 ////////////////////////////////////////////////////////////////////
 void ModelPool::
 ns_release_model(const string &filename) {
+  MutexHolder holder(_lock);
   Models::iterator ti;
   ti = _models.find(filename);
   if (ti != _models.end()) {
@@ -112,6 +137,7 @@ ns_release_model(const string &filename) {
 ////////////////////////////////////////////////////////////////////
 void ModelPool::
 ns_release_all_models() {
+  MutexHolder holder(_lock);
   _models.clear();
 }
 
@@ -122,6 +148,8 @@ ns_release_all_models() {
 ////////////////////////////////////////////////////////////////////
 int ModelPool::
 ns_garbage_collect() {
+  MutexHolder holder(_lock);
+
   int num_released = 0;
   Models new_set;
 
@@ -150,6 +178,8 @@ ns_garbage_collect() {
 ////////////////////////////////////////////////////////////////////
 void ModelPool::
 ns_list_contents(ostream &out) const {
+  MutexHolder holder(_lock);
+
   out << _models.size() << " models:\n";
   Models::const_iterator ti;
   for (ti = _models.begin(); ti != _models.end(); ++ti) {

+ 8 - 3
panda/src/pgraph/modelPool.h

@@ -24,8 +24,9 @@
 #include "filename.h"
 #include "pandaNode.h"
 #include "pointerTo.h"
-
+#include "pmutex.h"
 #include "pmap.h"
+#include "loaderOptions.h"
 
 ////////////////////////////////////////////////////////////////////
 //       Class : ModelPool
@@ -50,7 +51,8 @@ class EXPCL_PANDA ModelPool {
 PUBLISHED:
   INLINE static bool has_model(const string &filename);
   INLINE static bool verify_model(const string &filename);
-  INLINE static PandaNode *load_model(const string &filename);
+  INLINE static PandaNode *load_model(const string &filename,
+                                      const LoaderOptions &options = LoaderOptions());
 
   INLINE static void add_model(const string &filename, PandaNode *model);
   INLINE static void release_model(const string &filename);
@@ -65,7 +67,8 @@ private:
   INLINE ModelPool();
 
   bool ns_has_model(const string &filename);
-  PandaNode *ns_load_model(const string &filename);
+  PandaNode *ns_load_model(const string &filename,
+                           const LoaderOptions &options);
   void ns_add_model(const string &filename, PandaNode *model);
   void ns_release_model(const string &filename);
   void ns_release_all_models();
@@ -75,6 +78,8 @@ private:
   static ModelPool *get_ptr();
 
   static ModelPool *_global_ptr;
+
+  Mutex _lock;
   typedef pmap<string,  PT(PandaNode) > Models;
   Models _models;
 };

+ 36 - 6
panda/src/pgraph/shaderPool.cxx

@@ -43,6 +43,8 @@ write(ostream &out) {
 ////////////////////////////////////////////////////////////////////
 bool ShaderPool::
 ns_has_shader(const string &str) {
+  MutexHolder holder(_lock);
+
   string index_str;
   Filename filename;
   int face_index;
@@ -70,11 +72,15 @@ ns_load_shader(const string &str) {
   int face_index;
   lookup_filename(str, index_str, filename, face_index);
 
-  Shaders::const_iterator ti;
-  ti = _shaders.find(index_str);
-  if (ti != _shaders.end()) {
-    // This shader was previously loaded.
-    return (*ti).second;
+  {
+    MutexHolder holder(_lock);
+
+    Shaders::const_iterator ti;
+    ti = _shaders.find(index_str);
+    if (ti != _shaders.end()) {
+      // This shader was previously loaded.
+      return (*ti).second;
+    }
   }
 
 /*
@@ -111,7 +117,21 @@ ns_load_shader(const string &str) {
     return NULL;
   }
 
-  _shaders[index_str] = shader;
+  {
+    MutexHolder holder(_lock);
+
+    // Now try again.  Someone may have loaded the shader in another
+    // thread.
+    Shaders::const_iterator ti;
+    ti = _shaders.find(index_str);
+    if (ti != _shaders.end()) {
+      // This shader was previously loaded.
+      return (*ti).second;
+    }
+
+    _shaders[index_str] = shader;
+  }
+
   return shader;
 }
 
@@ -122,6 +142,8 @@ ns_load_shader(const string &str) {
 ////////////////////////////////////////////////////////////////////
 void ShaderPool::
 ns_add_shader(const string &str, Shader *shader) {
+  MutexHolder holder(_lock);
+
   string index_str;
   Filename filename;
   int face_index;
@@ -138,6 +160,8 @@ ns_add_shader(const string &str, Shader *shader) {
 ////////////////////////////////////////////////////////////////////
 void ShaderPool::
 ns_release_shader(const string &filename) {
+  MutexHolder holder(_lock);
+
   Shaders::iterator ti;
   ti = _shaders.find(filename);
   if (ti != _shaders.end()) {
@@ -152,6 +176,8 @@ ns_release_shader(const string &filename) {
 ////////////////////////////////////////////////////////////////////
 void ShaderPool::
 ns_release_all_shaders() {
+  MutexHolder holder(_lock);
+
   _shaders.clear();
 }
 
@@ -162,6 +188,8 @@ ns_release_all_shaders() {
 ////////////////////////////////////////////////////////////////////
 int ShaderPool::
 ns_garbage_collect() {
+  MutexHolder holder(_lock);
+
   int num_released = 0;
   Shaders new_set;
 
@@ -192,6 +220,8 @@ ns_garbage_collect() {
 ////////////////////////////////////////////////////////////////////
 void ShaderPool::
 ns_list_contents(ostream &out) const {
+  MutexHolder holder(_lock);
+
   out << _shaders.size() << " shaders:\n";
   Shaders::const_iterator ti;
   for (ti = _shaders.begin(); ti != _shaders.end(); ++ti) {

+ 3 - 3
panda/src/pgraph/shaderPool.h

@@ -20,10 +20,9 @@
 #define SHADERPOOL_H
 
 #include "pandabase.h"
-
 #include "shader.h"
-
 #include "filename.h"
+#include "pmutex.h"
 #include "pmap.h"
 
 ////////////////////////////////////////////////////////////////////
@@ -65,8 +64,9 @@ private:
                               Filename &filename, int &face_index);
 
   static ShaderPool *get_ptr();
-
   static ShaderPool *_global_ptr;
+
+  Mutex _lock;
   typedef pmap<string,  CPT(Shader) > Shaders;
   Shaders _shaders;
 };

+ 16 - 1
panda/src/pipeline/thread.I

@@ -249,6 +249,17 @@ sleep(double seconds) {
   ThreadImpl::sleep(seconds);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: Thread::is_started
+//       Access: Public
+//  Description: Returns true if the thread has been started, false if
+//               it has not, or if join() has already been called.
+////////////////////////////////////////////////////////////////////
+INLINE bool Thread::
+is_started() const {
+  return _started;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: Thread::interrupt
 //       Access: Public
@@ -260,6 +271,7 @@ sleep(double seconds) {
 ////////////////////////////////////////////////////////////////////
 INLINE void Thread::
 interrupt() {
+  nassertv(_started);
   _impl.interrupt();
 }
 
@@ -273,7 +285,10 @@ interrupt() {
 INLINE void Thread::
 join() {
   TAU_PROFILE("void Thread::join()", " ", TAU_USER);
-  _impl.join();
+  if (_started) {
+    _impl.join();
+    _started = false;
+  }
 }
 
 ////////////////////////////////////////////////////////////////////

+ 2 - 0
panda/src/pipeline/thread.h

@@ -79,6 +79,8 @@ PUBLISHED:
   virtual void output(ostream &out) const;
 
 public:
+  INLINE bool is_started() const;
+
   bool start(ThreadPriority priority, bool global, bool joinable);
   INLINE void interrupt();
   INLINE void join();

+ 37 - 6
panda/src/text/fontPool.cxx

@@ -24,6 +24,7 @@
 #include "virtualFileSystem.h"
 #include "nodePath.h"
 #include "loader.h"
+#include "mutexHolder.h"
 
 FontPool *FontPool::_global_ptr = (FontPool *)NULL;
 
@@ -47,6 +48,8 @@ write(ostream &out) {
 ////////////////////////////////////////////////////////////////////
 bool FontPool::
 ns_has_font(const string &str) {
+  MutexHolder holder(_lock);
+
   string index_str;
   Filename filename;
   int face_index;
@@ -74,11 +77,15 @@ ns_load_font(const string &str) {
   int face_index;
   lookup_filename(str, index_str, filename, face_index);
 
-  Fonts::const_iterator ti;
-  ti = _fonts.find(index_str);
-  if (ti != _fonts.end()) {
-    // This font was previously loaded.
-    return (*ti).second;
+  {
+    MutexHolder holder(_lock);
+    
+    Fonts::const_iterator ti;
+    ti = _fonts.find(index_str);
+    if (ti != _fonts.end()) {
+      // This font was previously loaded.
+      return (*ti).second;
+    }
   }
 
   text_cat.info()
@@ -114,7 +121,21 @@ ns_load_font(const string &str) {
     return NULL;
   }
 
-  _fonts[index_str] = font;
+
+  {
+    MutexHolder holder(_lock);
+
+    // Look again.  It may have been loaded by another thread.
+    Fonts::const_iterator ti;
+    ti = _fonts.find(index_str);
+    if (ti != _fonts.end()) {
+      // This font was previously loaded.
+      return (*ti).second;
+    }
+
+    _fonts[index_str] = font;
+  }
+
   return font;
 }
 
@@ -125,6 +146,8 @@ ns_load_font(const string &str) {
 ////////////////////////////////////////////////////////////////////
 void FontPool::
 ns_add_font(const string &str, TextFont *font) {
+  MutexHolder holder(_lock);
+
   string index_str;
   Filename filename;
   int face_index;
@@ -141,6 +164,8 @@ ns_add_font(const string &str, TextFont *font) {
 ////////////////////////////////////////////////////////////////////
 void FontPool::
 ns_release_font(const string &filename) {
+  MutexHolder holder(_lock);
+
   Fonts::iterator ti;
   ti = _fonts.find(filename);
   if (ti != _fonts.end()) {
@@ -155,6 +180,8 @@ ns_release_font(const string &filename) {
 ////////////////////////////////////////////////////////////////////
 void FontPool::
 ns_release_all_fonts() {
+  MutexHolder holder(_lock);
+
   _fonts.clear();
 }
 
@@ -165,6 +192,8 @@ ns_release_all_fonts() {
 ////////////////////////////////////////////////////////////////////
 int FontPool::
 ns_garbage_collect() {
+  MutexHolder holder(_lock);
+
   int num_released = 0;
   Fonts new_set;
 
@@ -193,6 +222,8 @@ ns_garbage_collect() {
 ////////////////////////////////////////////////////////////////////
 void FontPool::
 ns_list_contents(ostream &out) const {
+  MutexHolder holder(_lock);
+
   out << _fonts.size() << " fonts:\n";
   Fonts::const_iterator ti;
   for (ti = _fonts.begin(); ti != _fonts.end(); ++ti) {

+ 3 - 2
panda/src/text/fontPool.h

@@ -23,8 +23,8 @@
 
 #include "texture.h"
 #include "textFont.h"
-
 #include "filename.h"
+#include "pmutex.h"
 #include "pmap.h"
 
 ////////////////////////////////////////////////////////////////////
@@ -66,8 +66,9 @@ private:
                               Filename &filename, int &face_index);
 
   static FontPool *get_ptr();
-
   static FontPool *_global_ptr;
+
+  Mutex _lock;
   typedef pmap<string,  PT(TextFont) > Fonts;
   Fonts _fonts;
 };