mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-04 19:08:55 -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 pandac.PandaModules import *
|
||||||
from direct.directnotify.DirectNotifyGlobal import *
|
from direct.directnotify.DirectNotifyGlobal import *
|
||||||
|
from direct.showbase.DirectObject import DirectObject
|
||||||
|
import types
|
||||||
|
|
||||||
# You can specify a phaseChecker callback to check
|
# You can specify a phaseChecker callback to check
|
||||||
# a modelPath to see if it is being loaded in the correct
|
# a modelPath to see if it is being loaded in the correct
|
||||||
# phase
|
# phase
|
||||||
phaseChecker = None
|
phaseChecker = None
|
||||||
|
|
||||||
class Loader:
|
class Loader(DirectObject):
|
||||||
"""
|
"""
|
||||||
Load models, textures, sounds, and code.
|
Load models, textures, sounds, and code.
|
||||||
"""
|
"""
|
||||||
notify = directNotify.newCategory("Loader")
|
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
|
# special methods
|
||||||
def __init__(self, base):
|
def __init__(self, base):
|
||||||
self.base = base
|
self.base = base
|
||||||
self.loader = PandaLoader()
|
self.loader = PandaLoader()
|
||||||
|
self.pendingCallbacks = {}
|
||||||
|
|
||||||
|
self.hook = "async_loader_%s" % (Loader.loaderIndex)
|
||||||
|
Loader.loaderIndex += 1
|
||||||
|
self.accept(self.hook, self.__gotAsyncModel)
|
||||||
|
|
||||||
def destroy(self):
|
def destroy(self):
|
||||||
|
self.ignore(self.hook)
|
||||||
del self.base
|
del self.base
|
||||||
del self.loader
|
del self.loader
|
||||||
|
|
||||||
# model loading funcs
|
# model loading funcs
|
||||||
def loadModel(self, modelPath, fMakeNodeNamesUnique = 0,
|
def loadModel(self, modelPath, loaderOptions = None, noCache = None,
|
||||||
loaderOptions = 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))
|
assert Loader.notify.debug("Loading model: %s" % (modelPath))
|
||||||
if phaseChecker:
|
if phaseChecker:
|
||||||
phaseChecker(modelPath)
|
phaseChecker(modelPath)
|
||||||
if loaderOptions == None:
|
if loaderOptions == None:
|
||||||
loaderOptions = LoaderOptions()
|
loaderOptions = LoaderOptions()
|
||||||
|
else:
|
||||||
|
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)
|
node = self.loader.loadSync(Filename(modelPath), loaderOptions)
|
||||||
if (node != None):
|
if (node != None):
|
||||||
nodePath = NodePath(node)
|
nodePath = NodePath(node)
|
||||||
if fMakeNodeNamesUnique:
|
|
||||||
self.makeNodeNamesUnique(nodePath, 0)
|
|
||||||
else:
|
else:
|
||||||
nodePath = None
|
nodePath = None
|
||||||
return nodePath
|
|
||||||
|
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):
|
def loadModelOnce(self, modelPath):
|
||||||
"""
|
"""
|
||||||
@ -57,56 +162,19 @@ class Loader:
|
|||||||
then attempt to load it from disk. Return a nodepath to
|
then attempt to load it from disk. Return a nodepath to
|
||||||
the model if successful or None otherwise
|
the model if successful or None otherwise
|
||||||
"""
|
"""
|
||||||
assert Loader.notify.debug("Loading model once: %s" % (modelPath))
|
Loader.notify.debug("loader.loadModelOnce() is deprecated; use loader.loadModel() instead.")
|
||||||
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):
|
return self.loadModel(modelPath, noCache = False)
|
||||||
"""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
|
def loadModelCopy(self, modelPath, callback = None):
|
||||||
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):
|
|
||||||
"""loadModelCopy(self, string)
|
"""loadModelCopy(self, string)
|
||||||
Attempt to load a model from modelPool, if not present
|
Attempt to load a model from modelPool, if not present
|
||||||
then attempt to load it from disk. Return a nodepath to
|
then attempt to load it from disk. Return a nodepath to
|
||||||
a copy of the model if successful or None otherwise
|
a copy of the model if successful or None otherwise
|
||||||
"""
|
"""
|
||||||
assert Loader.notify.debug("Loading model copy: %s" % (modelPath))
|
Loader.notify.debug("loader.loadModelCopy() is deprecated; use loader.loadModel() instead.")
|
||||||
if phaseChecker:
|
|
||||||
phaseChecker(modelPath)
|
return self.loadModel(modelPath, noCache = False)
|
||||||
node = ModelPool.loadModel(modelPath)
|
|
||||||
if (node != None):
|
|
||||||
return NodePath(node.copySubgraph())
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def loadModelNode(self, modelPath):
|
def loadModelNode(self, modelPath):
|
||||||
"""
|
"""
|
||||||
@ -122,10 +190,13 @@ class Loader:
|
|||||||
|
|
||||||
However, if you're loading a font, see loadFont(), below.
|
However, if you're loading a font, see loadFont(), below.
|
||||||
"""
|
"""
|
||||||
assert Loader.notify.debug("Loading model once node: %s" % (modelPath))
|
Loader.notify.debug("loader.loadModelNode() is deprecated; use loader.loadModel() instead.")
|
||||||
if phaseChecker:
|
|
||||||
phaseChecker(modelPath)
|
model = self.loadModel(modelPath, noCache = False)
|
||||||
return ModelPool.loadModel(modelPath)
|
if model is not None:
|
||||||
|
model = model.node()
|
||||||
|
|
||||||
|
return model
|
||||||
|
|
||||||
def unloadModel(self, modelPath):
|
def unloadModel(self, modelPath):
|
||||||
"""
|
"""
|
||||||
@ -285,15 +356,14 @@ class Loader:
|
|||||||
Loader.notify.warning("Could not load music file %s." % name)
|
Loader.notify.warning("Could not load music file %s." % name)
|
||||||
return sound
|
return sound
|
||||||
|
|
||||||
|
## def makeNodeNamesUnique(self, nodePath, nodeCount):
|
||||||
def makeNodeNamesUnique(self, nodePath, nodeCount):
|
## if nodeCount == 0:
|
||||||
if nodeCount == 0:
|
## Loader.modelCount += 1
|
||||||
Loader.modelCount += 1
|
## nodePath.setName(nodePath.getName() +
|
||||||
nodePath.setName(nodePath.getName() +
|
## ('_%d_%d' % (Loader.modelCount, nodeCount)))
|
||||||
('_%d_%d' % (Loader.modelCount, nodeCount)))
|
## for i in range(nodePath.getNumChildren()):
|
||||||
for i in range(nodePath.getNumChildren()):
|
## nodeCount += 1
|
||||||
nodeCount += 1
|
## self.makeNodeNamesUnique(nodePath.getChild(i), nodeCount)
|
||||||
self.makeNodeNamesUnique(nodePath.getChild(i), nodeCount)
|
|
||||||
|
|
||||||
def loadShader (self, shaderPath):
|
def loadShader (self, shaderPath):
|
||||||
shader = ShaderPool.loadShader (shaderPath)
|
shader = ShaderPool.loadShader (shaderPath)
|
||||||
@ -305,3 +375,18 @@ class Loader:
|
|||||||
if (shaderPath != None):
|
if (shaderPath != None):
|
||||||
ShaderPool.releaseShader(shaderPath)
|
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