mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-04 10:54:24 -04:00
asynchronous model loads
This commit is contained in:
parent
2dca55d07e
commit
c6b8cb9cd6
@ -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.
|
||||
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.
|
||||
|
||||
Attempt to load a model from given file path, return
|
||||
a nodepath to the model if successful or None otherwise.
|
||||
"""
|
||||
|
||||
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
|
||||
Loader.notify.debug("loader.loadModelOnce() is deprecated; use loader.loadModel() instead.")
|
||||
|
||||
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.
|
||||
return self.loadModel(modelPath, noCache = False)
|
||||
|
||||
underNode may be either a node name, or a NodePath or a Node
|
||||
to an already-existing node.
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user