Quellcode durchsuchen

asynchronous model loads

David Rose vor 19 Jahren
Ursprung
Commit
c6b8cb9cd6
1 geänderte Dateien mit 156 neuen und 71 gelöschten Zeilen
  1. 156 71
      direct/src/showbase/Loader.py

+ 156 - 71
direct/src/showbase/Loader.py

@@ -4,50 +4,155 @@ __all__ = ['Loader']
 
 from pandac.PandaModules import *
 from direct.directnotify.DirectNotifyGlobal import *
+from direct.showbase.DirectObject import DirectObject
+import types
 
 # You can specify a phaseChecker callback to check
 # a modelPath to see if it is being loaded in the correct
 # phase
 phaseChecker = None
 
-class Loader:
+class Loader(DirectObject):
     """
     Load models, textures, sounds, and code.
     """
     notify = directNotify.newCategory("Loader")
-    modelCount = 0
+    loaderIndex = 0
+
+    class Callback:
+        def __init__(self, numModels, callback, extraArgs):
+            self.models = [None] * numModels
+            self.callback = callback
+            self.extraArgs = extraArgs
+            self.numRemaining = numModels
+
+        def gotModel(self, index, model):
+            self.models[index] = model
+            self.numRemaining -= 1
+
+            if self.numRemaining == 0:
+                self.callback(*(self.models + 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)
 
     def destroy(self):
+        self.ignore(self.hook)
         del self.base
         del self.loader
 
     # model loading funcs
-    def loadModel(self, modelPath, fMakeNodeNamesUnique = 0,
-                  loaderOptions = None):
+    def loadModel(self, modelPath, loaderOptions = None, noCache = None,
+                  callback = None, extraArgs = []):
         """
-        modelPath is a string.
-
-        Attempt to load a model from given file path, return
-        a nodepath to the model if successful or None otherwise.
+        Attempts to load a model or models from one or more relative
+        pathnames.  If the input modelPath is a string (a single model
+        pathname), the return value will be a NodePath to the model
+        loaded if the load was successful, or None otherwise.  If the
+        input modelPath is a list of pathnames, the return value will
+        be a list of NodePaths and/or Nones.
+
+        loaderOptions may optionally be passed in to control details
+        about the way the model is searched and loaded.  See the
+        LoaderOptions class for more.
+
+        The default is to look in the ModelPool (RAM) cache first, and
+        return a copy from that if the model can be found there.  If
+        the bam cache is enabled (via the model-cache-dir config
+        variable), then that will be consulted next, and if both
+        caches fail, the file will be loaded from disk.  If noCache is
+        True, then neither cache will be consulted or updated.
+
+        If callback is not None, then the model load will be performed
+        asynchronously.  In this case, loadModel() will initiate a
+        background load and return immediately, with no return value.
+        At some later point, when the requested model(s) have finished
+        loading, the callback function will be invoked with the n
+        loaded models passed as its parameter list.  It is possible
+        that the callback will be invoked immediately, even before
+        loadModel() returns.
+
+        True asynchronous model loading requires Panda to have been
+        compiled with threading support enabled (you can test
+        Thread.isThreadingSupported()).  In the absence of threading
+        support, the asynchronous interface still exists and still
+        behaves exactly as described, except that loadModel() might
+        not return immediately.
+        
         """
+        
         assert Loader.notify.debug("Loading model: %s" % (modelPath))
         if phaseChecker:
             phaseChecker(modelPath)
         if loaderOptions == None:
             loaderOptions = LoaderOptions()
-        node = self.loader.loadSync(Filename(modelPath), loaderOptions)
-        if (node != None):
-            nodePath = NodePath(node)
-            if fMakeNodeNamesUnique:
-                self.makeNodeNamesUnique(nodePath, 0)
         else:
-            nodePath = None
-        return nodePath
+            loaderOptions = LoaderOptions(loaderOptions)
+
+        if noCache is not None:
+            if noCache:
+                loaderOptions.setFlags(loaderOptions.getFlags() | LoaderOptions.LFNoCache)
+            else:
+                loaderOptions.setFlags(loaderOptions.getFlags() & ~LoaderOptions.LFNoCache)
+
+        if isinstance(modelPath, types.StringTypes) or \
+           isinstance(modelPath, Filename):
+            # We were given a single model pathname.
+            modelList = [modelPath]
+            gotList = False
+        else:
+            # Assume we were given a list of model pathnames.
+            modelList = modelPath
+            gotList = True
+
+        if callback is None:
+            # We got no callback, so it's a synchronous load.
+
+            result = []
+            for modelPath in modelList:
+                node = self.loader.loadSync(Filename(modelPath), loaderOptions)
+                if (node != None):
+                    nodePath = NodePath(node)
+                else:
+                    nodePath = None
+
+                result.append(nodePath)
+
+            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 models have been loaded, we'll invoke the
+            # callback (passing it the models on the parameter list).
+            
+            cb = Loader.Callback(len(modelList), callback, extraArgs)
+            for i in range(len(modelList)):
+                modelPath = modelList[i]
+                if loaderOptions.allowRamCache() and ModelPool.hasModel(modelPath):
+                    # If it's already in the model pool, we won't
+                    # bother bouncing the load request through the
+                    # thread; and maybe we can just make the callback
+                    # immediately.
+                    node = ModelPool.loadModel(modelPath)
+                    nodePath = NodePath(node)
+                    cb.gotModel(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)
 
     def loadModelOnce(self, modelPath):
         """
@@ -57,56 +162,19 @@ class Loader:
         then attempt to load it from disk. Return a nodepath to
         the model if successful or None otherwise
         """
-        assert Loader.notify.debug("Loading model once: %s" % (modelPath))
-        if phaseChecker:
-            phaseChecker(modelPath)
-        node = ModelPool.loadModel(modelPath)
-        if (node != None):
-            nodePath = NodePath.anyPath(node)
-        else:
-            nodePath = None
-        return nodePath
-
-    def loadModelOnceUnder(self, modelPath, underNode):
-        """loadModelOnceUnder(self, string, string | node | NodePath)
-        Behaves like loadModelOnce, but also implicitly creates a new
-        node to attach the model under, which helps to differentiate
-        different instances.
-
-        underNode may be either a node name, or a NodePath or a Node
-        to an already-existing node.
+        Loader.notify.debug("loader.loadModelOnce() is deprecated; use loader.loadModel() instead.")
 
-        This is useful when you want to load a model once several
-        times before parenting each instance somewhere, or when you
-        want to load a model and immediately set a transform on it.
-        But also consider loadModelCopy().
-        """
-        assert Loader.notify.debug(
-            "Loading model once: %s under %s" % (modelPath, underNode))
-        if phaseChecker:
-            phaseChecker(modelPath)
-        node = ModelPool.loadModel(modelPath)
-        if (node != None):
-            nodePath = NodePath(underNode)
-            nodePath.attachNewNode(node)
-        else:
-            nodePath = None
-        return nodePath
+        return self.loadModel(modelPath, noCache = False)
 
-    def loadModelCopy(self, modelPath):
+    def loadModelCopy(self, modelPath, callback = None):
         """loadModelCopy(self, string)
         Attempt to load a model from modelPool, if not present
         then attempt to load it from disk. Return a nodepath to
         a copy of the model if successful or None otherwise
         """
-        assert Loader.notify.debug("Loading model copy: %s" % (modelPath))
-        if phaseChecker:
-            phaseChecker(modelPath)
-        node = ModelPool.loadModel(modelPath)
-        if (node != None):
-            return NodePath(node.copySubgraph())
-        else:
-            return None
+        Loader.notify.debug("loader.loadModelCopy() is deprecated; use loader.loadModel() instead.")
+
+        return self.loadModel(modelPath, noCache = False)
 
     def loadModelNode(self, modelPath):
         """
@@ -122,10 +190,13 @@ class Loader:
 
         However, if you're loading a font, see loadFont(), below.
         """
-        assert Loader.notify.debug("Loading model once node: %s" % (modelPath))
-        if phaseChecker:
-            phaseChecker(modelPath)
-        return ModelPool.loadModel(modelPath)
+        Loader.notify.debug("loader.loadModelNode() is deprecated; use loader.loadModel() instead.")
+
+        model = self.loadModel(modelPath, noCache = False)
+        if model is not None:
+            model = model.node()
+
+        return model
 
     def unloadModel(self, modelPath):
         """
@@ -285,15 +356,14 @@ class Loader:
             Loader.notify.warning("Could not load music file %s." % name)
         return sound
 
-
-    def makeNodeNamesUnique(self, nodePath, nodeCount):
-        if nodeCount == 0:
-            Loader.modelCount += 1
-        nodePath.setName(nodePath.getName() +
-                         ('_%d_%d' % (Loader.modelCount, nodeCount)))
-        for i in range(nodePath.getNumChildren()):
-            nodeCount += 1
-            self.makeNodeNamesUnique(nodePath.getChild(i), nodeCount)
+##     def makeNodeNamesUnique(self, nodePath, nodeCount):
+##         if nodeCount == 0:
+##             Loader.modelCount += 1
+##         nodePath.setName(nodePath.getName() +
+##                          ('_%d_%d' % (Loader.modelCount, nodeCount)))
+##         for i in range(nodePath.getNumChildren()):
+##             nodeCount += 1
+##             self.makeNodeNamesUnique(nodePath.getChild(i), nodeCount)
 
     def loadShader (self, shaderPath):
         shader = ShaderPool.loadShader (shaderPath)
@@ -305,3 +375,18 @@ class Loader:
         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."""
+
+        cb, i = self.pendingCallbacks[id]
+        del self.pendingCallbacks[id]
+
+        node = self.loader.fetchLoad(id)
+        if (node != None):
+            nodePath = NodePath(node)
+        else:
+            nodePath = None
+
+        cb.gotModel(i, nodePath)