Merge branch 'master' of github.com:panda3d/panda3d into deploy-ng
This commit is contained in:
commit
c911ba8059
|
|
@ -116,8 +116,9 @@ class Interval(DirectObject):
|
|||
return self.currT
|
||||
|
||||
def start(self, startT = 0.0, endT = -1.0, playRate = 1.0):
|
||||
""" Starts the interval. Returns an awaitable. """
|
||||
self.setupPlay(startT, endT, playRate, 0)
|
||||
self.__spawnTask()
|
||||
return self.__spawnTask()
|
||||
|
||||
def loop(self, startT = 0.0, endT = -1.0, playRate = 1.0):
|
||||
self.setupPlay(startT, endT, playRate, 1)
|
||||
|
|
@ -427,6 +428,7 @@ class Interval(DirectObject):
|
|||
task = Task(self.__playTask)
|
||||
task.interval = self
|
||||
taskMgr.add(task, taskName)
|
||||
return task
|
||||
|
||||
def __removeTask(self):
|
||||
# Kill old task(s), including those from a similarly-named but
|
||||
|
|
|
|||
|
|
@ -50,10 +50,10 @@ class Loader(DirectObject):
|
|||
def cancel(self):
|
||||
"Cancels the request. Callback won't be called."
|
||||
if self._loader:
|
||||
self._loader = None
|
||||
for request in self.requests:
|
||||
self._loader.loader.remove(request)
|
||||
del self._loader._requests[request]
|
||||
self._loader = None
|
||||
self.requests = None
|
||||
self.requestList = None
|
||||
|
||||
|
|
@ -66,7 +66,9 @@ class Loader(DirectObject):
|
|||
return not self.requests
|
||||
|
||||
def result(self):
|
||||
assert not self.requests, "Result is not ready."
|
||||
"Returns the results, suspending the thread to wait if necessary."
|
||||
for r in list(self.requests):
|
||||
r.wait()
|
||||
if self.gotList:
|
||||
return self.objects
|
||||
else:
|
||||
|
|
@ -132,7 +134,8 @@ class Loader(DirectObject):
|
|||
# model loading funcs
|
||||
def loadModel(self, modelPath, loaderOptions = None, noCache = None,
|
||||
allowInstance = False, okMissing = None,
|
||||
callback = None, extraArgs = [], priority = None):
|
||||
callback = None, extraArgs = [], priority = None,
|
||||
blocking = None):
|
||||
"""
|
||||
Attempts to load a model or models from one or more relative
|
||||
pathnames. If the input modelPath is a string (a single model
|
||||
|
|
@ -169,10 +172,10 @@ class Loader(DirectObject):
|
|||
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. The return value will
|
||||
be an object that may later be passed to
|
||||
loader.cancelRequest() to cancel the asynchronous request. At
|
||||
some later point, when the requested model(s) have finished
|
||||
loading, the callback function will be invoked with the n
|
||||
be an object that can be used to check the status, cancel the
|
||||
request, or use it in an `await` expression. Unless callback
|
||||
is the special value True, when the requested model(s) have
|
||||
finished loading, it 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. If you use callback, you may also
|
||||
|
|
@ -224,7 +227,10 @@ class Loader(DirectObject):
|
|||
modelList = modelPath
|
||||
gotList = True
|
||||
|
||||
if callback is None:
|
||||
if blocking is None:
|
||||
blocking = callback is None
|
||||
|
||||
if blocking:
|
||||
# We got no callback, so it's a synchronous load.
|
||||
|
||||
result = []
|
||||
|
|
@ -362,7 +368,8 @@ class Loader(DirectObject):
|
|||
ModelPool.releaseModel(modelNode)
|
||||
|
||||
def saveModel(self, modelPath, node, loaderOptions = None,
|
||||
callback = None, extraArgs = [], priority = None):
|
||||
callback = None, extraArgs = [], priority = None,
|
||||
blocking = None):
|
||||
""" Saves the model (a NodePath or PandaNode) to the indicated
|
||||
filename path. Returns true on success, false on failure. If
|
||||
a callback is used, the model is saved asynchronously, and the
|
||||
|
|
@ -397,7 +404,10 @@ class Loader(DirectObject):
|
|||
# From here on, we deal with a list of (filename, node) pairs.
|
||||
modelList = list(zip(modelList, nodeList))
|
||||
|
||||
if callback is None:
|
||||
if blocking is None:
|
||||
blocking = callback is None
|
||||
|
||||
if blocking:
|
||||
# We got no callback, so it's a synchronous save.
|
||||
|
||||
result = []
|
||||
|
|
@ -1059,7 +1069,7 @@ class Loader(DirectObject):
|
|||
return
|
||||
|
||||
cb, i = self._requests[request]
|
||||
if cb.cancelled():
|
||||
if cb.cancelled() or request.cancelled():
|
||||
# Shouldn't be here.
|
||||
del self._requests[request]
|
||||
return
|
||||
|
|
@ -1068,7 +1078,11 @@ class Loader(DirectObject):
|
|||
if not cb.requests:
|
||||
del self._requests[request]
|
||||
|
||||
cb.gotObject(i, request.result() or None)
|
||||
result = request.result()
|
||||
if isinstance(result, PandaNode):
|
||||
result = NodePath(result)
|
||||
|
||||
cb.gotObject(i, result)
|
||||
|
||||
load_model = loadModel
|
||||
unload_model = unloadModel
|
||||
|
|
|
|||
|
|
@ -109,6 +109,12 @@ class Messenger:
|
|||
if record[0] <= 0:
|
||||
del self._id2object[id]
|
||||
|
||||
def future(self, event):
|
||||
""" Returns a future that is triggered by the given event name. This
|
||||
will function only once. """
|
||||
|
||||
return eventMgr.eventHandler.get_future(event)
|
||||
|
||||
def accept(self, event, object, method, extraArgs=[], persistent=1):
|
||||
""" accept(self, string, DirectObject, Function, List, Boolean)
|
||||
|
||||
|
|
@ -409,10 +415,14 @@ class Messenger:
|
|||
# Release the lock temporarily while we call the method.
|
||||
self.lock.release()
|
||||
try:
|
||||
method (*(extraArgs + sentArgs))
|
||||
result = method (*(extraArgs + sentArgs))
|
||||
finally:
|
||||
self.lock.acquire()
|
||||
|
||||
if hasattr(result, 'cr_await'):
|
||||
# It's a coroutine, so schedule it with the task manager.
|
||||
taskMgr.add(result)
|
||||
|
||||
def clear(self):
|
||||
"""
|
||||
Start fresh with a clear dict
|
||||
|
|
|
|||
|
|
@ -968,7 +968,9 @@ class ShowBase(DirectObject.DirectObject):
|
|||
if isinstance(self.win, GraphicsWindow):
|
||||
self.setupMouse(self.win)
|
||||
self.makeCamera2d(self.win)
|
||||
self.makeCamera2dp(self.win)
|
||||
|
||||
if self.wantRender2dp:
|
||||
self.makeCamera2dp(self.win)
|
||||
|
||||
if oldLens != None:
|
||||
# Restore the previous lens properties.
|
||||
|
|
@ -1258,9 +1260,9 @@ class ShowBase(DirectObject.DirectObject):
|
|||
if win == None:
|
||||
win = self.win
|
||||
|
||||
if win != None and win.hasSize() and win.getSbsLeftYSize() != 0:
|
||||
if win != None and win.getSideBySideStereo() and \
|
||||
win.hasSize() and win.getSbsLeftYSize() != 0:
|
||||
aspectRatio = float(win.getSbsLeftXSize()) / float(win.getSbsLeftYSize())
|
||||
|
||||
else:
|
||||
if win == None or not hasattr(win, "getRequestedProperties"):
|
||||
props = WindowProperties.getDefault()
|
||||
|
|
@ -1295,8 +1297,7 @@ class ShowBase(DirectObject.DirectObject):
|
|||
if not props.hasSize():
|
||||
props = WindowProperties.getDefault()
|
||||
|
||||
if props.hasSize():
|
||||
return props.getXSize(), props.getYSize()
|
||||
return props.getXSize(), props.getYSize()
|
||||
|
||||
def makeCamera(self, win, sort = 0, scene = None,
|
||||
displayRegion = (0, 1, 0, 1), stereo = None,
|
||||
|
|
@ -1560,9 +1561,11 @@ class ShowBase(DirectObject.DirectObject):
|
|||
|
||||
# Tell the gui system about our new mouse watcher.
|
||||
self.aspect2d.node().setMouseWatcher(mw.node())
|
||||
self.aspect2dp.node().setMouseWatcher(mw.node())
|
||||
self.pixel2d.node().setMouseWatcher(mw.node())
|
||||
self.pixel2dp.node().setMouseWatcher(mw.node())
|
||||
if self.wantRender2dp:
|
||||
self.aspect2dp.node().setMouseWatcher(mw.node())
|
||||
self.pixel2dp.node().setMouseWatcher(mw.node())
|
||||
|
||||
mw.node().addRegion(PGMouseWatcherBackground())
|
||||
|
||||
return self.buttonThrowers[0]
|
||||
|
|
@ -2723,13 +2726,15 @@ class ShowBase(DirectObject.DirectObject):
|
|||
# changed and update the camera lenses and aspect2d parameters
|
||||
self.adjustWindowAspectRatio(self.getAspectRatio())
|
||||
|
||||
# Temporary hasattr for old Pandas
|
||||
if not hasattr(win, 'getSbsLeftXSize'):
|
||||
self.pixel2d.setScale(2.0 / win.getXSize(), 1.0, 2.0 / win.getYSize())
|
||||
self.pixel2dp.setScale(2.0 / win.getXSize(), 1.0, 2.0 / win.getYSize())
|
||||
else:
|
||||
if win.getSideBySideStereo() and win.hasSize() and win.getSbsLeftYSize() != 0:
|
||||
self.pixel2d.setScale(2.0 / win.getSbsLeftXSize(), 1.0, 2.0 / win.getSbsLeftYSize())
|
||||
self.pixel2dp.setScale(2.0 / win.getSbsLeftXSize(), 1.0, 2.0 / win.getSbsLeftYSize())
|
||||
else:
|
||||
xsize, ysize = self.getSize()
|
||||
if xsize > 0 and ysize > 0:
|
||||
self.pixel2d.setScale(2.0 / xsize, 1.0, 2.0 / ysize)
|
||||
if self.wantRender2dp:
|
||||
self.pixel2dp.setScale(2.0 / xsize, 1.0, 2.0 / ysize)
|
||||
|
||||
def adjustWindowAspectRatio(self, aspectRatio):
|
||||
""" This function is normally called internally by
|
||||
|
|
@ -2753,11 +2758,12 @@ class ShowBase(DirectObject.DirectObject):
|
|||
self.a2dLeft = -1
|
||||
self.a2dRight = 1.0
|
||||
# Don't forget 2dp
|
||||
self.aspect2dp.setScale(1.0, aspectRatio, aspectRatio)
|
||||
self.a2dpTop = 1.0 / aspectRatio
|
||||
self.a2dpBottom = - 1.0 / aspectRatio
|
||||
self.a2dpLeft = -1
|
||||
self.a2dpRight = 1.0
|
||||
if self.wantRender2dp:
|
||||
self.aspect2dp.setScale(1.0, aspectRatio, aspectRatio)
|
||||
self.a2dpTop = 1.0 / aspectRatio
|
||||
self.a2dpBottom = - 1.0 / aspectRatio
|
||||
self.a2dpLeft = -1
|
||||
self.a2dpRight = 1.0
|
||||
|
||||
else:
|
||||
# If the window is WIDE, lets expand the left and right
|
||||
|
|
@ -2767,41 +2773,43 @@ class ShowBase(DirectObject.DirectObject):
|
|||
self.a2dLeft = -aspectRatio
|
||||
self.a2dRight = aspectRatio
|
||||
# Don't forget 2dp
|
||||
self.aspect2dp.setScale(1.0 / aspectRatio, 1.0, 1.0)
|
||||
self.a2dpTop = 1.0
|
||||
self.a2dpBottom = -1.0
|
||||
self.a2dpLeft = -aspectRatio
|
||||
self.a2dpRight = aspectRatio
|
||||
if self.wantRender2dp:
|
||||
self.aspect2dp.setScale(1.0 / aspectRatio, 1.0, 1.0)
|
||||
self.a2dpTop = 1.0
|
||||
self.a2dpBottom = -1.0
|
||||
self.a2dpLeft = -aspectRatio
|
||||
self.a2dpRight = aspectRatio
|
||||
|
||||
# Reposition the aspect2d marker nodes
|
||||
self.a2dTopCenter.setPos(0, self.a2dTop, self.a2dTop)
|
||||
self.a2dBottomCenter.setPos(0, self.a2dBottom, self.a2dBottom)
|
||||
self.a2dTopCenter.setPos(0, 0, self.a2dTop)
|
||||
self.a2dTopCenterNs.setPos(0, 0, self.a2dTop)
|
||||
self.a2dBottomCenter.setPos(0, 0, self.a2dBottom)
|
||||
self.a2dBottomCenterNs.setPos(0, 0, self.a2dBottom)
|
||||
self.a2dLeftCenter.setPos(self.a2dLeft, 0, 0)
|
||||
self.a2dRightCenter.setPos(self.a2dRight, 0, 0)
|
||||
self.a2dTopLeft.setPos(self.a2dLeft, self.a2dTop, self.a2dTop)
|
||||
self.a2dTopRight.setPos(self.a2dRight, self.a2dTop, self.a2dTop)
|
||||
self.a2dBottomLeft.setPos(self.a2dLeft, self.a2dBottom, self.a2dBottom)
|
||||
self.a2dBottomRight.setPos(self.a2dRight, self.a2dBottom, self.a2dBottom)
|
||||
|
||||
# Reposition the aspect2d marker nodes
|
||||
self.a2dTopCenterNs.setPos(0, self.a2dTop, self.a2dTop)
|
||||
self.a2dBottomCenterNs.setPos(0, self.a2dBottom, self.a2dBottom)
|
||||
self.a2dLeftCenterNs.setPos(self.a2dLeft, 0, 0)
|
||||
self.a2dRightCenter.setPos(self.a2dRight, 0, 0)
|
||||
self.a2dRightCenterNs.setPos(self.a2dRight, 0, 0)
|
||||
self.a2dTopLeftNs.setPos(self.a2dLeft, self.a2dTop, self.a2dTop)
|
||||
self.a2dTopRightNs.setPos(self.a2dRight, self.a2dTop, self.a2dTop)
|
||||
self.a2dBottomLeftNs.setPos(self.a2dLeft, self.a2dBottom, self.a2dBottom)
|
||||
self.a2dBottomRightNs.setPos(self.a2dRight, self.a2dBottom, self.a2dBottom)
|
||||
|
||||
self.a2dTopLeft.setPos(self.a2dLeft, 0, self.a2dTop)
|
||||
self.a2dTopLeftNs.setPos(self.a2dLeft, 0, self.a2dTop)
|
||||
self.a2dTopRight.setPos(self.a2dRight, 0, self.a2dTop)
|
||||
self.a2dTopRightNs.setPos(self.a2dRight, 0, self.a2dTop)
|
||||
self.a2dBottomLeft.setPos(self.a2dLeft, 0, self.a2dBottom)
|
||||
self.a2dBottomLeftNs.setPos(self.a2dLeft, 0, self.a2dBottom)
|
||||
self.a2dBottomRight.setPos(self.a2dRight, 0, self.a2dBottom)
|
||||
self.a2dBottomRightNs.setPos(self.a2dRight, 0, self.a2dBottom)
|
||||
|
||||
# Reposition the aspect2dp marker nodes
|
||||
self.a2dpTopCenter.setPos(0, self.a2dpTop, self.a2dpTop)
|
||||
self.a2dpBottomCenter.setPos(0, self.a2dpBottom, self.a2dpBottom)
|
||||
self.a2dpLeftCenter.setPos(self.a2dpLeft, 0, 0)
|
||||
self.a2dpRightCenter.setPos(self.a2dpRight, 0, 0)
|
||||
self.a2dpTopLeft.setPos(self.a2dpLeft, self.a2dpTop, self.a2dpTop)
|
||||
self.a2dpTopRight.setPos(self.a2dpRight, self.a2dpTop, self.a2dpTop)
|
||||
self.a2dpBottomLeft.setPos(self.a2dpLeft, self.a2dpBottom, self.a2dpBottom)
|
||||
self.a2dpBottomRight.setPos(self.a2dpRight, self.a2dpBottom, self.a2dpBottom)
|
||||
if self.wantRender2dp:
|
||||
self.a2dpTopCenter.setPos(0, 0, self.a2dpTop)
|
||||
self.a2dpBottomCenter.setPos(0, 0, self.a2dpBottom)
|
||||
self.a2dpLeftCenter.setPos(self.a2dpLeft, 0, 0)
|
||||
self.a2dpRightCenter.setPos(self.a2dpRight, 0, 0)
|
||||
|
||||
self.a2dpTopLeft.setPos(self.a2dpLeft, 0, self.a2dpTop)
|
||||
self.a2dpTopRight.setPos(self.a2dpRight, 0, self.a2dpTop)
|
||||
self.a2dpBottomLeft.setPos(self.a2dpLeft, 0, self.a2dpBottom)
|
||||
self.a2dpBottomRight.setPos(self.a2dpRight, 0, self.a2dpBottom)
|
||||
|
||||
# If anybody needs to update their GUI, put a callback on this event
|
||||
messenger.send("aspectRatioChanged")
|
||||
|
|
|
|||
|
|
@ -74,7 +74,9 @@ Task = PythonTask
|
|||
# Copy the module-level enums above into the class level. This funny
|
||||
# syntax is necessary because it's a C++-wrapped extension type, not a
|
||||
# true Python class.
|
||||
Task.DtoolClassDict['done'] = done
|
||||
# We can't override 'done', which is already a known method. We have a
|
||||
# special check in PythonTask for when the method is being returned.
|
||||
#Task.DtoolClassDict['done'] = done
|
||||
Task.DtoolClassDict['cont'] = cont
|
||||
Task.DtoolClassDict['again'] = again
|
||||
Task.DtoolClassDict['pickup'] = pickup
|
||||
|
|
@ -84,6 +86,8 @@ Task.DtoolClassDict['exit'] = exit
|
|||
pause = AsyncTaskPause
|
||||
Task.DtoolClassDict['pause'] = staticmethod(pause)
|
||||
|
||||
gather = Task.gather
|
||||
|
||||
def sequence(*taskList):
|
||||
seq = AsyncTaskSequence('sequence')
|
||||
for task in taskList:
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ __init__(PyObject *path) {
|
|||
|
||||
if (Py_TYPE(path) == &Dtool_Filename._PyType) {
|
||||
// Copy constructor.
|
||||
(*_this) = *((Filename *)((Dtool_PyInstDef *)path)->_ptr_to_object);
|
||||
*_this = *(Filename *)DtoolInstance_VOID_PTR(path);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -797,6 +797,7 @@ setup_properties(const InterrogateFunction &ifunc, InterfaceMaker *interface_mak
|
|||
(_parameters[first_param + 1]._name == "kwargs" ||
|
||||
_parameters[first_param + 1]._name == "kwds")) {
|
||||
_flags |= F_explicit_args;
|
||||
_args_type = InterfaceMaker::AT_keyword_args;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -909,6 +910,13 @@ setup_properties(const InterrogateFunction &ifunc, InterfaceMaker *interface_mak
|
|||
break;
|
||||
}
|
||||
}
|
||||
} else if (_args_type == InterfaceMaker::AT_single_arg) {
|
||||
// If it takes an argument named "args", we are directly passing the
|
||||
// "args" tuple to the function.
|
||||
if (_parameters[first_param]._name == "args") {
|
||||
_flags |= F_explicit_args;
|
||||
_args_type = InterfaceMaker::AT_varargs;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -1094,14 +1094,14 @@ write_class_details(ostream &out, Object *obj) {
|
|||
// Write support methods to cast from and to pointers of this type.
|
||||
{
|
||||
out << "static void *Dtool_UpcastInterface_" << ClassName << "(PyObject *self, Dtool_PyTypedObject *requested_type) {\n";
|
||||
out << " Dtool_PyTypedObject *SelfType = ((Dtool_PyInstDef *)self)->_My_Type;\n";
|
||||
out << " if (SelfType != Dtool_Ptr_" << ClassName << ") {\n";
|
||||
out << " Dtool_PyTypedObject *type = DtoolInstance_TYPE(self);\n";
|
||||
out << " if (type != &Dtool_" << ClassName << ") {\n";
|
||||
out << " printf(\"" << ClassName << " ** Bad Source Type-- Requesting Conversion from %s to %s\\n\", Py_TYPE(self)->tp_name, requested_type->_PyType.tp_name); fflush(NULL);\n";;
|
||||
out << " return NULL;\n";
|
||||
out << " }\n";
|
||||
out << "\n";
|
||||
out << " " << cClassName << " *local_this = (" << cClassName << " *)((Dtool_PyInstDef *)self)->_ptr_to_object;\n";
|
||||
out << " if (requested_type == Dtool_Ptr_" << ClassName << ") {\n";
|
||||
out << " " << cClassName << " *local_this = (" << cClassName << " *)DtoolInstance_VOID_PTR(self);\n";
|
||||
out << " if (requested_type == &Dtool_" << ClassName << ") {\n";
|
||||
out << " return local_this;\n";
|
||||
out << " }\n";
|
||||
|
||||
|
|
@ -2220,13 +2220,13 @@ write_module_class(ostream &out, Object *obj) {
|
|||
// provide a writable buffer or a readonly buffer.
|
||||
const string const_this = "(const " + cClassName + " *)local_this";
|
||||
if (remap_const != NULL && remap_nonconst != NULL) {
|
||||
out << " if (!((Dtool_PyInstDef *)self)->_is_const) {\n";
|
||||
out << " if (!DtoolInstance_IS_CONST(self)) {\n";
|
||||
out << " return " << remap_nonconst->call_function(out, 4, false, "local_this", params_nonconst) << ";\n";
|
||||
out << " } else {\n";
|
||||
out << " return " << remap_const->call_function(out, 4, false, const_this, params_const) << ";\n";
|
||||
out << " }\n";
|
||||
} else if (remap_nonconst != NULL) {
|
||||
out << " if (!((Dtool_PyInstDef *)self)->_is_const) {\n";
|
||||
out << " if (!DtoolInstance_IS_CONST(self)) {\n";
|
||||
out << " return " << remap_nonconst->call_function(out, 4, false, "local_this", params_nonconst) << ";\n";
|
||||
out << " } else {\n";
|
||||
out << " Dtool_Raise_TypeError(\"Cannot call " << ClassName << ".__getbuffer__() on a const object.\");\n";
|
||||
|
|
@ -2285,7 +2285,7 @@ write_module_class(ostream &out, Object *obj) {
|
|||
string return_expr;
|
||||
const string const_this = "(const " + cClassName + " *)local_this";
|
||||
if (remap_const != NULL && remap_nonconst != NULL) {
|
||||
out << " if (!((Dtool_PyInstDef *)self)->_is_const) {\n";
|
||||
out << " if (!DtoolInstance_IS_CONST(self)) {\n";
|
||||
return_expr = remap_nonconst->call_function(out, 4, false, "local_this", params_nonconst);
|
||||
if (!return_expr.empty()) {
|
||||
out << " " << return_expr << ";\n";
|
||||
|
|
@ -3078,7 +3078,7 @@ write_module_class(ostream &out, Object *obj) {
|
|||
|
||||
out << " Dtool_" << ClassName << "._PyType.tp_bases = PyTuple_Pack(" << bases.size() << baseargs << ");\n";
|
||||
} else {
|
||||
out << " Dtool_" << ClassName << "._PyType.tp_base = (PyTypeObject *)Dtool_Ptr_DTOOL_SUPER_BASE;\n";
|
||||
out << " Dtool_" << ClassName << "._PyType.tp_base = (PyTypeObject *)&Dtool_DTOOL_SUPER_BASE;\n";
|
||||
}
|
||||
|
||||
int num_nested = obj->_itype.number_of_nested_types();
|
||||
|
|
@ -3515,8 +3515,9 @@ write_function_for_name(ostream &out, Object *obj,
|
|||
out << " if (!Dtool_Call_ExtractThisPointer_NonConst(self, Dtool_" << ClassName << ", "
|
||||
<< "(void **)&local_this, \"" << classNameFromCppName(cClassName, false)
|
||||
<< "." << methodNameFromCppName(remap, cClassName, false) << "\")) {\n";
|
||||
|
||||
} else {
|
||||
out << " if (!Dtool_Call_ExtractThisPointer(self, Dtool_" << ClassName << ", (void **)&local_this)) {\n";
|
||||
out << " if (!DtoolInstance_GetPointer(self, local_this, Dtool_" << ClassName << ")) {\n";
|
||||
}
|
||||
|
||||
error_return(out, 4, return_flags);
|
||||
|
|
@ -3616,7 +3617,7 @@ write_function_for_name(ostream &out, Object *obj,
|
|||
if (strip_keyword_args) {
|
||||
// None of the remaps take any keyword arguments, so let's check that
|
||||
// we take none. This saves some checks later on.
|
||||
indent(out, 4) << "if (kwds == NULL || ((PyDictObject *)kwds)->ma_used == 0) {\n";
|
||||
indent(out, 4) << "if (kwds == NULL || PyDict_GET_SIZE(kwds) == 0) {\n";
|
||||
if (min_args == 1 && min_args == 1) {
|
||||
indent(out, 4) << " PyObject *arg = PyTuple_GET_ITEM(args, 0);\n";
|
||||
write_function_forset(out, mii->second, min_args, max_args, expected_params, 6,
|
||||
|
|
@ -3890,11 +3891,10 @@ write_coerce_constructor(ostream &out, Object *obj, bool is_const) {
|
|||
// Note: this relies on the PT() being initialized to NULL. This is
|
||||
// currently the case in all invocations, but this may not be true in the
|
||||
// future.
|
||||
out << " DTOOL_Call_ExtractThisPointerForType(args, &Dtool_" << ClassName << ", (void**)&coerced.cheat());\n";
|
||||
out << " if (coerced != NULL) {\n";
|
||||
out << " if (DtoolInstance_GetPointer(args, coerced.cheat(), Dtool_" << ClassName << ")) {\n";
|
||||
out << " // The argument is already of matching type, no need to coerce.\n";
|
||||
if (!is_const) {
|
||||
out << " if (!((Dtool_PyInstDef *)args)->_is_const) {\n";
|
||||
out << " if (!DtoolInstance_IS_CONST(args)) {\n";
|
||||
out << " // A non-const instance is required, which this is.\n";
|
||||
out << " coerced->ref();\n";
|
||||
out << " return true;\n";
|
||||
|
|
@ -3909,9 +3909,8 @@ write_coerce_constructor(ostream &out, Object *obj, bool is_const) {
|
|||
out << cClassName << " *Dtool_Coerce_" << ClassName << "(PyObject *args, " << cClassName << " &coerced) {\n";
|
||||
|
||||
out << " " << cClassName << " *local_this;\n";
|
||||
out << " DTOOL_Call_ExtractThisPointerForType(args, &Dtool_" << ClassName << ", (void**)&local_this);\n";
|
||||
out << " if (local_this != NULL) {\n";
|
||||
out << " if (((Dtool_PyInstDef *)args)->_is_const) {\n";
|
||||
out << " if (DtoolInstance_GetPointer(args, local_this, Dtool_" << ClassName << ")) {\n";
|
||||
out << " if (DtoolInstance_IS_CONST(args)) {\n";
|
||||
out << " // This is a const object. Make a copy.\n";
|
||||
out << " coerced = *(const " << cClassName << " *)local_this;\n";
|
||||
out << " return &coerced;\n";
|
||||
|
|
@ -4340,7 +4339,7 @@ write_function_forset(ostream &out,
|
|||
if (all_nonconst) {
|
||||
// Yes, they do. Check that the parameter has the required constness.
|
||||
indent(out, indent_level)
|
||||
<< "if (!((Dtool_PyInstDef *)self)->_is_const) {\n";
|
||||
<< "if (!DtoolInstance_IS_CONST(self)) {\n";
|
||||
indent_level += 2;
|
||||
verify_const = false;
|
||||
}
|
||||
|
|
@ -4406,7 +4405,7 @@ write_function_forset(ostream &out,
|
|||
if (verify_const && (remap->_has_this && !remap->_const_method)) {
|
||||
// If it's a non-const method, we only allow a non-const this.
|
||||
indent(out, indent_level)
|
||||
<< "if (!((Dtool_PyInstDef *)self)->_is_const) {\n";
|
||||
<< "if (!DtoolInstance_IS_CONST(self)) {\n";
|
||||
} else {
|
||||
indent(out, indent_level)
|
||||
<< "{\n";
|
||||
|
|
@ -4441,7 +4440,7 @@ write_function_forset(ostream &out,
|
|||
|
||||
if (verify_const && (remap->_has_this && !remap->_const_method)) {
|
||||
indent(out, indent_level)
|
||||
<< "if (!((Dtool_PyInstDef *)self)->_is_const) {\n";
|
||||
<< "if (!DtoolInstance_IS_CONST(self)) {\n";
|
||||
} else {
|
||||
indent(out, indent_level)
|
||||
<< "{\n";
|
||||
|
|
@ -5525,14 +5524,13 @@ write_function_instance(ostream &out, FunctionRemap *remap,
|
|||
// This function does the same thing in this case and is slightly
|
||||
// simpler. But maybe we should just reorganize these functions
|
||||
// entirely?
|
||||
extra_convert << ";\n";
|
||||
if (is_optional) {
|
||||
extra_convert << " ";
|
||||
}
|
||||
extra_convert
|
||||
<< "DTOOL_Call_ExtractThisPointerForType(" << param_name
|
||||
<< ", Dtool_Ptr_" << make_safe_name(class_name)
|
||||
<< ", (void **)&" << param_name << "_this);\n";
|
||||
extra_convert << " = NULL;\n";
|
||||
int indent_level = is_optional ? 2 : 0;
|
||||
indent(extra_convert, indent_level)
|
||||
<< "DtoolInstance_GetPointer(" << param_name
|
||||
<< ", " << param_name << "_this"
|
||||
<< ", *Dtool_Ptr_" << make_safe_name(class_name)
|
||||
<< ");\n";
|
||||
} else {
|
||||
extra_convert << boolalpha
|
||||
<< " = (" << class_name << " *)"
|
||||
|
|
@ -6796,7 +6794,7 @@ write_getset(ostream &out, Object *obj, Property *property) {
|
|||
out << " if (wrap != NULL) {\n"
|
||||
" wrap->_getitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Mapping_Getitem;\n";
|
||||
if (!property->_setter_remaps.empty()) {
|
||||
out << " if (!((Dtool_PyInstDef *)self)->_is_const) {\n";
|
||||
out << " if (!DtoolInstance_IS_CONST(self)) {\n";
|
||||
out << " wrap->_setitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Mapping_Setitem;\n";
|
||||
out << " }\n";
|
||||
}
|
||||
|
|
@ -6828,7 +6826,7 @@ write_getset(ostream &out, Object *obj, Property *property) {
|
|||
" wrap->_len_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Len;\n"
|
||||
" wrap->_getitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Sequence_Getitem;\n";
|
||||
if (!property->_setter_remaps.empty()) {
|
||||
out << " if (!((Dtool_PyInstDef *)self)->_is_const) {\n";
|
||||
out << " if (!DtoolInstance_IS_CONST(self)) {\n";
|
||||
out << " wrap->_setitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Sequence_Setitem;\n";
|
||||
if (property->_inserter != nullptr) {
|
||||
out << " wrap->_insert_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Sequence_insert;\n";
|
||||
|
|
|
|||
|
|
@ -5,4 +5,5 @@
|
|||
#include "interrogate_interface.cxx"
|
||||
#include "interrogate_request.cxx"
|
||||
#include "py_panda.cxx"
|
||||
#include "py_compat.cxx"
|
||||
#include "py_wrappers.cxx"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file py_compat.cxx
|
||||
* @author rdb
|
||||
* @date 2017-12-03
|
||||
*/
|
||||
|
||||
#include "py_compat.h"
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
|
||||
PyTupleObject Dtool_EmptyTuple = {PyVarObject_HEAD_INIT(nullptr, 0)};
|
||||
|
||||
#if PY_MAJOR_VERSION < 3
|
||||
/**
|
||||
* Given a long or int, returns a size_t, or raises an OverflowError if it is
|
||||
* out of range.
|
||||
*/
|
||||
size_t PyLongOrInt_AsSize_t(PyObject *vv) {
|
||||
if (PyInt_Check(vv)) {
|
||||
long value = PyInt_AS_LONG(vv);
|
||||
if (value < 0) {
|
||||
PyErr_SetString(PyExc_OverflowError,
|
||||
"can't convert negative value to size_t");
|
||||
return (size_t)-1;
|
||||
}
|
||||
return (size_t)value;
|
||||
}
|
||||
|
||||
if (!PyLong_Check(vv)) {
|
||||
Dtool_Raise_TypeError("a long or int was expected");
|
||||
return (size_t)-1;
|
||||
}
|
||||
|
||||
size_t bytes;
|
||||
int one = 1;
|
||||
int res = _PyLong_AsByteArray((PyLongObject *)vv, (unsigned char *)&bytes,
|
||||
SIZEOF_SIZE_T, (int)*(unsigned char*)&one, 0);
|
||||
|
||||
if (res < 0) {
|
||||
return (size_t)res;
|
||||
} else {
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // HAVE_PYTHON
|
||||
|
|
@ -0,0 +1,175 @@
|
|||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file py_compat.h
|
||||
* @author rdb
|
||||
* @date 2017-12-02
|
||||
*/
|
||||
|
||||
#ifndef PY_COMPAT_H
|
||||
#define PY_COMPAT_H
|
||||
|
||||
#include "dtoolbase.h"
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
|
||||
// The contents of this file were originally part of py_panda.h. It
|
||||
// specifically contains polyfills that are required to maintain compatibility
|
||||
// with Python 2 and older versions of Python 3.
|
||||
|
||||
// These compatibility hacks are sorted by Python version that removes the
|
||||
// need for the respective hack.
|
||||
|
||||
#ifdef _POSIX_C_SOURCE
|
||||
# undef _POSIX_C_SOURCE
|
||||
#endif
|
||||
|
||||
#ifdef _XOPEN_SOURCE
|
||||
# undef _XOPEN_SOURCE
|
||||
#endif
|
||||
|
||||
// See PEP 353
|
||||
#define PY_SSIZE_T_CLEAN 1
|
||||
|
||||
#include "Python.h"
|
||||
|
||||
/* Python 2.4 */
|
||||
|
||||
// 2.4 macros which aren't available in 2.3
|
||||
#ifndef Py_RETURN_NONE
|
||||
# define Py_RETURN_NONE return Py_INCREF(Py_None), Py_None
|
||||
#endif
|
||||
|
||||
#ifndef Py_RETURN_TRUE
|
||||
# define Py_RETURN_TRUE return Py_INCREF(Py_True), Py_True
|
||||
#endif
|
||||
|
||||
#ifndef Py_RETURN_FALSE
|
||||
# define Py_RETURN_FALSE return Py_INCREF(Py_False), Py_False
|
||||
#endif
|
||||
|
||||
/* Python 2.5 */
|
||||
|
||||
// Prior to Python 2.5, we didn't have Py_ssize_t.
|
||||
#if PY_VERSION_HEX < 0x02050000
|
||||
typedef int Py_ssize_t;
|
||||
# define PyInt_FromSsize_t PyInt_FromLong
|
||||
# define PyInt_AsSsize_t PyInt_AsLong
|
||||
#endif
|
||||
|
||||
/* Python 2.6 */
|
||||
|
||||
#ifndef Py_TYPE
|
||||
# define Py_TYPE(ob) (((PyObject*)(ob))->ob_type)
|
||||
#endif
|
||||
|
||||
/* Python 2.7, 3.1 */
|
||||
|
||||
#ifndef PyVarObject_HEAD_INIT
|
||||
#define PyVarObject_HEAD_INIT(type, size) \
|
||||
PyObject_HEAD_INIT(type) size,
|
||||
#endif
|
||||
|
||||
/* Python 2.7, 3.2 */
|
||||
|
||||
#if PY_VERSION_HEX < 0x03020000
|
||||
# define PyErr_NewExceptionWithDoc(name, doc, base, dict) \
|
||||
PyErr_NewException(name, base, dict)
|
||||
#endif
|
||||
|
||||
/* Python 3.0 */
|
||||
|
||||
// Always on in Python 3
|
||||
#ifndef Py_TPFLAGS_CHECKTYPES
|
||||
# define Py_TPFLAGS_CHECKTYPES 0
|
||||
#endif
|
||||
|
||||
// Macros for writing code that will compile in both versions.
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
# define nb_nonzero nb_bool
|
||||
# define nb_divide nb_true_divide
|
||||
# define nb_inplace_divide nb_inplace_true_divide
|
||||
|
||||
# define PyLongOrInt_Check(x) PyLong_Check(x)
|
||||
# define PyLongOrInt_AS_LONG PyLong_AS_LONG
|
||||
# define PyInt_Check PyLong_Check
|
||||
# define PyInt_AsLong PyLong_AsLong
|
||||
# define PyInt_AS_LONG PyLong_AS_LONG
|
||||
# define PyLongOrInt_AsSize_t PyLong_AsSize_t
|
||||
#else
|
||||
# define PyLongOrInt_Check(x) (PyInt_Check(x) || PyLong_Check(x))
|
||||
// PyInt_FromSize_t automatically picks the right type.
|
||||
# define PyLongOrInt_AS_LONG PyInt_AsLong
|
||||
|
||||
EXPCL_INTERROGATEDB size_t PyLongOrInt_AsSize_t(PyObject *);
|
||||
#endif
|
||||
|
||||
// Which character to use in PyArg_ParseTuple et al for a byte string.
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
# define FMTCHAR_BYTES "y"
|
||||
#else
|
||||
# define FMTCHAR_BYTES "s"
|
||||
#endif
|
||||
|
||||
/* Python 3.2 */
|
||||
|
||||
#if PY_VERSION_HEX < 0x03020000
|
||||
typedef long Py_hash_t;
|
||||
#endif
|
||||
|
||||
/* Python 3.3 */
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
// Python 3 versions before 3.3.3 defined this incorrectly.
|
||||
# undef _PyErr_OCCURRED
|
||||
# define _PyErr_OCCURRED() (PyThreadState_GET()->curexc_type)
|
||||
|
||||
// Python versions before 3.3 did not define this.
|
||||
# if PY_VERSION_HEX < 0x03030000
|
||||
# define PyUnicode_AsUTF8 _PyUnicode_AsString
|
||||
# define PyUnicode_AsUTF8AndSize _PyUnicode_AsStringAndSize
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/* Python 3.6 */
|
||||
|
||||
// Used to implement _PyObject_CallNoArg
|
||||
extern EXPCL_INTERROGATEDB PyTupleObject Dtool_EmptyTuple;
|
||||
|
||||
#ifndef _PyObject_CallNoArg
|
||||
# define _PyObject_CallNoArg(func) PyObject_Call((func), (PyObject *)&Dtool_EmptyTuple, nullptr)
|
||||
#endif
|
||||
|
||||
// Python versions before 3.6 didn't require longlong support to be enabled.
|
||||
#ifndef HAVE_LONG_LONG
|
||||
# define PyLong_FromLongLong(x) PyLong_FromLong((long) (x))
|
||||
# define PyLong_FromUnsignedLongLong(x) PyLong_FromUnsignedLong((unsigned long) (x))
|
||||
# define PyLong_AsLongLong(x) PyLong_AsLong(x)
|
||||
# define PyLong_AsUnsignedLongLong(x) PyLong_AsUnsignedLong(x)
|
||||
# define PyLong_AsUnsignedLongLongMask(x) PyLong_AsUnsignedLongMask(x)
|
||||
# define PyLong_AsLongLongAndOverflow(x) PyLong_AsLongAndOverflow(x)
|
||||
#endif
|
||||
|
||||
/* Python 3.7 */
|
||||
|
||||
#ifndef PyDict_GET_SIZE
|
||||
# define PyDict_GET_SIZE(mp) (((PyDictObject *)mp)->ma_used)
|
||||
#endif
|
||||
|
||||
/* Other Python implementations */
|
||||
|
||||
// _PyErr_OCCURRED is an undocumented macro version of PyErr_Occurred.
|
||||
// Some implementations of the CPython API (e.g. PyPy's cpyext) do not define
|
||||
// it, so in these cases we just silently fall back to PyErr_Occurred.
|
||||
#ifndef _PyErr_OCCURRED
|
||||
# define _PyErr_OCCURRED() PyErr_Occurred()
|
||||
#endif
|
||||
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
#endif // PY_COMPAT_H
|
||||
|
|
@ -11,20 +11,54 @@
|
|||
* @date 2016-06-06
|
||||
*/
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define _IS_FINAL(T) (__is_sealed(T))
|
||||
#elif defined(__GNUC__)
|
||||
#define _IS_FINAL(T) (__is_final(T))
|
||||
#else
|
||||
#define _IS_FINAL(T) (0)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Template function that can be used to extract any TypedObject pointer from
|
||||
* a wrapped Python object.
|
||||
*/
|
||||
template<class T> INLINE bool
|
||||
DTOOL_Call_ExtractThisPointer(PyObject *self, T *&into) {
|
||||
if (DtoolCanThisBeAPandaInstance(self)) {
|
||||
DtoolInstance_GetPointer(PyObject *self, T *&into) {
|
||||
if (DtoolInstance_Check(self)) {
|
||||
Dtool_PyTypedObject *target_class = Dtool_RuntimeTypeDtoolType(get_type_handle(T).get_index());
|
||||
if (target_class != NULL) {
|
||||
into = (T*) ((Dtool_PyInstDef *)self)->_My_Type->_Dtool_UpcastInterface(self, target_class);
|
||||
return (into != NULL);
|
||||
if (target_class != nullptr) {
|
||||
if (_IS_FINAL(T)) {
|
||||
if (DtoolInstance_TYPE(self) == target_class) {
|
||||
into = (T *)DtoolInstance_VOID_PTR(self);
|
||||
}
|
||||
} else {
|
||||
into = (T *)DtoolInstance_UPCAST(self, *target_class);
|
||||
}
|
||||
return (into != nullptr);
|
||||
}
|
||||
}
|
||||
into = NULL;
|
||||
into = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Template function that can be used to extract any TypedObject pointer from
|
||||
* a wrapped Python object. In this case, the Dtool_PyTypedObject is known.
|
||||
*/
|
||||
template<class T> INLINE bool
|
||||
DtoolInstance_GetPointer(PyObject *self, T *&into, Dtool_PyTypedObject &target_class) {
|
||||
if (DtoolInstance_Check(self)) {
|
||||
if (_IS_FINAL(T)) {
|
||||
if (DtoolInstance_TYPE(self) == &target_class) {
|
||||
into = (T *)DtoolInstance_VOID_PTR(self);
|
||||
}
|
||||
} else {
|
||||
into = (T *)DtoolInstance_UPCAST(self, target_class);
|
||||
}
|
||||
return (into != nullptr);
|
||||
}
|
||||
into = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -73,7 +107,7 @@ Dtool_CheckNoArgs(PyObject *args) {
|
|||
ALWAYS_INLINE bool
|
||||
Dtool_CheckNoArgs(PyObject *args, PyObject *kwds) {
|
||||
return PyTuple_GET_SIZE(args) == 0 &&
|
||||
(kwds == NULL || ((PyDictObject *)kwds)->ma_used == 0);
|
||||
(kwds == nullptr || PyDict_GET_SIZE(kwds) == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -17,8 +17,6 @@
|
|||
|
||||
#ifdef HAVE_PYTHON
|
||||
|
||||
PyTupleObject Dtool_EmptyTuple;
|
||||
|
||||
PyMemberDef standard_type_members[] = {
|
||||
{(char *)"this", (sizeof(void*) == sizeof(int)) ? T_UINT : T_ULONGLONG, offsetof(Dtool_PyInstDef, _ptr_to_object), READONLY, (char *)"C++ 'this' pointer, if any"},
|
||||
{(char *)"this_ownership", T_BOOL, offsetof(Dtool_PyInstDef, _memory_rules), READONLY, (char *)"C++ 'this' ownership rules"},
|
||||
|
|
@ -33,67 +31,14 @@ static RuntimeTypeMap runtime_type_map;
|
|||
static RuntimeTypeSet runtime_type_set;
|
||||
static NamedTypeMap named_type_map;
|
||||
|
||||
#if PY_MAJOR_VERSION < 3
|
||||
/**
|
||||
* Given a long or int, returns a size_t, or raises an OverflowError if it is
|
||||
* out of range.
|
||||
*/
|
||||
size_t PyLongOrInt_AsSize_t(PyObject *vv) {
|
||||
if (PyInt_Check(vv)) {
|
||||
long value = PyInt_AS_LONG(vv);
|
||||
if (value < 0) {
|
||||
PyErr_SetString(PyExc_OverflowError,
|
||||
"can't convert negative value to size_t");
|
||||
return (size_t)-1;
|
||||
}
|
||||
return (size_t)value;
|
||||
}
|
||||
|
||||
if (!PyLong_Check(vv)) {
|
||||
Dtool_Raise_TypeError("a long or int was expected");
|
||||
return (size_t)-1;
|
||||
}
|
||||
|
||||
size_t bytes;
|
||||
int one = 1;
|
||||
int res = _PyLong_AsByteArray((PyLongObject *)vv, (unsigned char *)&bytes,
|
||||
SIZEOF_SIZE_T, (int)*(unsigned char*)&one, 0);
|
||||
|
||||
if (res < 0) {
|
||||
return (size_t)res;
|
||||
} else {
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Given a valid (non-NULL) PyObject, does a simple check to see if it might
|
||||
* be an instance of a Panda type. It does this using a signature that is
|
||||
* encoded on each instance.
|
||||
*/
|
||||
bool DtoolCanThisBeAPandaInstance(PyObject *self) {
|
||||
// simple sanity check for the class type..size.. will stop basic foobars..
|
||||
// It is arguably better to use something like this:
|
||||
// PyType_IsSubtype(Py_TYPE(self), &Dtool_DTOOL_SUPER_BASE._PyType) ...but
|
||||
// probably not as fast.
|
||||
if (Py_TYPE(self)->tp_basicsize >= (int)sizeof(Dtool_PyInstDef)) {
|
||||
Dtool_PyInstDef *pyself = (Dtool_PyInstDef *) self;
|
||||
if (pyself->_signature == PY_PANDA_SIGNATURE) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
*/
|
||||
void DTOOL_Call_ExtractThisPointerForType(PyObject *self, Dtool_PyTypedObject *classdef, void **answer) {
|
||||
if (DtoolCanThisBeAPandaInstance(self)) {
|
||||
*answer = ((Dtool_PyInstDef *)self)->_My_Type->_Dtool_UpcastInterface(self, classdef);
|
||||
if (DtoolInstance_Check(self)) {
|
||||
*answer = DtoolInstance_UPCAST(self, *classdef);
|
||||
} else {
|
||||
*answer = NULL;
|
||||
*answer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -103,12 +48,12 @@ void DTOOL_Call_ExtractThisPointerForType(PyObject *self, Dtool_PyTypedObject *c
|
|||
* was of the wrong type, raises an AttributeError.
|
||||
*/
|
||||
bool Dtool_Call_ExtractThisPointer(PyObject *self, Dtool_PyTypedObject &classdef, void **answer) {
|
||||
if (self == NULL || !DtoolCanThisBeAPandaInstance(self) || ((Dtool_PyInstDef *)self)->_ptr_to_object == NULL) {
|
||||
if (self == nullptr || !DtoolInstance_Check(self) || DtoolInstance_VOID_PTR(self) == nullptr) {
|
||||
Dtool_Raise_TypeError("C++ object is not yet constructed, or already destructed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
*answer = ((Dtool_PyInstDef *)self)->_My_Type->_Dtool_UpcastInterface(self, &classdef);
|
||||
*answer = DtoolInstance_UPCAST(self, classdef);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -123,12 +68,12 @@ bool Dtool_Call_ExtractThisPointer(PyObject *self, Dtool_PyTypedObject &classdef
|
|||
bool Dtool_Call_ExtractThisPointer_NonConst(PyObject *self, Dtool_PyTypedObject &classdef,
|
||||
void **answer, const char *method_name) {
|
||||
|
||||
if (self == NULL || !DtoolCanThisBeAPandaInstance(self) || ((Dtool_PyInstDef *)self)->_ptr_to_object == NULL) {
|
||||
if (self == nullptr || !DtoolInstance_Check(self) || DtoolInstance_VOID_PTR(self) == nullptr) {
|
||||
Dtool_Raise_TypeError("C++ object is not yet constructed, or already destructed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (((Dtool_PyInstDef *)self)->_is_const) {
|
||||
if (DtoolInstance_IS_CONST(self)) {
|
||||
// All overloads of this function are non-const.
|
||||
PyErr_Format(PyExc_TypeError,
|
||||
"Cannot call %s() on a const object.",
|
||||
|
|
@ -136,7 +81,7 @@ bool Dtool_Call_ExtractThisPointer_NonConst(PyObject *self, Dtool_PyTypedObject
|
|||
return false;
|
||||
}
|
||||
|
||||
*answer = ((Dtool_PyInstDef *)self)->_My_Type->_Dtool_UpcastInterface(self, &classdef);
|
||||
*answer = DtoolInstance_UPCAST(self, classdef);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -166,19 +111,19 @@ void *
|
|||
DTOOL_Call_GetPointerThisClass(PyObject *self, Dtool_PyTypedObject *classdef,
|
||||
int param, const string &function_name, bool const_ok,
|
||||
bool report_errors) {
|
||||
// if (PyErr_Occurred()) { return NULL; }
|
||||
if (self == NULL) {
|
||||
// if (PyErr_Occurred()) { return nullptr; }
|
||||
if (self == nullptr) {
|
||||
if (report_errors) {
|
||||
return Dtool_Raise_TypeError("self is NULL");
|
||||
return Dtool_Raise_TypeError("self is nullptr");
|
||||
}
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (DtoolCanThisBeAPandaInstance(self)) {
|
||||
void *result = ((Dtool_PyInstDef *)self)->_My_Type->_Dtool_UpcastInterface(self, classdef);
|
||||
if (DtoolInstance_Check(self)) {
|
||||
void *result = DtoolInstance_UPCAST(self, *classdef);
|
||||
|
||||
if (result != NULL) {
|
||||
if (const_ok || !((Dtool_PyInstDef *)self)->_is_const) {
|
||||
if (result != nullptr) {
|
||||
if (const_ok || !DtoolInstance_IS_CONST(self)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -187,7 +132,7 @@ DTOOL_Call_GetPointerThisClass(PyObject *self, Dtool_PyTypedObject *classdef,
|
|||
"%s() argument %d may not be const",
|
||||
function_name.c_str(), param);
|
||||
}
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -195,17 +140,14 @@ DTOOL_Call_GetPointerThisClass(PyObject *self, Dtool_PyTypedObject *classdef,
|
|||
return Dtool_Raise_ArgTypeError(self, param, function_name.c_str(), classdef->_PyType.tp_name);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void *DTOOL_Call_GetPointerThis(PyObject *self) {
|
||||
if (self != NULL) {
|
||||
if (DtoolCanThisBeAPandaInstance(self)) {
|
||||
Dtool_PyInstDef * pyself = (Dtool_PyInstDef *) self;
|
||||
return pyself->_ptr_to_object;
|
||||
}
|
||||
if (self != nullptr && DtoolInstance_Check(self)) {
|
||||
return DtoolInstance_VOID_PTR(self);
|
||||
}
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -646,8 +588,9 @@ PyObject *Dtool_PyModuleInitHelper(LibraryDef *defs[], const char *modulename) {
|
|||
return Dtool_Raise_TypeError("PyType_Ready(Dtool_StaticProperty_Type)");
|
||||
}
|
||||
|
||||
// Initialize the "empty tuple".
|
||||
(void)PyObject_INIT_VAR((PyObject *)&Dtool_EmptyTuple, &PyTuple_Type, 0);
|
||||
#ifdef Py_TRACE_REFS
|
||||
_Py_AddToAllObjects((PyObject *)&Dtool_EmptyTuple, 0);
|
||||
#endif
|
||||
|
||||
// Initialize the base class of everything.
|
||||
Dtool_PyModuleClassInit_DTOOL_SUPER_BASE(NULL);
|
||||
|
|
@ -749,7 +692,7 @@ PyObject *Dtool_BorrowThisReference(PyObject *self, PyObject *args) {
|
|||
PyObject *to_in = NULL;
|
||||
if (PyArg_UnpackTuple(args, "Dtool_BorrowThisReference", 2, 2, &to_in, &from_in)) {
|
||||
|
||||
if (DtoolCanThisBeAPandaInstance(from_in) && DtoolCanThisBeAPandaInstance(to_in)) {
|
||||
if (DtoolInstance_Check(from_in) && DtoolInstance_Check(to_in)) {
|
||||
Dtool_PyInstDef *from = (Dtool_PyInstDef *) from_in;
|
||||
Dtool_PyInstDef *to = (Dtool_PyInstDef *) to_in;
|
||||
|
||||
|
|
@ -794,9 +737,8 @@ PyObject *Dtool_AddToDictionary(PyObject *self1, PyObject *args) {
|
|||
}
|
||||
|
||||
Py_hash_t DTOOL_PyObject_HashPointer(PyObject *self) {
|
||||
if (self != NULL && DtoolCanThisBeAPandaInstance(self)) {
|
||||
Dtool_PyInstDef * pyself = (Dtool_PyInstDef *) self;
|
||||
return (Py_hash_t) pyself->_ptr_to_object;
|
||||
if (self != nullptr && DtoolInstance_Check(self)) {
|
||||
return (Py_hash_t)DtoolInstance_VOID_PTR(self);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
|
@ -969,14 +911,14 @@ bool Dtool_ExtractArg(PyObject **result, PyObject *args, PyObject *kwds,
|
|||
const char *keyword) {
|
||||
|
||||
if (PyTuple_GET_SIZE(args) == 1) {
|
||||
if (kwds == NULL || ((PyDictObject *)kwds)->ma_used == 0) {
|
||||
if (kwds == nullptr || PyDict_GET_SIZE(kwds) == 0) {
|
||||
*result = PyTuple_GET_ITEM(args, 0);
|
||||
return true;
|
||||
}
|
||||
} else if (PyTuple_GET_SIZE(args) == 0) {
|
||||
PyObject *key;
|
||||
Py_ssize_t ppos = 0;
|
||||
if (kwds != NULL && ((PyDictObject *)kwds)->ma_used == 1 &&
|
||||
if (kwds != nullptr && PyDict_GET_SIZE(kwds) == 1 &&
|
||||
PyDict_Next(kwds, &ppos, &key, result)) {
|
||||
// We got the item, we just need to make sure that it had the right key.
|
||||
#if PY_VERSION_HEX >= 0x03060000
|
||||
|
|
@ -997,7 +939,7 @@ bool Dtool_ExtractArg(PyObject **result, PyObject *args, PyObject *kwds,
|
|||
*/
|
||||
bool Dtool_ExtractArg(PyObject **result, PyObject *args, PyObject *kwds) {
|
||||
if (PyTuple_GET_SIZE(args) == 1 &&
|
||||
(kwds == NULL || ((PyDictObject *)kwds)->ma_used == 0)) {
|
||||
(kwds == nullptr || PyDict_GET_SIZE(kwds) == 0)) {
|
||||
*result = PyTuple_GET_ITEM(args, 0);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1015,12 +957,12 @@ bool Dtool_ExtractOptionalArg(PyObject **result, PyObject *args, PyObject *kwds,
|
|||
const char *keyword) {
|
||||
|
||||
if (PyTuple_GET_SIZE(args) == 1) {
|
||||
if (kwds == NULL || ((PyDictObject *)kwds)->ma_used == 0) {
|
||||
if (kwds == nullptr || PyDict_GET_SIZE(kwds) == 0) {
|
||||
*result = PyTuple_GET_ITEM(args, 0);
|
||||
return true;
|
||||
}
|
||||
} else if (PyTuple_GET_SIZE(args) == 0) {
|
||||
if (kwds != NULL && ((PyDictObject *)kwds)->ma_used == 1) {
|
||||
if (kwds != nullptr && PyDict_GET_SIZE(kwds) == 1) {
|
||||
PyObject *key;
|
||||
Py_ssize_t ppos = 0;
|
||||
if (!PyDict_Next(kwds, &ppos, &key, result)) {
|
||||
|
|
@ -1047,7 +989,7 @@ bool Dtool_ExtractOptionalArg(PyObject **result, PyObject *args, PyObject *kwds,
|
|||
* Variant of Dtool_ExtractOptionalArg that does not accept a keyword argument.
|
||||
*/
|
||||
bool Dtool_ExtractOptionalArg(PyObject **result, PyObject *args, PyObject *kwds) {
|
||||
if (kwds != NULL && ((PyDictObject *)kwds)->ma_used != 0) {
|
||||
if (kwds != nullptr && PyDict_GET_SIZE(kwds) != 0) {
|
||||
return false;
|
||||
}
|
||||
if (PyTuple_GET_SIZE(args) == 1) {
|
||||
|
|
|
|||
|
|
@ -20,131 +20,15 @@
|
|||
#define Py_DEBUG
|
||||
#endif
|
||||
|
||||
#ifndef NO_RUNTIME_TYPES
|
||||
|
||||
#include "dtoolbase.h"
|
||||
#include "typedObject.h"
|
||||
#include "typeRegistry.h"
|
||||
|
||||
#endif
|
||||
|
||||
#include "pnotify.h"
|
||||
#include "vector_uchar.h"
|
||||
|
||||
#if defined(HAVE_PYTHON) && !defined(CPPPARSER)
|
||||
|
||||
#ifdef _POSIX_C_SOURCE
|
||||
#undef _POSIX_C_SOURCE
|
||||
#endif
|
||||
|
||||
#ifdef _XOPEN_SOURCE
|
||||
#undef _XOPEN_SOURCE
|
||||
#endif
|
||||
|
||||
#define PY_SSIZE_T_CLEAN 1
|
||||
|
||||
#include "Python.h"
|
||||
// py_compat.h includes Python.h.
|
||||
#include "py_compat.h"
|
||||
#include "structmember.h"
|
||||
|
||||
#ifndef HAVE_LONG_LONG
|
||||
#define PyLong_FromLongLong(x) PyLong_FromLong((long) (x))
|
||||
#define PyLong_FromUnsignedLongLong(x) PyLong_FromUnsignedLong((unsigned long) (x))
|
||||
#define PyLong_AsLongLong(x) PyLong_AsLong(x)
|
||||
#define PyLong_AsUnsignedLongLong(x) PyLong_AsUnsignedLong(x)
|
||||
#define PyLong_AsUnsignedLongLongMask(x) PyLong_AsUnsignedLongMask(x)
|
||||
#define PyLong_AsLongLongAndOverflow(x) PyLong_AsLongAndOverflow(x)
|
||||
#endif
|
||||
|
||||
#if PY_VERSION_HEX < 0x02050000
|
||||
|
||||
// Prior to Python 2.5, we didn't have Py_ssize_t.
|
||||
typedef int Py_ssize_t;
|
||||
#define PyInt_FromSsize_t PyInt_FromLong
|
||||
#define PyInt_AsSsize_t PyInt_AsLong
|
||||
|
||||
#endif // PY_VERSION_HEX
|
||||
|
||||
// 2.4 macros which aren't available in 2.3
|
||||
#ifndef Py_RETURN_NONE
|
||||
inline PyObject* doPy_RETURN_NONE()
|
||||
{ Py_INCREF(Py_None); return Py_None; }
|
||||
#define Py_RETURN_NONE return doPy_RETURN_NONE()
|
||||
#endif
|
||||
|
||||
#ifndef Py_RETURN_TRUE
|
||||
inline PyObject* doPy_RETURN_TRUE()
|
||||
{Py_INCREF(Py_True); return Py_True;}
|
||||
#define Py_RETURN_TRUE return doPy_RETURN_TRUE()
|
||||
#endif
|
||||
|
||||
#ifndef Py_RETURN_FALSE
|
||||
inline PyObject* doPy_RETURN_FALSE()
|
||||
{Py_INCREF(Py_False); return Py_False;}
|
||||
#define Py_RETURN_FALSE return doPy_RETURN_FALSE()
|
||||
#endif
|
||||
|
||||
#ifndef PyVarObject_HEAD_INIT
|
||||
#define PyVarObject_HEAD_INIT(type, size) \
|
||||
PyObject_HEAD_INIT(type) size,
|
||||
#endif
|
||||
|
||||
#ifndef Py_TYPE
|
||||
#define Py_TYPE(ob) (((PyObject*)(ob))->ob_type)
|
||||
#endif
|
||||
|
||||
#ifndef Py_TPFLAGS_CHECKTYPES
|
||||
// Always on in Python 3
|
||||
#define Py_TPFLAGS_CHECKTYPES 0
|
||||
#endif
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
// For writing code that will compile in both versions.
|
||||
#define nb_nonzero nb_bool
|
||||
#define nb_divide nb_true_divide
|
||||
#define nb_inplace_divide nb_inplace_true_divide
|
||||
|
||||
#define PyLongOrInt_Check(x) PyLong_Check(x)
|
||||
#define PyLongOrInt_AS_LONG PyLong_AS_LONG
|
||||
#define PyInt_Check PyLong_Check
|
||||
#define PyInt_AsLong PyLong_AsLong
|
||||
#define PyInt_AS_LONG PyLong_AS_LONG
|
||||
#define PyLongOrInt_AsSize_t PyLong_AsSize_t
|
||||
#else
|
||||
#define PyLongOrInt_Check(x) (PyInt_Check(x) || PyLong_Check(x))
|
||||
// PyInt_FromSize_t automatically picks the right type.
|
||||
#define PyLongOrInt_AS_LONG PyInt_AsLong
|
||||
|
||||
EXPCL_INTERROGATEDB size_t PyLongOrInt_AsSize_t(PyObject *);
|
||||
|
||||
// For more portably defining hash functions.
|
||||
typedef long Py_hash_t;
|
||||
#endif
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
// Python 3 versions before 3.3.3 defined this incorrectly.
|
||||
#undef _PyErr_OCCURRED
|
||||
#define _PyErr_OCCURRED() (PyThreadState_GET()->curexc_type)
|
||||
|
||||
// Python versions before 3.3 did not define this.
|
||||
#if PY_VERSION_HEX < 0x03030000
|
||||
#define PyUnicode_AsUTF8 _PyUnicode_AsString
|
||||
#define PyUnicode_AsUTF8AndSize _PyUnicode_AsStringAndSize
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Which character to use in PyArg_ParseTuple et al for a byte string.
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
#define FMTCHAR_BYTES "y"
|
||||
#else
|
||||
#define FMTCHAR_BYTES "s"
|
||||
#endif
|
||||
|
||||
extern EXPCL_INTERROGATEDB PyTupleObject Dtool_EmptyTuple;
|
||||
|
||||
#ifndef _PyObject_CallNoArg
|
||||
#define _PyObject_CallNoArg(func) PyObject_Call((func), (PyObject *)&Dtool_EmptyTuple, NULL)
|
||||
#endif
|
||||
|
||||
using namespace std;
|
||||
|
||||
// this is tempory .. untill this is glued better into the panda build system
|
||||
|
|
@ -243,7 +127,7 @@ static void Dtool_FreeInstance_##CLASS_NAME(PyObject *self) {\
|
|||
#else // NDEBUG
|
||||
#define Define_Dtool_FreeInstance_Private(CLASS_NAME,CNAME)\
|
||||
static void Dtool_FreeInstance_##CLASS_NAME(PyObject *self) {\
|
||||
if (((Dtool_PyInstDef *)self)->_ptr_to_object != NULL) {\
|
||||
if (DtoolInstance_VOID_PTR(self) != nullptr) {\
|
||||
if (((Dtool_PyInstDef *)self)->_memory_rules) {\
|
||||
cerr << "Detected leak for " << #CLASS_NAME \
|
||||
<< " which interrogate cannot delete.\n"; \
|
||||
|
|
@ -255,9 +139,9 @@ static void Dtool_FreeInstance_##CLASS_NAME(PyObject *self) {\
|
|||
|
||||
#define Define_Dtool_FreeInstance(CLASS_NAME,CNAME)\
|
||||
static void Dtool_FreeInstance_##CLASS_NAME(PyObject *self) {\
|
||||
if (((Dtool_PyInstDef *)self)->_ptr_to_object != NULL) {\
|
||||
if (DtoolInstance_VOID_PTR(self) != nullptr) {\
|
||||
if (((Dtool_PyInstDef *)self)->_memory_rules) {\
|
||||
delete ((CNAME *)((Dtool_PyInstDef *)self)->_ptr_to_object);\
|
||||
delete (CNAME *)DtoolInstance_VOID_PTR(self);\
|
||||
}\
|
||||
}\
|
||||
Py_TYPE(self)->tp_free(self);\
|
||||
|
|
@ -265,9 +149,9 @@ static void Dtool_FreeInstance_##CLASS_NAME(PyObject *self) {\
|
|||
|
||||
#define Define_Dtool_FreeInstanceRef(CLASS_NAME,CNAME)\
|
||||
static void Dtool_FreeInstance_##CLASS_NAME(PyObject *self) {\
|
||||
if (((Dtool_PyInstDef *)self)->_ptr_to_object != NULL) {\
|
||||
if (DtoolInstance_VOID_PTR(self) != nullptr) {\
|
||||
if (((Dtool_PyInstDef *)self)->_memory_rules) {\
|
||||
unref_delete((CNAME *)((Dtool_PyInstDef *)self)->_ptr_to_object);\
|
||||
unref_delete((CNAME *)DtoolInstance_VOID_PTR(self));\
|
||||
}\
|
||||
}\
|
||||
Py_TYPE(self)->tp_free(self);\
|
||||
|
|
@ -279,8 +163,17 @@ static void Dtool_FreeInstance_##CLASS_NAME(PyObject *self) {\
|
|||
Py_TYPE(self)->tp_free(self);\
|
||||
}
|
||||
|
||||
// Simple Recognition Functions..
|
||||
EXPCL_INTERROGATEDB bool DtoolCanThisBeAPandaInstance(PyObject *self);
|
||||
// Use DtoolInstance_Check to check whether a PyObject* is a DtoolInstance.
|
||||
#define DtoolInstance_Check(obj) \
|
||||
(Py_TYPE(obj)->tp_basicsize >= (int)sizeof(Dtool_PyInstDef) && \
|
||||
((Dtool_PyInstDef *)obj)->_signature == PY_PANDA_SIGNATURE)
|
||||
|
||||
// These macros access the DtoolInstance without error checking.
|
||||
#define DtoolInstance_TYPE(obj) (((Dtool_PyInstDef *)obj)->_My_Type)
|
||||
#define DtoolInstance_IS_CONST(obj) (((Dtool_PyInstDef *)obj)->_is_const)
|
||||
#define DtoolInstance_VOID_PTR(obj) (((Dtool_PyInstDef *)obj)->_ptr_to_object)
|
||||
#define DtoolInstance_INIT_PTR(obj, ptr) { ((Dtool_PyInstDef *)obj)->_ptr_to_object = (void*)(ptr); }
|
||||
#define DtoolInstance_UPCAST(obj, type) (((Dtool_PyInstDef *)(obj))->_My_Type->_Dtool_UpcastInterface((obj), &(type)))
|
||||
|
||||
// ** HACK ** allert.. Need to keep a runtime type dictionary ... that is
|
||||
// forward declared of typed object. We rely on the fact that typed objects
|
||||
|
|
@ -308,20 +201,14 @@ EXPCL_INTERROGATEDB bool Dtool_Call_ExtractThisPointer(PyObject *self, Dtool_PyT
|
|||
EXPCL_INTERROGATEDB bool Dtool_Call_ExtractThisPointer_NonConst(PyObject *self, Dtool_PyTypedObject &classdef,
|
||||
void **answer, const char *method_name);
|
||||
|
||||
template<class T> INLINE bool DTOOL_Call_ExtractThisPointer(PyObject *self, T *&into);
|
||||
template<class T> INLINE bool DtoolInstance_GetPointer(PyObject *self, T *&into);
|
||||
template<class T> INLINE bool DtoolInstance_GetPointer(PyObject *self, T *&into, Dtool_PyTypedObject &classdef);
|
||||
|
||||
// Functions related to error reporting.
|
||||
EXPCL_INTERROGATEDB bool _Dtool_CheckErrorOccurred();
|
||||
|
||||
// _PyErr_OCCURRED is an undocumented macro version of PyErr_Occurred.
|
||||
// Some implementations of the CPython API (e.g. PyPy's cpyext) do not define
|
||||
// it, so in these cases we just silently fall back to PyErr_Occurred.
|
||||
#ifndef _PyErr_OCCURRED
|
||||
#define _PyErr_OCCURRED() PyErr_Occurred()
|
||||
#endif
|
||||
|
||||
#ifdef NDEBUG
|
||||
#define Dtool_CheckErrorOccurred() (UNLIKELY(_PyErr_OCCURRED() != NULL))
|
||||
#define Dtool_CheckErrorOccurred() (UNLIKELY(_PyErr_OCCURRED() != nullptr))
|
||||
#else
|
||||
#define Dtool_CheckErrorOccurred() (UNLIKELY(_Dtool_CheckErrorOccurred()))
|
||||
#endif
|
||||
|
|
@ -348,8 +235,8 @@ EXPCL_INTERROGATEDB PyObject *Dtool_Return_Bool(bool value);
|
|||
EXPCL_INTERROGATEDB PyObject *_Dtool_Return(PyObject *value);
|
||||
|
||||
#ifdef NDEBUG
|
||||
#define Dtool_Return_None() (_PyErr_OCCURRED() != NULL ? NULL : (Py_INCREF(Py_None), Py_None))
|
||||
#define Dtool_Return(value) (_PyErr_OCCURRED() != NULL ? NULL : value)
|
||||
#define Dtool_Return_None() (LIKELY(_PyErr_OCCURRED() == nullptr) ? (Py_INCREF(Py_None), Py_None) : nullptr)
|
||||
#define Dtool_Return(value) (LIKELY(_PyErr_OCCURRED() == nullptr) ? value : nullptr)
|
||||
#else
|
||||
#define Dtool_Return_None() _Dtool_Return_None()
|
||||
#define Dtool_Return(value) _Dtool_Return(value)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@
|
|||
|
||||
#include "py_wrappers.h"
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
|
||||
#if PY_VERSION_HEX >= 0x03040000
|
||||
#define _COLLECTIONS_ABC "_collections_abc"
|
||||
#elif PY_VERSION_HEX >= 0x03030000
|
||||
|
|
@ -1669,3 +1671,5 @@ Dtool_NewStaticProperty(PyTypeObject *type, const PyGetSetDef *getset) {
|
|||
}
|
||||
return (PyObject *)descr;
|
||||
}
|
||||
|
||||
#endif // HAVE_PYTHON
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
#include "py_panda.h"
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
|
||||
/**
|
||||
* These classes are returned from properties that require a subscript
|
||||
* interface, ie. something.children[i] = 3.
|
||||
|
|
@ -71,4 +73,6 @@ EXPCL_INTERROGATEDB Dtool_MappingWrapper *Dtool_NewMutableMappingWrapper(PyObjec
|
|||
EXPCL_INTERROGATEDB PyObject *Dtool_NewGenerator(PyObject *self, const char *name, iternextfunc func);
|
||||
EXPCL_INTERROGATEDB PyObject *Dtool_NewStaticProperty(PyTypeObject *obj, const PyGetSetDef *getset);
|
||||
|
||||
#endif
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
#endif // PY_WRAPPERS_H
|
||||
|
|
|
|||
|
|
@ -586,6 +586,7 @@ Section "3ds Max plug-ins" SecMaxPlugins
|
|||
SetOutPath $INSTDIR\plugins
|
||||
File /nonfatal /r "${BUILT}\plugins\*.dle"
|
||||
File /nonfatal /r "${BUILT}\plugins\*.dlo"
|
||||
File /nonfatal /r "${BUILT}\plugins\*.ms"
|
||||
File "${SOURCE}\doc\INSTALLING-PLUGINS.TXT"
|
||||
SectionEnd
|
||||
!endif
|
||||
|
|
@ -601,7 +602,6 @@ Section "Maya plug-ins" SecMayaPlugins
|
|||
SetOutPath $INSTDIR\plugins
|
||||
File /nonfatal /r "${BUILT}\plugins\*.mll"
|
||||
File /nonfatal /r "${BUILT}\plugins\*.mel"
|
||||
File /nonfatal /r "${BUILT}\plugins\*.ms"
|
||||
File "${SOURCE}\doc\INSTALLING-PLUGINS.TXT"
|
||||
SectionEnd
|
||||
!endif
|
||||
|
|
|
|||
|
|
@ -3729,7 +3729,7 @@ if (not RUNTIME):
|
|||
TargetAdd('p3event_composite2.obj', opts=OPTS, input='p3event_composite2.cxx')
|
||||
|
||||
OPTS=['DIR:panda/src/event', 'PYTHON']
|
||||
TargetAdd('p3event_asyncTask_ext.obj', opts=OPTS, input='asyncTask_ext.cxx')
|
||||
TargetAdd('p3event_asyncFuture_ext.obj', opts=OPTS, input='asyncFuture_ext.cxx')
|
||||
TargetAdd('p3event_pythonTask.obj', opts=OPTS, input='pythonTask.cxx')
|
||||
IGATEFILES=GetDirectoryContents('panda/src/event', ["*.h", "*_composite*.cxx"])
|
||||
TargetAdd('libp3event.in', opts=OPTS, input=IGATEFILES)
|
||||
|
|
@ -4323,7 +4323,7 @@ if (not RUNTIME):
|
|||
TargetAdd('core.pyd', input='p3pipeline_pythonThread.obj')
|
||||
TargetAdd('core.pyd', input='p3putil_ext_composite.obj')
|
||||
TargetAdd('core.pyd', input='p3pnmimage_pfmFile_ext.obj')
|
||||
TargetAdd('core.pyd', input='p3event_asyncTask_ext.obj')
|
||||
TargetAdd('core.pyd', input='p3event_asyncFuture_ext.obj')
|
||||
TargetAdd('core.pyd', input='p3event_pythonTask.obj')
|
||||
TargetAdd('core.pyd', input='p3gobj_ext_composite.obj')
|
||||
TargetAdd('core.pyd', input='p3pgraph_ext_composite.obj')
|
||||
|
|
@ -6467,6 +6467,8 @@ for VER in MAYAVERSIONS:
|
|||
continue
|
||||
elif GetTarget() == 'darwin' and int(VNUM) >= 2009:
|
||||
ARCH_OPTS = ['NOARCH:PPC']
|
||||
elif GetTarget() == 'darwin':
|
||||
ARCH_OPTS = ['NOARCH:X86_64']
|
||||
else:
|
||||
ARCH_OPTS = []
|
||||
|
||||
|
|
|
|||
|
|
@ -344,7 +344,6 @@ class WheelFile(object):
|
|||
# Otherwise, just copy it over.
|
||||
temp.write(open(source_path, 'rb').read())
|
||||
|
||||
temp.write(open(source_path, 'rb').read())
|
||||
os.fchmod(temp.fileno(), os.fstat(temp.fileno()).st_mode | 0o111)
|
||||
temp.close()
|
||||
|
||||
|
|
|
|||
|
|
@ -20,8 +20,7 @@ AudioLoadRequest(AudioManager *audio_manager, const string &filename,
|
|||
bool positional) :
|
||||
_audio_manager(audio_manager),
|
||||
_filename(filename),
|
||||
_positional(positional),
|
||||
_is_ready(false)
|
||||
_positional(positional)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -55,31 +54,22 @@ get_positional() const {
|
|||
* Returns true if this request has completed, false if it is still pending.
|
||||
* When this returns true, you may retrieve the sound loaded by calling
|
||||
* get_sound().
|
||||
* Equivalent to `req.done() and not req.cancelled()`.
|
||||
* @see done()
|
||||
*/
|
||||
INLINE bool AudioLoadRequest::
|
||||
is_ready() const {
|
||||
return _is_ready;
|
||||
return (FutureState)AtomicAdjust::get(_future_state) == FS_finished;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sound that was loaded asynchronously, if any, or nullptr if
|
||||
* there was an error. It is an error to call this unless is_ready() returns
|
||||
* there was an error. It is an error to call this unless done() returns
|
||||
* true.
|
||||
* @deprecated Use result() instead.
|
||||
*/
|
||||
INLINE AudioSound *AudioLoadRequest::
|
||||
get_sound() const {
|
||||
nassertr(_is_ready, NULL);
|
||||
return _sound;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sound that was loaded asynchronously, if any, or nullptr if
|
||||
* there was an error. It is an error to call this unless is_ready() returns
|
||||
* true.
|
||||
*/
|
||||
INLINE AudioSound *AudioLoadRequest::
|
||||
result() const {
|
||||
nassertr(_is_ready, nullptr);
|
||||
return _sound;
|
||||
nassertr_always(done(), nullptr);
|
||||
return (AudioSound *)_result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,8 +21,7 @@ TypeHandle AudioLoadRequest::_type_handle;
|
|||
*/
|
||||
AsyncTask::DoneStatus AudioLoadRequest::
|
||||
do_task() {
|
||||
_sound = _audio_manager->get_sound(_filename, _positional);
|
||||
_is_ready = true;
|
||||
set_result(_audio_manager->get_sound(_filename, _positional));
|
||||
|
||||
// Don't continue the task; we're done.
|
||||
return DS_done;
|
||||
|
|
|
|||
|
|
@ -43,8 +43,6 @@ PUBLISHED:
|
|||
INLINE bool is_ready() const;
|
||||
INLINE AudioSound *get_sound() const;
|
||||
|
||||
INLINE AudioSound *result() const;
|
||||
|
||||
protected:
|
||||
virtual DoneStatus do_task();
|
||||
|
||||
|
|
@ -53,9 +51,6 @@ private:
|
|||
string _filename;
|
||||
bool _positional;
|
||||
|
||||
bool _is_ready;
|
||||
PT(AudioSound) _sound;
|
||||
|
||||
public:
|
||||
static TypeHandle get_class_type() {
|
||||
return _type_handle;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file asyncTaskBase.I
|
||||
* @author drose
|
||||
* @date 2010-02-09
|
||||
* @file cocoaGraphicsBuffer.I
|
||||
* @author rdb
|
||||
* @date 2017-12-19
|
||||
*/
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file cocoaGraphicsBuffer.h
|
||||
* @author rdb
|
||||
* @date 2017-12-19
|
||||
*/
|
||||
|
||||
#ifndef COCOAGRAPHICSBUFFER_H
|
||||
#define COCOAGRAPHICSBUFFER_H
|
||||
|
||||
#include "pandabase.h"
|
||||
#include "glgsg.h"
|
||||
|
||||
/**
|
||||
* This is a light wrapper around GLGraphicsBuffer (ie. FBOs) to interface
|
||||
* with Cocoa contexts, so that it can be used without a host window.
|
||||
*/
|
||||
class CocoaGraphicsBuffer : public GLGraphicsBuffer {
|
||||
public:
|
||||
CocoaGraphicsBuffer(GraphicsEngine *engine, GraphicsPipe *pipe,
|
||||
const string &name,
|
||||
const FrameBufferProperties &fb_prop,
|
||||
const WindowProperties &win_prop,
|
||||
int flags,
|
||||
GraphicsStateGuardian *gsg,
|
||||
GraphicsOutput *host);
|
||||
|
||||
virtual bool begin_frame(FrameMode mode, Thread *current_thread);
|
||||
virtual void end_frame(FrameMode mode, Thread *current_thread);
|
||||
|
||||
protected:
|
||||
virtual void close_buffer();
|
||||
virtual bool open_buffer();
|
||||
|
||||
public:
|
||||
static TypeHandle get_class_type() {
|
||||
return _type_handle;
|
||||
}
|
||||
static void init_type() {
|
||||
GLGraphicsBuffer::init_type();
|
||||
register_type(_type_handle, "CocoaGraphicsBuffer",
|
||||
GLGraphicsBuffer::get_class_type());
|
||||
}
|
||||
virtual TypeHandle get_type() const {
|
||||
return get_class_type();
|
||||
}
|
||||
virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
|
||||
|
||||
private:
|
||||
static TypeHandle _type_handle;
|
||||
};
|
||||
|
||||
#include "cocoaGraphicsBuffer.I"
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file cocoaGraphicsBuffer.mm
|
||||
* @author rdb
|
||||
* @date 2017-12-19
|
||||
*/
|
||||
|
||||
#include "cocoaGraphicsBuffer.h"
|
||||
#include "cocoaGraphicsStateGuardian.h"
|
||||
#include "config_cocoadisplay.h"
|
||||
#include "cocoaGraphicsPipe.h"
|
||||
|
||||
#import <OpenGL/OpenGL.h>
|
||||
|
||||
TypeHandle CocoaGraphicsBuffer::_type_handle;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
CocoaGraphicsBuffer::
|
||||
CocoaGraphicsBuffer(GraphicsEngine *engine, GraphicsPipe *pipe,
|
||||
const string &name,
|
||||
const FrameBufferProperties &fb_prop,
|
||||
const WindowProperties &win_prop,
|
||||
int flags,
|
||||
GraphicsStateGuardian *gsg,
|
||||
GraphicsOutput *host) : // Ignore the host.
|
||||
GLGraphicsBuffer(engine, pipe, name, fb_prop, win_prop, flags, gsg, nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will be called within the draw thread before beginning
|
||||
* rendering for a given frame. It should do whatever setup is required, and
|
||||
* return true if the frame should be rendered, or false if it should be
|
||||
* skipped.
|
||||
*/
|
||||
bool CocoaGraphicsBuffer::
|
||||
begin_frame(FrameMode mode, Thread *current_thread) {
|
||||
if (_gsg == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CocoaGraphicsStateGuardian *cocoagsg;
|
||||
DCAST_INTO_R(cocoagsg, _gsg, false);
|
||||
nassertr(cocoagsg->_context != nil, false);
|
||||
|
||||
// Lock the context and make it current.
|
||||
{
|
||||
PStatTimer timer(_make_current_pcollector, current_thread);
|
||||
cocoagsg->lock_context();
|
||||
[cocoagsg->_context makeCurrentContext];
|
||||
}
|
||||
|
||||
return GLGraphicsBuffer::begin_frame(mode, current_thread);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will be called within the draw thread after rendering is
|
||||
* completed for a given frame. It should do whatever finalization is
|
||||
* required.
|
||||
*/
|
||||
void CocoaGraphicsBuffer::
|
||||
end_frame(FrameMode mode, Thread *current_thread) {
|
||||
nassertv(_gsg != nullptr);
|
||||
|
||||
GLGraphicsBuffer::end_frame(mode, current_thread);
|
||||
|
||||
// Release the context.
|
||||
CocoaGraphicsStateGuardian *cocoagsg;
|
||||
DCAST_INTO_V(cocoagsg, _gsg);
|
||||
cocoagsg->unlock_context();
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the buffer right now. Called from the window thread. Returns true
|
||||
* if the buffer is successfully opened, or false if there was a problem.
|
||||
*/
|
||||
bool CocoaGraphicsBuffer::
|
||||
open_buffer() {
|
||||
CocoaGraphicsPipe *cocoa_pipe;
|
||||
DCAST_INTO_R(cocoa_pipe, _pipe, false);
|
||||
|
||||
// GSG CreationInitialization
|
||||
CocoaGraphicsStateGuardian *cocoagsg;
|
||||
if (_gsg == nullptr) {
|
||||
// There is no old gsg. Create a new one.
|
||||
cocoagsg = new CocoaGraphicsStateGuardian(_engine, _pipe, nullptr);
|
||||
cocoagsg->choose_pixel_format(_fb_properties, cocoa_pipe->get_display_id(), false);
|
||||
_gsg = cocoagsg;
|
||||
} else {
|
||||
// If the old gsg has the wrong pixel format, create a new one that shares
|
||||
// with the old gsg.
|
||||
DCAST_INTO_R(cocoagsg, _gsg, false);
|
||||
if (!cocoagsg->get_fb_properties().subsumes(_fb_properties)) {
|
||||
cocoagsg = new CocoaGraphicsStateGuardian(_engine, _pipe, cocoagsg);
|
||||
cocoagsg->choose_pixel_format(_fb_properties, cocoa_pipe->get_display_id(), false);
|
||||
_gsg = cocoagsg;
|
||||
}
|
||||
}
|
||||
|
||||
FrameBufferProperties desired_props(_fb_properties);
|
||||
|
||||
// Lock the context, so we can safely operate on it.
|
||||
cocoagsg->lock_context();
|
||||
|
||||
// Make the context current and initialize what we need.
|
||||
[cocoagsg->_context makeCurrentContext];
|
||||
[cocoagsg->_context update];
|
||||
cocoagsg->reset_if_new();
|
||||
|
||||
// These properties are determined by choose_pixel_format.
|
||||
_fb_properties.set_force_hardware(cocoagsg->_fbprops.get_force_hardware());
|
||||
_fb_properties.set_force_software(cocoagsg->_fbprops.get_force_software());
|
||||
|
||||
bool success = GLGraphicsBuffer::open_buffer();
|
||||
if (success) {
|
||||
rebuild_bitplanes();
|
||||
if (_needs_rebuild) {
|
||||
// If it still needs rebuild, then something must have gone wrong.
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (success && !_fb_properties.verify_hardware_software
|
||||
(desired_props, cocoagsg->get_gl_renderer())) {
|
||||
GLGraphicsBuffer::close_buffer();
|
||||
success = false;
|
||||
}
|
||||
|
||||
// Release the context.
|
||||
cocoagsg->unlock_context();
|
||||
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the buffer right now. Called from the window thread.
|
||||
*/
|
||||
void CocoaGraphicsBuffer::
|
||||
close_buffer() {
|
||||
if (_gsg != nullptr) {
|
||||
CocoaGraphicsStateGuardian *cocoagsg;
|
||||
cocoagsg = DCAST(CocoaGraphicsStateGuardian, _gsg);
|
||||
|
||||
if (cocoagsg != nullptr && cocoagsg->_context != nil) {
|
||||
cocoagsg->lock_context();
|
||||
GLGraphicsBuffer::close_buffer();
|
||||
cocoagsg->unlock_context();
|
||||
}
|
||||
_gsg.clear();
|
||||
} else {
|
||||
GLGraphicsBuffer::close_buffer();
|
||||
}
|
||||
}
|
||||
|
|
@ -18,11 +18,3 @@ INLINE CGDirectDisplayID CocoaGraphicsPipe::
|
|||
get_display_id() const {
|
||||
return _display;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Cocoa NSScreen pointer associated with this graphics pipe.
|
||||
*/
|
||||
INLINE NSScreen *CocoaGraphicsPipe::
|
||||
get_nsscreen() const {
|
||||
return _screen;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,13 +35,10 @@ class FrameBufferProperties;
|
|||
*/
|
||||
class CocoaGraphicsPipe : public GraphicsPipe {
|
||||
public:
|
||||
CocoaGraphicsPipe();
|
||||
CocoaGraphicsPipe(CGDirectDisplayID display);
|
||||
CocoaGraphicsPipe(NSScreen *screen);
|
||||
CocoaGraphicsPipe(CGDirectDisplayID display = CGMainDisplayID());
|
||||
virtual ~CocoaGraphicsPipe();
|
||||
|
||||
INLINE CGDirectDisplayID get_display_id() const;
|
||||
INLINE NSScreen *get_nsscreen() const;
|
||||
|
||||
virtual string get_interface_name() const;
|
||||
static PT(GraphicsPipe) pipe_constructor();
|
||||
|
|
@ -64,11 +61,8 @@ protected:
|
|||
private:
|
||||
void load_display_information();
|
||||
|
||||
// _display and _screen refer to the same thing, NSScreen being the tiny
|
||||
// Cocoa wrapper around the Quartz display ID. NSScreen isn't generally
|
||||
// useful, but we need it when creating the window.
|
||||
// This is the Quartz display identifier.
|
||||
CGDirectDisplayID _display;
|
||||
NSScreen *_screen;
|
||||
|
||||
friend class CocoaGraphicsWindow;
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
*/
|
||||
|
||||
#include "cocoaGraphicsPipe.h"
|
||||
// #include "cocoaGraphicsBuffer.h"
|
||||
#include "cocoaGraphicsBuffer.h"
|
||||
#include "cocoaGraphicsWindow.h"
|
||||
#include "cocoaGraphicsStateGuardian.h"
|
||||
#include "cocoaPandaApp.h"
|
||||
|
|
@ -30,104 +30,32 @@
|
|||
|
||||
TypeHandle CocoaGraphicsPipe::_type_handle;
|
||||
|
||||
static void init_app() {
|
||||
if (NSApp == nil) {
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
[CocoaPandaApp sharedApplication];
|
||||
|
||||
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1060
|
||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||
#endif
|
||||
[NSApp finishLaunching];
|
||||
[NSApp activateIgnoringOtherApps:YES];
|
||||
|
||||
// Put Cocoa into thread-safe mode by spawning a thread which immediately
|
||||
// exits.
|
||||
NSThread* thread = [[NSThread alloc] init];
|
||||
[thread start];
|
||||
[thread autorelease];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the main screen (the one the user is most likely to be working in at
|
||||
* the moment).
|
||||
* Takes a CoreGraphics display ID, which defaults to the main display.
|
||||
*/
|
||||
CocoaGraphicsPipe::
|
||||
CocoaGraphicsPipe() {
|
||||
CocoaGraphicsPipe(CGDirectDisplayID display) : _display(display) {
|
||||
_supported_types = OT_window | OT_buffer | OT_texture_buffer;
|
||||
_is_valid = true;
|
||||
|
||||
init_app();
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
_screen = [NSScreen mainScreen];
|
||||
NSNumber *num = [[_screen deviceDescription] objectForKey: @"NSScreenNumber"];
|
||||
_display = (CGDirectDisplayID) [num longValue];
|
||||
// Put Cocoa into thread-safe mode by spawning a thread which immediately
|
||||
// exits.
|
||||
NSThread* thread = [[NSThread alloc] init];
|
||||
[thread start];
|
||||
[thread autorelease];
|
||||
|
||||
// We used to also obtain the corresponding NSScreen here, but this causes
|
||||
// the application icon to start bouncing, which may be undesirable for
|
||||
// apps that will never open a window.
|
||||
|
||||
_display_width = CGDisplayPixelsWide(_display);
|
||||
_display_height = CGDisplayPixelsHigh(_display);
|
||||
load_display_information();
|
||||
|
||||
cocoadisplay_cat.debug()
|
||||
<< "Creating CocoaGraphicsPipe for main screen "
|
||||
<< _screen << " with display ID " << _display << "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a CoreGraphics display ID.
|
||||
*/
|
||||
CocoaGraphicsPipe::
|
||||
CocoaGraphicsPipe(CGDirectDisplayID display) {
|
||||
_supported_types = OT_window | OT_buffer | OT_texture_buffer;
|
||||
_is_valid = true;
|
||||
_display = display;
|
||||
|
||||
init_app();
|
||||
|
||||
// Iterate over the screens to find the one with our display ID.
|
||||
NSEnumerator *e = [[NSScreen screens] objectEnumerator];
|
||||
while (NSScreen *screen = (NSScreen *) [e nextObject]) {
|
||||
NSNumber *num = [[screen deviceDescription] objectForKey: @"NSScreenNumber"];
|
||||
if (display == (CGDirectDisplayID) [num longValue]) {
|
||||
_screen = screen;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_display_width = CGDisplayPixelsWide(_display);
|
||||
_display_height = CGDisplayPixelsHigh(_display);
|
||||
load_display_information();
|
||||
|
||||
cocoadisplay_cat.debug()
|
||||
<< "Creating CocoaGraphicsPipe for screen "
|
||||
<< _screen << " with display ID " << _display << "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an NSScreen pointer.
|
||||
*/
|
||||
CocoaGraphicsPipe::
|
||||
CocoaGraphicsPipe(NSScreen *screen) {
|
||||
_supported_types = OT_window | OT_buffer | OT_texture_buffer;
|
||||
_is_valid = true;
|
||||
|
||||
init_app();
|
||||
|
||||
if (screen == nil) {
|
||||
_screen = [NSScreen mainScreen];
|
||||
} else {
|
||||
_screen = screen;
|
||||
}
|
||||
NSNumber *num = [[_screen deviceDescription] objectForKey: @"NSScreenNumber"];
|
||||
_display = (CGDirectDisplayID) [num longValue];
|
||||
|
||||
_display_width = CGDisplayPixelsWide(_display);
|
||||
_display_height = CGDisplayPixelsHigh(_display);
|
||||
load_display_information();
|
||||
|
||||
cocoadisplay_cat.debug()
|
||||
<< "Creating CocoaGraphicsPipe for screen "
|
||||
<< _screen << " with display ID " << _display << "\n";
|
||||
<< "Creating CocoaGraphicsPipe for display ID " << _display << "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -308,10 +236,12 @@ make_output(const string &name,
|
|||
flags, gsg, host);
|
||||
}
|
||||
|
||||
// Second thing to try: a GLGraphicsBuffer
|
||||
// Second thing to try: a GLGraphicsBuffer. This requires a context, so if
|
||||
// we don't have a host window, we instead create a CocoaGraphicsBuffer,
|
||||
// which wraps around GLGraphicsBuffer and manages a context.
|
||||
|
||||
if (retry == 1) {
|
||||
if (!gl_support_fbo || host == NULL ||
|
||||
if (!gl_support_fbo ||
|
||||
(flags & (BF_require_parasite | BF_require_window)) != 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
|
@ -334,33 +264,14 @@ make_output(const string &name,
|
|||
precertify = true;
|
||||
}
|
||||
}
|
||||
return new GLGraphicsBuffer(engine, this, name, fb_prop, win_prop,
|
||||
flags, gsg, host);
|
||||
}
|
||||
/*
|
||||
// Third thing to try: a CocoaGraphicsBuffer
|
||||
if (retry == 2) {
|
||||
if (((flags&BF_require_parasite)!=0)||
|
||||
((flags&BF_require_window)!=0)||
|
||||
((flags&BF_resizeable)!=0)||
|
||||
((flags&BF_size_track_host)!=0)||
|
||||
((flags&BF_can_bind_layered)!=0)) {
|
||||
return NULL;
|
||||
if (host != NULL) {
|
||||
return new GLGraphicsBuffer(engine, this, name, fb_prop, win_prop,
|
||||
flags, gsg, host);
|
||||
} else {
|
||||
return new CocoaGraphicsBuffer(engine, this, name, fb_prop, win_prop,
|
||||
flags, gsg, host);
|
||||
}
|
||||
|
||||
if (!support_rtt) {
|
||||
if (((flags&BF_rtt_cumulative)!=0)||
|
||||
((flags&BF_can_bind_every)!=0)) {
|
||||
// If we require Render-to-Texture, but can't be sure we support it,
|
||||
// bail.
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return new CocoaGraphicsBuffer(engine, this, name, fb_prop, win_prop,
|
||||
flags, gsg, host);
|
||||
}
|
||||
*/
|
||||
|
||||
// Nothing else left to try.
|
||||
return NULL;
|
||||
|
|
|
|||
|
|
@ -19,3 +19,21 @@ INLINE const FrameBufferProperties &CocoaGraphicsStateGuardian::
|
|||
get_fb_properties() const {
|
||||
return _fbprops;
|
||||
}
|
||||
|
||||
/**
|
||||
* Locks the context.
|
||||
*/
|
||||
INLINE void CocoaGraphicsStateGuardian::
|
||||
lock_context() {
|
||||
nassertv(_context != nil);
|
||||
CGLLockContext((CGLContextObj) [_context CGLContextObj]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlocks the context.
|
||||
*/
|
||||
INLINE void CocoaGraphicsStateGuardian::
|
||||
unlock_context() {
|
||||
nassertv(_context != nil);
|
||||
CGLUnlockContext((CGLContextObj) [_context CGLContextObj]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
#include "glgsg.h"
|
||||
|
||||
#import <AppKit/NSOpenGL.h>
|
||||
#import <OpenGL/OpenGL.h>
|
||||
|
||||
/**
|
||||
* A tiny specialization on GLGraphicsStateGuardian to add some Cocoa-specific
|
||||
|
|
@ -38,6 +39,9 @@ public:
|
|||
|
||||
virtual ~CocoaGraphicsStateGuardian();
|
||||
|
||||
INLINE void lock_context();
|
||||
INLINE void unlock_context();
|
||||
|
||||
NSOpenGLContext *_share_context;
|
||||
NSOpenGLContext *_context;
|
||||
FrameBufferProperties _fbprops;
|
||||
|
|
|
|||
|
|
@ -24,6 +24,10 @@
|
|||
#define kCGLRendererIDMatchingMask 0x00FE7F00
|
||||
#endif
|
||||
|
||||
#ifndef NSAppKitVersionNumber10_7
|
||||
#define NSAppKitVersionNumber10_7 1138
|
||||
#endif
|
||||
|
||||
TypeHandle CocoaGraphicsStateGuardian::_type_handle;
|
||||
|
||||
/**
|
||||
|
|
@ -207,7 +211,8 @@ choose_pixel_format(const FrameBufferProperties &properties,
|
|||
attribs.push_back(NSOpenGLPFAAccelerated);
|
||||
}
|
||||
|
||||
attribs.push_back(NSOpenGLPFAWindow);
|
||||
// This seems to cause getting a 3.2+ context to fail.
|
||||
//attribs.push_back(NSOpenGLPFAWindow);
|
||||
|
||||
if (need_pbuffer) {
|
||||
attribs.push_back(NSOpenGLPFAPixelBuffer);
|
||||
|
|
@ -217,6 +222,16 @@ choose_pixel_format(const FrameBufferProperties &properties,
|
|||
attribs.push_back(NSOpenGLPFAScreenMask);
|
||||
attribs.push_back(CGDisplayIDToOpenGLDisplayMask(display));
|
||||
|
||||
// Set OpenGL version if a minimum was requested.
|
||||
if (gl_version.size() >= 1 && NSAppKitVersionNumber >= NSAppKitVersionNumber10_7) {
|
||||
//NB. There is also NSOpenGLProfileVersion4_1Core, but this seems to cause
|
||||
// a software implementation to be selected on my mac mini running 10.11.
|
||||
if (gl_version[0] >= 4 || (gl_version.size() >= 2 && gl_version[0] == 3 && gl_version[1] >= 2)) {
|
||||
attribs.push_back((NSOpenGLPixelFormatAttribute)99); // NSOpenGLPFAOpenGLProfile
|
||||
attribs.push_back((NSOpenGLPixelFormatAttribute)0x3200); // NSOpenGLProfileVersion3_2Core
|
||||
}
|
||||
}
|
||||
|
||||
// End of the array
|
||||
attribs.push_back((NSOpenGLPixelFormatAttribute)0);
|
||||
|
||||
|
|
|
|||
|
|
@ -65,6 +65,16 @@ CocoaGraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe,
|
|||
_fullscreen_mode = NULL;
|
||||
_windowed_mode = NULL;
|
||||
|
||||
// Now that we know for sure we want a window, we can create the Cocoa app.
|
||||
// This will cause the application icon to appear and start bouncing.
|
||||
if (NSApp == nil) {
|
||||
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1060
|
||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||
#endif
|
||||
[NSApp finishLaunching];
|
||||
[NSApp activateIgnoringOtherApps:YES];
|
||||
}
|
||||
|
||||
GraphicsWindowInputDevice device =
|
||||
GraphicsWindowInputDevice::pointer_and_keyboard(this, "keyboard_mouse");
|
||||
add_input_device(device);
|
||||
|
|
@ -144,7 +154,7 @@ begin_frame(FrameMode mode, Thread *current_thread) {
|
|||
nassertr(_view != nil, false);
|
||||
|
||||
// Place a lock on the context.
|
||||
CGLLockContext((CGLContextObj) [cocoagsg->_context CGLContextObj]);
|
||||
cocoagsg->lock_context();
|
||||
|
||||
// Set the drawable.
|
||||
if (_properties.get_fullscreen()) {
|
||||
|
|
@ -210,7 +220,7 @@ end_frame(FrameMode mode, Thread *current_thread) {
|
|||
CocoaGraphicsStateGuardian *cocoagsg;
|
||||
DCAST_INTO_V(cocoagsg, _gsg);
|
||||
|
||||
CGLUnlockContext((CGLContextObj) [cocoagsg->_context CGLContextObj]);
|
||||
cocoagsg->unlock_context();
|
||||
|
||||
if (mode == FM_render) {
|
||||
// end_render_texture();
|
||||
|
|
@ -239,7 +249,7 @@ end_flip() {
|
|||
CocoaGraphicsStateGuardian *cocoagsg;
|
||||
DCAST_INTO_V(cocoagsg, _gsg);
|
||||
|
||||
CGLLockContext((CGLContextObj) [cocoagsg->_context CGLContextObj]);
|
||||
cocoagsg->lock_context();
|
||||
|
||||
// Swap the front and back buffer.
|
||||
[cocoagsg->_context flushBuffer];
|
||||
|
|
@ -247,7 +257,7 @@ end_flip() {
|
|||
// Flush the window
|
||||
[[_view window] flushWindow];
|
||||
|
||||
CGLUnlockContext((CGLContextObj) [cocoagsg->_context CGLContextObj]);
|
||||
cocoagsg->unlock_context();
|
||||
}
|
||||
GraphicsWindow::end_flip();
|
||||
}
|
||||
|
|
@ -399,6 +409,16 @@ open_window() {
|
|||
}
|
||||
}
|
||||
|
||||
// Iterate over the screens to find the one with our display ID.
|
||||
NSScreen *screen;
|
||||
NSEnumerator *e = [[NSScreen screens] objectEnumerator];
|
||||
while (screen = (NSScreen *) [e nextObject]) {
|
||||
NSNumber *num = [[screen deviceDescription] objectForKey: @"NSScreenNumber"];
|
||||
if (cocoa_pipe->_display == (CGDirectDisplayID) [num longValue]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Center the window if coordinates were set to -1 or -2 TODO: perhaps in
|
||||
// future, in the case of -1, it should use the origin used in a previous
|
||||
// run of Panda
|
||||
|
|
@ -406,7 +426,7 @@ open_window() {
|
|||
if (parent_nsview != NULL) {
|
||||
container = [parent_nsview bounds];
|
||||
} else {
|
||||
container = [cocoa_pipe->_screen frame];
|
||||
container = [screen frame];
|
||||
container.origin = NSMakePoint(0, 0);
|
||||
}
|
||||
int x = _properties.get_x_origin();
|
||||
|
|
@ -453,7 +473,7 @@ open_window() {
|
|||
_window = [[CocoaPandaWindow alloc]
|
||||
initWithContentRect: rect
|
||||
styleMask:windowStyle
|
||||
screen:cocoa_pipe->_screen
|
||||
screen:screen
|
||||
window:this];
|
||||
|
||||
if (_window == nil) {
|
||||
|
|
@ -464,7 +484,7 @@ open_window() {
|
|||
}
|
||||
|
||||
// Lock the context, so we can safely operate on it.
|
||||
CGLLockContext((CGLContextObj) [cocoagsg->_context CGLContextObj]);
|
||||
cocoagsg->lock_context();
|
||||
|
||||
// Create the NSView to render to.
|
||||
NSRect rect = NSMakeRect(0, 0, _properties.get_x_size(), _properties.get_y_size());
|
||||
|
|
@ -588,7 +608,7 @@ open_window() {
|
|||
cocoagsg->reset_if_new();
|
||||
|
||||
// Release the context.
|
||||
CGLUnlockContext((CGLContextObj) [cocoagsg->_context CGLContextObj]);
|
||||
cocoagsg->unlock_context();
|
||||
|
||||
if (!cocoagsg->is_valid()) {
|
||||
close_window();
|
||||
|
|
@ -637,9 +657,9 @@ close_window() {
|
|||
cocoagsg = DCAST(CocoaGraphicsStateGuardian, _gsg);
|
||||
|
||||
if (cocoagsg != NULL && cocoagsg->_context != nil) {
|
||||
CGLLockContext((CGLContextObj) [cocoagsg->_context CGLContextObj]);
|
||||
cocoagsg->lock_context();
|
||||
[cocoagsg->_context clearDrawable];
|
||||
CGLUnlockContext((CGLContextObj) [cocoagsg->_context CGLContextObj]);
|
||||
cocoagsg->unlock_context();
|
||||
}
|
||||
_gsg.clear();
|
||||
}
|
||||
|
|
@ -1429,9 +1449,9 @@ handle_close_event() {
|
|||
cocoagsg = DCAST(CocoaGraphicsStateGuardian, _gsg);
|
||||
|
||||
if (cocoagsg != NULL && cocoagsg->_context != nil) {
|
||||
CGLLockContext((CGLContextObj) [cocoagsg->_context CGLContextObj]);
|
||||
cocoagsg->lock_context();
|
||||
[cocoagsg->_context clearDrawable];
|
||||
CGLUnlockContext((CGLContextObj) [cocoagsg->_context CGLContextObj]);
|
||||
cocoagsg->unlock_context();
|
||||
}
|
||||
_gsg.clear();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
*/
|
||||
|
||||
#include "config_cocoadisplay.h"
|
||||
#include "cocoaGraphicsBuffer.h"
|
||||
#include "cocoaGraphicsPipe.h"
|
||||
#include "cocoaGraphicsStateGuardian.h"
|
||||
#include "cocoaGraphicsWindow.h"
|
||||
|
|
@ -40,6 +41,7 @@ init_libcocoadisplay() {
|
|||
}
|
||||
initialized = true;
|
||||
|
||||
CocoaGraphicsBuffer::init_type();
|
||||
CocoaGraphicsPipe::init_type();
|
||||
CocoaGraphicsStateGuardian::init_type();
|
||||
CocoaGraphicsWindow::init_type();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include "config_cocoadisplay.mm"
|
||||
#include "cocoaGraphicsBuffer.mm"
|
||||
#include "cocoaGraphicsPipe.mm"
|
||||
#include "cocoaGraphicsStateGuardian.mm"
|
||||
#include "cocoaGraphicsWindow.mm"
|
||||
|
|
|
|||
|
|
@ -489,10 +489,16 @@ get_child_sort() const {
|
|||
/**
|
||||
* When the GraphicsOutput is in triggered copy mode, this function triggers
|
||||
* the copy (at the end of the next frame).
|
||||
* @returns a future that can be awaited.
|
||||
*/
|
||||
INLINE void GraphicsOutput::
|
||||
trigger_copy() {
|
||||
_trigger_copy = true;
|
||||
INLINE AsyncFuture *GraphicsOutput::
|
||||
trigger_copy() {
|
||||
AsyncFuture *future = _trigger_copy;
|
||||
if (future == nullptr) {
|
||||
future = new AsyncFuture;
|
||||
_trigger_copy = future;
|
||||
}
|
||||
return future;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -115,7 +115,6 @@ GraphicsOutput(GraphicsEngine *engine, GraphicsPipe *pipe,
|
|||
_sbs_left_dimensions.set(0.0f, 1.0f, 0.0f, 1.0f);
|
||||
_sbs_right_dimensions.set(0.0f, 1.0f, 0.0f, 1.0f);
|
||||
_delete_flag = false;
|
||||
_trigger_copy = false;
|
||||
|
||||
if (_fb_properties.is_single_buffered()) {
|
||||
_draw_buffer_type = RenderBuffer::T_front;
|
||||
|
|
@ -276,12 +275,21 @@ add_render_texture(Texture *tex, RenderTextureMode mode,
|
|||
|
||||
// Choose a default bitplane.
|
||||
if (plane == RTP_COUNT) {
|
||||
if (tex->get_format() == Texture::F_depth_stencil) {
|
||||
switch (tex->get_format()) {
|
||||
case Texture::F_depth_stencil:
|
||||
plane = RTP_depth_stencil;
|
||||
} else if (tex->get_format() == Texture::F_depth_component) {
|
||||
break;
|
||||
|
||||
case Texture::F_depth_component:
|
||||
case Texture::F_depth_component16:
|
||||
case Texture::F_depth_component24:
|
||||
case Texture::F_depth_component32:
|
||||
plane = RTP_depth;
|
||||
} else {
|
||||
break;
|
||||
|
||||
default:
|
||||
plane = RTP_color;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1435,7 +1443,10 @@ copy_to_textures() {
|
|||
}
|
||||
}
|
||||
}
|
||||
_trigger_copy = false;
|
||||
if (_trigger_copy != nullptr) {
|
||||
_trigger_copy->set_result(nullptr);
|
||||
_trigger_copy = nullptr;
|
||||
}
|
||||
|
||||
return okflag;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@
|
|||
#include "cycleDataWriter.h"
|
||||
#include "pipelineCycler.h"
|
||||
#include "updateSeq.h"
|
||||
#include "asyncFuture.h"
|
||||
|
||||
class PNMImage;
|
||||
class GraphicsEngine;
|
||||
|
|
@ -197,7 +198,7 @@ PUBLISHED:
|
|||
INLINE int get_child_sort() const;
|
||||
MAKE_PROPERTY(child_sort, get_child_sort, set_child_sort);
|
||||
|
||||
INLINE void trigger_copy();
|
||||
INLINE AsyncFuture *trigger_copy();
|
||||
|
||||
INLINE DisplayRegion *make_display_region();
|
||||
INLINE DisplayRegion *make_display_region(PN_stdfloat l, PN_stdfloat r, PN_stdfloat b, PN_stdfloat t);
|
||||
|
|
@ -330,7 +331,7 @@ protected:
|
|||
int _target_tex_view;
|
||||
DisplayRegion *_prev_page_dr;
|
||||
PT(GeomNode) _texture_card;
|
||||
bool _trigger_copy;
|
||||
PT(AsyncFuture) _trigger_copy;
|
||||
|
||||
class RenderTexture {
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -3184,15 +3184,15 @@ get_untextured_state() {
|
|||
* Should be called when a texture is encountered that needs to have its RAM
|
||||
* image reloaded, and get_incomplete_render() is true. This will fire off a
|
||||
* thread on the current Loader object that will request the texture to load
|
||||
* its image. The image will be available at some point in the future (no
|
||||
* event will be generated).
|
||||
* its image. The image will be available at some point in the future.
|
||||
* @returns a future object that can be used to check its status.
|
||||
*/
|
||||
void GraphicsStateGuardian::
|
||||
AsyncFuture *GraphicsStateGuardian::
|
||||
async_reload_texture(TextureContext *tc) {
|
||||
nassertv(_loader != (Loader *)NULL);
|
||||
nassertr(_loader != nullptr, nullptr);
|
||||
|
||||
int priority = 0;
|
||||
if (_current_display_region != (DisplayRegion *)NULL) {
|
||||
if (_current_display_region != nullptr) {
|
||||
priority = _current_display_region->get_texture_reload_priority();
|
||||
}
|
||||
|
||||
|
|
@ -3201,15 +3201,15 @@ async_reload_texture(TextureContext *tc) {
|
|||
|
||||
// See if we are already loading this task.
|
||||
AsyncTaskCollection orig_tasks = task_mgr->find_tasks(task_name);
|
||||
int num_tasks = orig_tasks.get_num_tasks();
|
||||
for (int ti = 0; ti < num_tasks; ++ti) {
|
||||
size_t num_tasks = orig_tasks.get_num_tasks();
|
||||
for (size_t ti = 0; ti < num_tasks; ++ti) {
|
||||
AsyncTask *task = orig_tasks.get_task(ti);
|
||||
if (task->is_exact_type(TextureReloadRequest::get_class_type()) &&
|
||||
DCAST(TextureReloadRequest, task)->get_texture() == tc->get_texture()) {
|
||||
((TextureReloadRequest *)task)->get_texture() == tc->get_texture()) {
|
||||
// This texture is already queued to be reloaded. Don't queue it again,
|
||||
// just make sure the priority is updated, and return.
|
||||
task->set_priority(max(task->get_priority(), priority));
|
||||
return;
|
||||
return (AsyncFuture *)task;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3220,6 +3220,7 @@ async_reload_texture(TextureContext *tc) {
|
|||
_supports_compressed_texture);
|
||||
request->set_priority(priority);
|
||||
_loader->load_async(request);
|
||||
return (AsyncFuture *)request.p();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -460,7 +460,7 @@ protected:
|
|||
static CPT(RenderState) get_unclipped_state();
|
||||
static CPT(RenderState) get_untextured_state();
|
||||
|
||||
void async_reload_texture(TextureContext *tc);
|
||||
AsyncFuture *async_reload_texture(TextureContext *tc);
|
||||
|
||||
protected:
|
||||
PT(SceneSetup) _scene_null;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,200 @@
|
|||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file asyncFuture.I
|
||||
* @author rdb
|
||||
* @date 2017-11-28
|
||||
*/
|
||||
|
||||
/**
|
||||
* Initializes the future in the pending state.
|
||||
*/
|
||||
INLINE AsyncFuture::
|
||||
AsyncFuture() :
|
||||
_manager(nullptr),
|
||||
_future_state(FS_pending),
|
||||
_result(nullptr) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the future is done or has been cancelled. It is always
|
||||
* safe to call this.
|
||||
*/
|
||||
INLINE bool AsyncFuture::
|
||||
done() const {
|
||||
return (FutureState)AtomicAdjust::get(_future_state) >= FS_finished;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the future was cancelled. It is always safe to call this.
|
||||
*/
|
||||
INLINE bool AsyncFuture::
|
||||
cancelled() const {
|
||||
return (FutureState)AtomicAdjust::get(_future_state) == FS_cancelled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the event name that will be triggered when the future finishes. Will
|
||||
* not be triggered if the future is cancelled, but it will be triggered for
|
||||
* a coroutine task that exits with an exception.
|
||||
*/
|
||||
INLINE void AsyncFuture::
|
||||
set_done_event(const string &done_event) {
|
||||
nassertv(!done());
|
||||
_done_event = done_event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the event name that will be triggered when the future finishes.
|
||||
* See set_done_event().
|
||||
*/
|
||||
INLINE const string &AsyncFuture::
|
||||
get_done_event() const {
|
||||
return _done_event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this future's result. Can only be called if done() returns true.
|
||||
*/
|
||||
INLINE TypedObject *AsyncFuture::
|
||||
get_result() const {
|
||||
// This is thread safe, since _result may no longer be modified after the
|
||||
// state is changed to "done".
|
||||
nassertr_always(done(), nullptr);
|
||||
return _result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this future's result as a pair of TypedObject, ReferenceCount
|
||||
* pointers. Can only be called if done() returns true.
|
||||
*/
|
||||
INLINE void AsyncFuture::
|
||||
get_result(TypedObject *&ptr, ReferenceCount *&ref_ptr) const {
|
||||
// This is thread safe, since _result may no longer be modified after the
|
||||
// state is changed to "done".
|
||||
nassertd(done()) {
|
||||
ptr = nullptr;
|
||||
ref_ptr = nullptr;
|
||||
}
|
||||
ptr = _result;
|
||||
ref_ptr = _result_ref.p();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this future's result. Can only be called if done() returns false.
|
||||
*/
|
||||
INLINE void AsyncFuture::
|
||||
set_result(nullptr_t) {
|
||||
set_result(nullptr, nullptr);
|
||||
}
|
||||
|
||||
INLINE void AsyncFuture::
|
||||
set_result(TypedObject *result) {
|
||||
set_result(result, nullptr);
|
||||
}
|
||||
|
||||
INLINE void AsyncFuture::
|
||||
set_result(TypedReferenceCount *result) {
|
||||
set_result(result, result);
|
||||
}
|
||||
|
||||
INLINE void AsyncFuture::
|
||||
set_result(const EventParameter &result) {
|
||||
set_result(result.get_ptr(), result.get_ptr());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new future that returns `done()` when all of the contained
|
||||
* futures are done.
|
||||
*
|
||||
* Calling `cancel()` on the returned future will result in all contained
|
||||
* futures that have not yet finished to be cancelled.
|
||||
*/
|
||||
INLINE AsyncFuture *AsyncFuture::
|
||||
gather(Futures futures) {
|
||||
if (futures.empty()) {
|
||||
AsyncFuture *fut = new AsyncFuture;
|
||||
fut->_future_state = (AtomicAdjust::Integer)FS_finished;
|
||||
return fut;
|
||||
} else if (futures.size() == 1) {
|
||||
return futures[0].p();
|
||||
} else {
|
||||
return (AsyncFuture *)new AsyncGatheringFuture(move(futures));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to atomically lock the future, assuming it is pending. Returns false
|
||||
* if it is not in the pending state, implying it's either done or about to be
|
||||
* cancelled.
|
||||
*/
|
||||
INLINE bool AsyncFuture::
|
||||
try_lock_pending() {
|
||||
return set_future_state(FS_locked_pending);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called after try_lock_pending() returns true.
|
||||
*/
|
||||
INLINE void AsyncFuture::
|
||||
unlock(FutureState new_state) {
|
||||
nassertv(new_state != FS_locked_pending);
|
||||
FutureState orig_state = (FutureState)AtomicAdjust::set(_future_state, (AtomicAdjust::Integer)new_state);
|
||||
nassertv(orig_state == FS_locked_pending);
|
||||
}
|
||||
|
||||
/**
|
||||
* Atomically changes the future state from pending to another state. Returns
|
||||
* true if successful, false if the future was already done.
|
||||
* Note that once a future is in a "done" state (ie. cancelled or finished) it
|
||||
* can never change state again.
|
||||
*/
|
||||
INLINE bool AsyncFuture::
|
||||
set_future_state(FutureState state) {
|
||||
FutureState orig_state = (FutureState)
|
||||
AtomicAdjust::compare_and_exchange(
|
||||
_future_state,
|
||||
(AtomicAdjust::Integer)FS_pending,
|
||||
(AtomicAdjust::Integer)state);
|
||||
|
||||
while (orig_state == FS_locked_pending) {
|
||||
Thread::force_yield();
|
||||
orig_state = (FutureState)AtomicAdjust::compare_and_exchange(
|
||||
_future_state,
|
||||
(AtomicAdjust::Integer)FS_pending,
|
||||
(AtomicAdjust::Integer)state);
|
||||
}
|
||||
|
||||
return orig_state == FS_pending;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of futures that were passed to the constructor.
|
||||
*/
|
||||
INLINE size_t AsyncGatheringFuture::
|
||||
get_num_futures() const {
|
||||
return _futures.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the nth future that was passed into the constructor.
|
||||
*/
|
||||
INLINE AsyncFuture *AsyncGatheringFuture::
|
||||
get_future(size_t i) const {
|
||||
nassertr(i < _futures.size(), nullptr);
|
||||
return _futures[i].p();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the result of the nth future that was passed into the constructor.
|
||||
*/
|
||||
INLINE TypedObject *AsyncGatheringFuture::
|
||||
get_result(size_t i) const {
|
||||
nassertr(i < _futures.size(), nullptr);
|
||||
return _futures[i]->get_result();
|
||||
}
|
||||
|
|
@ -0,0 +1,376 @@
|
|||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file asyncFuture.cxx
|
||||
* @author rdb
|
||||
* @date 2017-11-28
|
||||
*/
|
||||
|
||||
#include "asyncFuture.h"
|
||||
#include "asyncTask.h"
|
||||
#include "asyncTaskManager.h"
|
||||
#include "conditionVarFull.h"
|
||||
#include "config_event.h"
|
||||
#include "pStatTimer.h"
|
||||
#include "throw_event.h"
|
||||
|
||||
TypeHandle AsyncFuture::_type_handle;
|
||||
TypeHandle AsyncGatheringFuture::_type_handle;
|
||||
|
||||
/**
|
||||
* Destroys the future. Assumes notify_done() has already been called.
|
||||
*/
|
||||
AsyncFuture::
|
||||
~AsyncFuture() {
|
||||
// If this triggers, the future destroyed before it was cancelled, which is
|
||||
// not valid. Unless we should simply call cancel() here?
|
||||
nassertv(_waiting.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels the future. Returns true if it was cancelled, or false if the
|
||||
* future was already done. Either way, done() will return true after this
|
||||
* call returns.
|
||||
*
|
||||
* In the case of a task, this is equivalent to remove().
|
||||
*/
|
||||
bool AsyncFuture::
|
||||
cancel() {
|
||||
if (set_future_state(FS_cancelled)) {
|
||||
// The compare-swap operation succeeded, so schedule the callbacks.
|
||||
notify_done(false);
|
||||
return true;
|
||||
} else {
|
||||
// It's already done.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void AsyncFuture::
|
||||
output(ostream &out) const {
|
||||
out << get_type();
|
||||
FutureState state = (FutureState)AtomicAdjust::get(_future_state);
|
||||
switch (state) {
|
||||
case FS_pending:
|
||||
case FS_locked_pending:
|
||||
out << " (pending)";
|
||||
break;
|
||||
case FS_finished:
|
||||
out << " (finished)";
|
||||
break;
|
||||
case FS_cancelled:
|
||||
out << " (cancelled)";
|
||||
break;
|
||||
default:
|
||||
out << " (**INVALID**)";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits until the future is done.
|
||||
*/
|
||||
void AsyncFuture::
|
||||
wait() {
|
||||
if (done()) {
|
||||
return;
|
||||
}
|
||||
|
||||
PStatTimer timer(AsyncTaskChain::_wait_pcollector);
|
||||
if (task_cat.is_debug()) {
|
||||
task_cat.debug()
|
||||
<< "Waiting for future " << *this << "\n";
|
||||
}
|
||||
|
||||
// Continue to yield while the future isn't done. It may be more efficient
|
||||
// to use a condition variable, but let's not add the extra complexity
|
||||
// unless we're sure that we need it.
|
||||
do {
|
||||
Thread::force_yield();
|
||||
} while (!done());
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits until the future is done, or until the timeout is reached.
|
||||
*/
|
||||
void AsyncFuture::
|
||||
wait(double timeout) {
|
||||
if (done()) {
|
||||
return;
|
||||
}
|
||||
|
||||
PStatTimer timer(AsyncTaskChain::_wait_pcollector);
|
||||
if (task_cat.is_debug()) {
|
||||
task_cat.debug()
|
||||
<< "Waiting up to " << timeout << " seconds for future " << *this << "\n";
|
||||
}
|
||||
|
||||
// Continue to yield while the future isn't done. It may be more efficient
|
||||
// to use a condition variable, but let's not add the extra complexity
|
||||
// unless we're sure that we need it.
|
||||
ClockObject *clock = ClockObject::get_global_clock();
|
||||
double end = clock->get_real_time() + timeout;
|
||||
do {
|
||||
Thread::force_yield();
|
||||
} while (!done() && clock->get_real_time() < end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules the done callbacks. Called after the future has just entered the
|
||||
* 'done' state.
|
||||
* @param clean_exit true if finished successfully, false if cancelled.
|
||||
*/
|
||||
void AsyncFuture::
|
||||
notify_done(bool clean_exit) {
|
||||
nassertv(done());
|
||||
|
||||
// This will only be called by the thread that managed to set the
|
||||
// _future_state away from the "pending" state, so this is thread safe.
|
||||
|
||||
Futures::iterator it;
|
||||
for (it = _waiting.begin(); it != _waiting.end(); ++it) {
|
||||
AsyncFuture *fut = *it;
|
||||
if (fut->is_task()) {
|
||||
// It's a task. Make it active again.
|
||||
wake_task((AsyncTask *)fut);
|
||||
} else {
|
||||
// It's a gathering future. Decrease the pending count on it, and if
|
||||
// we're the last one, call notify_done() on it.
|
||||
AsyncGatheringFuture *gather = (AsyncGatheringFuture *)fut;
|
||||
if (!AtomicAdjust::dec(gather->_num_pending)) {
|
||||
if (gather->set_future_state(FS_finished)) {
|
||||
gather->notify_done(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_waiting.clear();
|
||||
|
||||
// For historical reasons, we don't send the "done event" if the future was
|
||||
// cancelled.
|
||||
if (clean_exit && !_done_event.empty()) {
|
||||
PT_Event event = new Event(_done_event);
|
||||
event->add_parameter(EventParameter(this));
|
||||
throw_event(move(event));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this future's result. Can only be done while the future is not done.
|
||||
* Calling this marks the future as done and schedules the done callbacks.
|
||||
*
|
||||
* This variant takes two pointers; the second one is only set if this object
|
||||
* inherits from ReferenceCount, so that a reference can be held.
|
||||
*
|
||||
* Assumes the manager's lock is *not* held.
|
||||
*/
|
||||
void AsyncFuture::
|
||||
set_result(TypedObject *ptr, ReferenceCount *ref_ptr) {
|
||||
// We don't strictly need to lock the future since only one thread is
|
||||
// allowed to call set_result(), but we might as well.
|
||||
FutureState orig_state = (FutureState)AtomicAdjust::
|
||||
compare_and_exchange(_future_state, (AtomicAdjust::Integer)FS_pending,
|
||||
(AtomicAdjust::Integer)FS_locked_pending);
|
||||
|
||||
while (orig_state == FS_locked_pending) {
|
||||
Thread::force_yield();
|
||||
orig_state = (FutureState)AtomicAdjust::
|
||||
compare_and_exchange(_future_state, (AtomicAdjust::Integer)FS_pending,
|
||||
(AtomicAdjust::Integer)FS_locked_pending);
|
||||
}
|
||||
|
||||
if (orig_state == FS_pending) {
|
||||
_result = ptr;
|
||||
_result_ref = ref_ptr;
|
||||
unlock(FS_finished);
|
||||
|
||||
// OK, now our thread owns the _waiting vector et al.
|
||||
notify_done(true);
|
||||
|
||||
} else if (orig_state == FS_cancelled) {
|
||||
// This was originally illegal, but there is a chance that the future was
|
||||
// cancelled while another thread was setting the result. So, we drop
|
||||
// this, but we can issue a warning.
|
||||
task_cat.warning()
|
||||
<< "Ignoring set_result() called on cancelled " << *this << "\n";
|
||||
|
||||
} else {
|
||||
task_cat.error()
|
||||
<< "set_result() was called on finished " << *this << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that the given task is waiting for this future to complete. When
|
||||
* the future is done, it will reactivate the given task. If this is called
|
||||
* while the future is already done, schedules the task immediately.
|
||||
* Assumes the manager's lock is not held.
|
||||
* @returns true if the future was pending, false if it was already done.
|
||||
*/
|
||||
bool AsyncFuture::
|
||||
add_waiting_task(AsyncTask *task) {
|
||||
nassertr(task->is_runnable(), false);
|
||||
|
||||
// We have to make sure we're not going to change state while we're in the
|
||||
// process of adding the task.
|
||||
if (try_lock_pending()) {
|
||||
if (_manager == nullptr) {
|
||||
_manager = task->_manager;
|
||||
}
|
||||
|
||||
_waiting.push_back(task);
|
||||
|
||||
// Unlock the state.
|
||||
unlock();
|
||||
nassertr(task->_manager == nullptr || task->_manager == _manager, true);
|
||||
return true;
|
||||
} else {
|
||||
// It's already done. Wake the task immediately.
|
||||
wake_task(task);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reactivates the given task. Assumes the manager lock is not held.
|
||||
*/
|
||||
void AsyncFuture::
|
||||
wake_task(AsyncTask *task) {
|
||||
cerr << "waking task\n";
|
||||
AsyncTaskManager *manager = task->_manager;
|
||||
if (manager == nullptr) {
|
||||
// If it's an unscheduled task, schedule it on the same manager as the
|
||||
// rest of the waiting tasks.
|
||||
manager = _manager;
|
||||
if (manager == nullptr) {
|
||||
manager = AsyncTaskManager::get_global_ptr();
|
||||
}
|
||||
}
|
||||
|
||||
MutexHolder holder(manager->_lock);
|
||||
switch (task->_state) {
|
||||
case AsyncTask::S_servicing_removed:
|
||||
nassertv(task->_manager == _manager);
|
||||
// Re-adding a self-removed task; this just means clearing the removed
|
||||
// flag.
|
||||
task->_state = AsyncTask::S_servicing;
|
||||
return;
|
||||
|
||||
case AsyncTask::S_inactive:
|
||||
// Schedule it immediately.
|
||||
nassertv(task->_manager == nullptr);
|
||||
|
||||
if (task_cat.is_debug()) {
|
||||
task_cat.debug()
|
||||
<< "Adding " << *task << " (woken by future " << *this << ")\n";
|
||||
}
|
||||
|
||||
{
|
||||
manager->_lock.release();
|
||||
task->upon_birth(manager);
|
||||
manager->_lock.acquire();
|
||||
nassertv(task->_manager == nullptr &&
|
||||
task->_state == AsyncTask::S_inactive);
|
||||
|
||||
AsyncTaskChain *chain = manager->do_find_task_chain(task->_chain_name);
|
||||
if (chain == nullptr) {
|
||||
task_cat.warning()
|
||||
<< "Creating implicit AsyncTaskChain " << task->_chain_name
|
||||
<< " for " << manager->get_type() << " " << manager->get_name() << "\n";
|
||||
chain = manager->do_make_task_chain(task->_chain_name);
|
||||
}
|
||||
chain->do_add(task);
|
||||
}
|
||||
return;
|
||||
|
||||
case AsyncTask::S_awaiting:
|
||||
nassertv(task->_manager == _manager);
|
||||
task->_state = AsyncTask::S_active;
|
||||
task->_chain->_active.push_back(task);
|
||||
--task->_chain->_num_awaiting_tasks;
|
||||
return;
|
||||
|
||||
default:
|
||||
nassertv(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see AsyncFuture::gather
|
||||
*/
|
||||
AsyncGatheringFuture::
|
||||
AsyncGatheringFuture(AsyncFuture::Futures futures) :
|
||||
_futures(move(futures)),
|
||||
_num_pending(0) {
|
||||
|
||||
bool any_pending = false;
|
||||
|
||||
AsyncFuture::Futures::const_iterator it;
|
||||
for (it = _futures.begin(); it != _futures.end(); ++it) {
|
||||
AsyncFuture *fut = *it;
|
||||
// If this returns true, the future is not yet done and we need to
|
||||
// register ourselves with it. This creates a circular reference, but it
|
||||
// is resolved when the future is completed or cancelled.
|
||||
if (fut->try_lock_pending()) {
|
||||
if (_manager == nullptr) {
|
||||
_manager = fut->_manager;
|
||||
}
|
||||
fut->_waiting.push_back((AsyncFuture *)this);
|
||||
AtomicAdjust::inc(_num_pending);
|
||||
fut->unlock();
|
||||
any_pending = true;
|
||||
}
|
||||
}
|
||||
if (!any_pending) {
|
||||
// Start in the done state if all the futures we were passed are done.
|
||||
// Note that it is only safe to set this member in this manner if indeed
|
||||
// no other future holds a reference to us.
|
||||
_future_state = (AtomicAdjust::Integer)FS_finished;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels all the futures. Returns true if any futures were cancelled.
|
||||
* Makes sure that all the futures finish before this one is marked done, in
|
||||
* order to maintain the guarantee that calling result() is safe when done()
|
||||
* returns true.
|
||||
*/
|
||||
bool AsyncGatheringFuture::
|
||||
cancel() {
|
||||
if (!done()) {
|
||||
// Temporarily increase the pending count so that the notify_done()
|
||||
// callbacks won't end up causing it to be set to "finished".
|
||||
AtomicAdjust::inc(_num_pending);
|
||||
|
||||
bool any_cancelled = false;
|
||||
AsyncFuture::Futures::const_iterator it;
|
||||
for (it = _futures.begin(); it != _futures.end(); ++it) {
|
||||
AsyncFuture *fut = *it;
|
||||
if (fut->cancel()) {
|
||||
any_cancelled = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Now change state to "cancelled" and call the notify_done() callbacks.
|
||||
// Don't call notify_done() if another thread has beaten us to it.
|
||||
if (set_future_state(FS_cancelled)) {
|
||||
notify_done(false);
|
||||
}
|
||||
|
||||
// Decreasing the pending count is kind of pointless now, so we do it only
|
||||
// in a debug build.
|
||||
nassertr(!AtomicAdjust::dec(_num_pending), any_cancelled);
|
||||
return any_cancelled;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,199 @@
|
|||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file asyncFuture.h
|
||||
* @author rdb
|
||||
* @date 2017-11-28
|
||||
*/
|
||||
|
||||
#ifndef ASYNCFUTURE_H
|
||||
#define ASYNCFUTURE_H
|
||||
|
||||
#include "pandabase.h"
|
||||
#include "typedReferenceCount.h"
|
||||
#include "typedWritableReferenceCount.h"
|
||||
#include "eventParameter.h"
|
||||
#include "atomicAdjust.h"
|
||||
|
||||
class AsyncTaskManager;
|
||||
class AsyncTask;
|
||||
class ConditionVarFull;
|
||||
|
||||
/**
|
||||
* This class represents a thread-safe handle to a promised future result of
|
||||
* an asynchronous operation, providing methods to query its status and result
|
||||
* as well as register callbacks for this future's completion.
|
||||
*
|
||||
* An AsyncFuture can be awaited from within a coroutine or task. It keeps
|
||||
* track of tasks waiting for this future and automatically reactivates them
|
||||
* upon this future's completion.
|
||||
*
|
||||
* A task itself is also a subclass of AsyncFuture. Other subclasses are
|
||||
* not generally necessary, except to override the function of `cancel()`.
|
||||
*
|
||||
* Until the future is done, it is "owned" by the resolver thread, though it's
|
||||
* still legal for other threads to query its state. When the resolver thread
|
||||
* resolves this future using `set_result()`, or any thread calls `cancel()`,
|
||||
* it instantly enters the "done" state, after which the result becomes a
|
||||
* read-only field that all threads can access.
|
||||
*
|
||||
* When the future returns true for done(), a thread can use cancelled() to
|
||||
* determine whether the future was cancelled or get_result() to access the
|
||||
* result of the operation. Not all operations define a meaningful result
|
||||
* value, so some will always return nullptr.
|
||||
*
|
||||
* In Python, the `cancelled()`, `wait()` and `get_result()` methods are
|
||||
* wrapped up into a single `result()` method which waits for the future to
|
||||
* complete before either returning the result or throwing an exception if the
|
||||
* future was cancelled.
|
||||
* However, it is preferable to use the `await` keyword when running from a
|
||||
* coroutine, which only suspends the current task and not the entire thread.
|
||||
*
|
||||
* This API aims to mirror and be compatible with Python's Future class.
|
||||
*/
|
||||
class EXPCL_PANDA_EVENT AsyncFuture : public TypedReferenceCount {
|
||||
PUBLISHED:
|
||||
INLINE AsyncFuture();
|
||||
virtual ~AsyncFuture();
|
||||
|
||||
EXTENSION(static PyObject *__await__(PyObject *self));
|
||||
EXTENSION(static PyObject *__iter__(PyObject *self));
|
||||
|
||||
INLINE bool done() const;
|
||||
INLINE bool cancelled() const;
|
||||
EXTENSION(PyObject *result(PyObject *timeout = Py_None) const);
|
||||
|
||||
virtual bool cancel();
|
||||
|
||||
INLINE void set_done_event(const string &done_event);
|
||||
INLINE const string &get_done_event() const;
|
||||
MAKE_PROPERTY(done_event, get_done_event, set_done_event);
|
||||
|
||||
EXTENSION(PyObject *add_done_callback(PyObject *self, PyObject *fn));
|
||||
|
||||
EXTENSION(static PyObject *gather(PyObject *args));
|
||||
|
||||
virtual void output(ostream &out) const;
|
||||
|
||||
BLOCKING void wait();
|
||||
BLOCKING void wait(double timeout);
|
||||
|
||||
INLINE void set_result(nullptr_t);
|
||||
INLINE void set_result(TypedObject *result);
|
||||
INLINE void set_result(TypedReferenceCount *result);
|
||||
INLINE void set_result(const EventParameter &result);
|
||||
|
||||
public:
|
||||
void set_result(TypedObject *ptr, ReferenceCount *ref_ptr);
|
||||
|
||||
INLINE TypedObject *get_result() const;
|
||||
INLINE void get_result(TypedObject *&ptr, ReferenceCount *&ref_ptr) const;
|
||||
|
||||
typedef pvector<PT(AsyncFuture)> Futures;
|
||||
INLINE static AsyncFuture *gather(Futures futures);
|
||||
|
||||
virtual bool is_task() const {return false;}
|
||||
|
||||
void notify_done(bool clean_exit);
|
||||
bool add_waiting_task(AsyncTask *task);
|
||||
|
||||
private:
|
||||
void wake_task(AsyncTask *task);
|
||||
|
||||
protected:
|
||||
enum FutureState {
|
||||
// Pending states
|
||||
FS_pending,
|
||||
FS_locked_pending,
|
||||
|
||||
// Done states
|
||||
FS_finished,
|
||||
FS_cancelled,
|
||||
};
|
||||
INLINE bool try_lock_pending();
|
||||
INLINE void unlock(FutureState new_state = FS_pending);
|
||||
INLINE bool set_future_state(FutureState state);
|
||||
|
||||
AsyncTaskManager *_manager;
|
||||
TypedObject *_result;
|
||||
PT(ReferenceCount) _result_ref;
|
||||
AtomicAdjust::Integer _future_state;
|
||||
|
||||
string _done_event;
|
||||
|
||||
// Tasks and gathering futures waiting for this one to complete.
|
||||
Futures _waiting;
|
||||
|
||||
friend class AsyncGatheringFuture;
|
||||
friend class AsyncTaskChain;
|
||||
friend class PythonTask;
|
||||
|
||||
public:
|
||||
static TypeHandle get_class_type() {
|
||||
return _type_handle;
|
||||
}
|
||||
static void init_type() {
|
||||
TypedReferenceCount::init_type();
|
||||
register_type(_type_handle, "AsyncFuture",
|
||||
TypedReferenceCount::get_class_type());
|
||||
}
|
||||
virtual TypeHandle get_type() const {
|
||||
return get_class_type();
|
||||
}
|
||||
virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
|
||||
|
||||
private:
|
||||
static TypeHandle _type_handle;
|
||||
};
|
||||
|
||||
INLINE ostream &operator << (ostream &out, const AsyncFuture &fut) {
|
||||
fut.output(out);
|
||||
return out;
|
||||
};
|
||||
|
||||
/**
|
||||
* Specific future that collects the results of several futures.
|
||||
*/
|
||||
class EXPCL_PANDA_EVENT AsyncGatheringFuture FINAL : public AsyncFuture {
|
||||
private:
|
||||
AsyncGatheringFuture(AsyncFuture::Futures futures);
|
||||
|
||||
public:
|
||||
virtual bool cancel() override;
|
||||
|
||||
INLINE size_t get_num_futures() const;
|
||||
INLINE AsyncFuture *get_future(size_t i) const;
|
||||
INLINE TypedObject *get_result(size_t i) const;
|
||||
|
||||
private:
|
||||
const Futures _futures;
|
||||
AtomicAdjust::Integer _num_pending;
|
||||
|
||||
friend class AsyncFuture;
|
||||
|
||||
public:
|
||||
static TypeHandle get_class_type() {
|
||||
return _type_handle;
|
||||
}
|
||||
static void init_type() {
|
||||
AsyncFuture::init_type();
|
||||
register_type(_type_handle, "AsyncGatheringFuture",
|
||||
AsyncFuture::get_class_type());
|
||||
}
|
||||
virtual TypeHandle get_type() const {
|
||||
return get_class_type();
|
||||
}
|
||||
virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
|
||||
|
||||
private:
|
||||
static TypeHandle _type_handle;
|
||||
};
|
||||
|
||||
#include "asyncFuture.I"
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,318 @@
|
|||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file asyncFuture_ext.h
|
||||
* @author rdb
|
||||
* @date 2017-10-29
|
||||
*/
|
||||
|
||||
#include "asyncFuture_ext.h"
|
||||
#include "asyncTaskSequence.h"
|
||||
#include "eventParameter.h"
|
||||
#include "paramValue.h"
|
||||
#include "pythonTask.h"
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
|
||||
#ifndef CPPPARSER
|
||||
extern struct Dtool_PyTypedObject Dtool_AsyncFuture;
|
||||
extern struct Dtool_PyTypedObject Dtool_ParamValueBase;
|
||||
extern struct Dtool_PyTypedObject Dtool_TypedObject;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Get the result of a future, which may be a PythonTask. Assumes that the
|
||||
* future is already done.
|
||||
*/
|
||||
static PyObject *get_done_result(const AsyncFuture *future) {
|
||||
if (!future->cancelled()) {
|
||||
if (future->is_of_type(PythonTask::get_class_type())) {
|
||||
// If it's a PythonTask, defer to its get_result(), since it may store
|
||||
// any PyObject value or raise an exception.
|
||||
const PythonTask *task = (const PythonTask *)future;
|
||||
return task->get_result();
|
||||
|
||||
} else if (future->is_of_type(AsyncTaskSequence::get_class_type())) {
|
||||
// If it's an AsyncTaskSequence, get the result for each task.
|
||||
const AsyncTaskSequence *task = (const AsyncTaskSequence *)future;
|
||||
Py_ssize_t num_tasks = (Py_ssize_t)task->get_num_tasks();
|
||||
PyObject *results = PyTuple_New(num_tasks);
|
||||
|
||||
for (Py_ssize_t i = 0; i < num_tasks; ++i) {
|
||||
PyObject *result = get_done_result(task->get_task(i));
|
||||
if (result != nullptr) {
|
||||
// This steals a reference.
|
||||
PyTuple_SET_ITEM(results, i, result);
|
||||
} else {
|
||||
Py_DECREF(results);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
return results;
|
||||
|
||||
} else if (future->is_of_type(AsyncGatheringFuture::get_class_type())) {
|
||||
// If it's an AsyncGatheringFuture, get the result for each future.
|
||||
const AsyncGatheringFuture *gather = (const AsyncGatheringFuture *)future;
|
||||
Py_ssize_t num_futures = (Py_ssize_t)gather->get_num_futures();
|
||||
PyObject *results = PyTuple_New(num_futures);
|
||||
|
||||
for (Py_ssize_t i = 0; i < num_futures; ++i) {
|
||||
PyObject *result = get_done_result(gather->get_future((size_t)i));
|
||||
if (result != nullptr) {
|
||||
// This steals a reference.
|
||||
PyTuple_SET_ITEM(results, i, result);
|
||||
} else {
|
||||
Py_DECREF(results);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
return results;
|
||||
|
||||
} else {
|
||||
// It's any other future.
|
||||
ReferenceCount *ref_ptr;
|
||||
TypedObject *ptr;
|
||||
future->get_result(ptr, ref_ptr);
|
||||
|
||||
if (ptr == nullptr) {
|
||||
Py_INCREF(Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
TypeHandle type = ptr->get_type();
|
||||
if (type.is_derived_from(ParamValueBase::get_class_type())) {
|
||||
// If this is a ParamValueBase, return the 'value' property.
|
||||
// EventStoreInt and Double are not exposed to Python for some reason.
|
||||
if (type == EventStoreInt::get_class_type()) {
|
||||
return Dtool_WrapValue(((EventStoreInt *)ptr)->get_value());
|
||||
} else if (type == EventStoreDouble::get_class_type()) {
|
||||
return Dtool_WrapValue(((EventStoreDouble *)ptr)->get_value());
|
||||
}
|
||||
|
||||
ParamValueBase *value = (ParamValueBase *)ptr;
|
||||
PyObject *wrap = DTool_CreatePyInstanceTyped
|
||||
((void *)value, Dtool_ParamValueBase, false, false, type.get_index());
|
||||
if (wrap != nullptr) {
|
||||
PyObject *value = PyObject_GetAttrString(wrap, "value");
|
||||
if (value != nullptr) {
|
||||
return value;
|
||||
}
|
||||
PyErr_Restore(nullptr, nullptr, nullptr);
|
||||
Py_DECREF(wrap);
|
||||
}
|
||||
}
|
||||
|
||||
if (ref_ptr != nullptr) {
|
||||
ref_ptr->ref();
|
||||
}
|
||||
|
||||
return DTool_CreatePyInstanceTyped
|
||||
((void *)ptr, Dtool_TypedObject, (ref_ptr != nullptr), false,
|
||||
type.get_index());
|
||||
}
|
||||
} else {
|
||||
// If the future was cancelled, we should raise an exception.
|
||||
static PyObject *exc_type = nullptr;
|
||||
if (exc_type == nullptr) {
|
||||
// Get the CancelledError that asyncio uses, too.
|
||||
PyObject *module = PyImport_ImportModule("concurrent.futures._base");
|
||||
if (module != nullptr) {
|
||||
exc_type = PyObject_GetAttrString(module, "CancelledError");
|
||||
Py_DECREF(module);
|
||||
}
|
||||
// If we can't get that, we should pretend and make our own.
|
||||
if (exc_type == nullptr) {
|
||||
exc_type = PyErr_NewExceptionWithDoc((char*)"concurrent.futures._base.CancelledError",
|
||||
(char*)"The Future was cancelled.",
|
||||
nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
Py_INCREF(exc_type);
|
||||
PyErr_Restore(exc_type, nullptr, nullptr);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Yields continuously until the task has finished.
|
||||
*/
|
||||
static PyObject *gen_next(PyObject *self) {
|
||||
const AsyncFuture *future = nullptr;
|
||||
if (!Dtool_Call_ExtractThisPointer(self, Dtool_AsyncFuture, (void **)&future)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!future->done()) {
|
||||
// Continue awaiting the result.
|
||||
Py_INCREF(self);
|
||||
return self;
|
||||
} else {
|
||||
PyObject *result = get_done_result(future);
|
||||
if (result != nullptr) {
|
||||
Py_INCREF(PyExc_StopIteration);
|
||||
PyErr_Restore(PyExc_StopIteration, result, nullptr);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a generator that continuously yields an awaitable until the task
|
||||
* has finished. This allows syntax like `model = await loader.load...` to be
|
||||
* used in a Python coroutine.
|
||||
*/
|
||||
PyObject *Extension<AsyncFuture>::
|
||||
__await__(PyObject *self) {
|
||||
Dtool_GeneratorWrapper *gen;
|
||||
gen = (Dtool_GeneratorWrapper *)PyType_GenericAlloc(&Dtool_GeneratorWrapper_Type, 0);
|
||||
if (gen != nullptr) {
|
||||
Py_INCREF(self);
|
||||
gen->_base._self = self;
|
||||
gen->_iternext_func = &gen_next;
|
||||
}
|
||||
return (PyObject *)gen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the result of this future, unless it was cancelled, in which case
|
||||
* it returns CancelledError.
|
||||
* If the future is not yet done, waits until the result is available. If a
|
||||
* timeout is passed and the future is not done within the given timeout,
|
||||
* raises TimeoutError.
|
||||
*/
|
||||
PyObject *Extension<AsyncFuture>::
|
||||
result(PyObject *timeout) const {
|
||||
if (!_this->done()) {
|
||||
// Not yet done? Wait until it is done, or until a timeout occurs. But
|
||||
// first check to make sure we're not trying to deadlock the thread.
|
||||
Thread *current_thread = Thread::get_current_thread();
|
||||
if (_this == (const AsyncFuture *)current_thread->get_current_task()) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "cannot call task.result() from within the task");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Release the GIL for the duration.
|
||||
#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
|
||||
PyThreadState *_save;
|
||||
Py_UNBLOCK_THREADS
|
||||
#endif
|
||||
if (timeout == Py_None) {
|
||||
_this->wait();
|
||||
} else {
|
||||
PyObject *num = PyNumber_Float(timeout);
|
||||
if (num != nullptr) {
|
||||
_this->wait(PyFloat_AS_DOUBLE(num));
|
||||
} else {
|
||||
return Dtool_Raise_ArgTypeError(timeout, 0, "result", "float");
|
||||
}
|
||||
}
|
||||
#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
|
||||
Py_BLOCK_THREADS
|
||||
#endif
|
||||
|
||||
if (!_this->done()) {
|
||||
// It timed out. Raise an exception.
|
||||
static PyObject *exc_type = nullptr;
|
||||
if (exc_type == nullptr) {
|
||||
// Get the TimeoutError that asyncio uses, too.
|
||||
PyObject *module = PyImport_ImportModule("concurrent.futures._base");
|
||||
if (module != nullptr) {
|
||||
exc_type = PyObject_GetAttrString(module, "TimeoutError");
|
||||
Py_DECREF(module);
|
||||
}
|
||||
// If we can't get that, we should pretend and make our own.
|
||||
if (exc_type == nullptr) {
|
||||
exc_type = PyErr_NewExceptionWithDoc((char*)"concurrent.futures._base.TimeoutError",
|
||||
(char*)"The operation exceeded the given deadline.",
|
||||
nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
Py_INCREF(exc_type);
|
||||
PyErr_Restore(exc_type, nullptr, nullptr);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return get_done_result(_this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules the given function to be run as soon as the future is complete.
|
||||
* This is also called if the future is cancelled.
|
||||
* If the future is already done, the callback is scheduled right away.
|
||||
*/
|
||||
PyObject *Extension<AsyncFuture>::
|
||||
add_done_callback(PyObject *self, PyObject *fn) {
|
||||
if (!PyCallable_Check(fn)) {
|
||||
return Dtool_Raise_ArgTypeError(fn, 0, "add_done_callback", "callable");
|
||||
}
|
||||
|
||||
PythonTask *task = new PythonTask(fn);
|
||||
Py_DECREF(task->_args);
|
||||
task->_args = PyTuple_Pack(1, self);
|
||||
task->_append_task = false;
|
||||
task->_ignore_return = true;
|
||||
|
||||
// If this is an AsyncTask, make sure it is scheduled on the same chain.
|
||||
if (_this->is_task()) {
|
||||
AsyncTask *this_task = (AsyncTask *)_this;
|
||||
task->set_task_chain(this_task->get_task_chain());
|
||||
}
|
||||
|
||||
_this->add_waiting_task(task);
|
||||
|
||||
Py_INCREF(Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new future that returns `done()` when all of the contained
|
||||
* futures are done.
|
||||
*
|
||||
* Calling `cancel()` on the returned future will result in all contained
|
||||
* futures that have not yet finished to be cancelled.
|
||||
*/
|
||||
PyObject *Extension<AsyncFuture>::
|
||||
gather(PyObject *args) {
|
||||
if (!PyTuple_Check(args)) {
|
||||
return Dtool_Raise_TypeError("args is not a tuple");
|
||||
}
|
||||
|
||||
Py_ssize_t size = Py_SIZE(args);
|
||||
AsyncFuture::Futures futures;
|
||||
futures.reserve(size);
|
||||
|
||||
for (Py_ssize_t i = 0; i < size; ++i) {
|
||||
PyObject *item = PyTuple_GET_ITEM(args, i);
|
||||
if (DtoolInstance_Check(item)) {
|
||||
AsyncFuture *fut = (AsyncFuture *)DtoolInstance_UPCAST(item, Dtool_AsyncFuture);
|
||||
if (fut != nullptr) {
|
||||
futures.push_back(fut);
|
||||
continue;
|
||||
}
|
||||
#if PY_VERSION_HEX >= 0x03050000
|
||||
} else if (PyCoro_CheckExact(item)) {
|
||||
// We allow passing in a coroutine instead of a future. This causes it
|
||||
// to be scheduled as a task.
|
||||
futures.push_back(new PythonTask(item));
|
||||
continue;
|
||||
#endif
|
||||
}
|
||||
return Dtool_Raise_ArgTypeError(item, i, "gather", "coroutine, task or future");
|
||||
}
|
||||
|
||||
AsyncFuture *future = AsyncFuture::gather(move(futures));
|
||||
if (future != nullptr) {
|
||||
future->ref();
|
||||
return DTool_CreatePyInstanceTyped((void *)future, Dtool_AsyncFuture, true, false, future->get_type_index());
|
||||
} else {
|
||||
return PyErr_NoMemory();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
18
panda/src/event/asyncTask_ext.h → panda/src/event/asyncFuture_ext.h
Executable file → Normal file
18
panda/src/event/asyncTask_ext.h → panda/src/event/asyncFuture_ext.h
Executable file → Normal file
|
|
@ -6,13 +6,13 @@
|
|||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file asyncTask_ext.h
|
||||
* @file asyncFuture_ext.h
|
||||
* @author rdb
|
||||
* @date 2017-10-29
|
||||
*/
|
||||
|
||||
#ifndef ASYNCTASK_EXT_H
|
||||
#define ASYNCTASK_EXT_H
|
||||
#ifndef ASYNCFUTURE_EXT_H
|
||||
#define ASYNCFUTURE_EXT_H
|
||||
|
||||
#include "extension.h"
|
||||
#include "py_panda.h"
|
||||
|
|
@ -21,15 +21,21 @@
|
|||
#ifdef HAVE_PYTHON
|
||||
|
||||
/**
|
||||
* Extension class for AsyncTask
|
||||
* Extension class for AsyncFuture
|
||||
*/
|
||||
template<>
|
||||
class Extension<AsyncTask> : public ExtensionBase<AsyncTask> {
|
||||
class Extension<AsyncFuture> : public ExtensionBase<AsyncFuture> {
|
||||
public:
|
||||
static PyObject *__await__(PyObject *self);
|
||||
static PyObject *__iter__(PyObject *self) { return __await__(self); }
|
||||
|
||||
PyObject *result(PyObject *timeout = Py_None) const;
|
||||
|
||||
PyObject *add_done_callback(PyObject *self, PyObject *fn);
|
||||
|
||||
static PyObject *gather(PyObject *args);
|
||||
};
|
||||
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
#endif // ASYNCTASK_EXT_H
|
||||
#endif // ASYNCFUTURE_EXT_H
|
||||
|
|
@ -181,15 +181,6 @@ set_done_event(const string &done_event) {
|
|||
_done_event = done_event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the event name that will be triggered when the task finishes. See
|
||||
* set_done_event().
|
||||
*/
|
||||
INLINE const string &AsyncTask::
|
||||
get_done_event() const {
|
||||
return _done_event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount of time elapsed during the task's previous run cycle, in
|
||||
* seconds.
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ AsyncTask(const string &name) :
|
|||
_priority(0),
|
||||
_state(S_inactive),
|
||||
_servicing_thread(NULL),
|
||||
_manager(NULL),
|
||||
_chain(NULL),
|
||||
_start_time(0.0),
|
||||
_start_frame(0),
|
||||
|
|
@ -68,11 +67,27 @@ AsyncTask::
|
|||
* S_inactive (or possible S_servicing_removed). This is a no-op if the state
|
||||
* is already S_inactive.
|
||||
*/
|
||||
void AsyncTask::
|
||||
bool AsyncTask::
|
||||
remove() {
|
||||
if (_manager != (AsyncTaskManager *)NULL) {
|
||||
_manager->remove(this);
|
||||
AsyncTaskManager *manager = _manager;
|
||||
if (manager != nullptr) {
|
||||
nassertr(_chain->_manager == manager, false);
|
||||
if (task_cat.is_debug()) {
|
||||
task_cat.debug()
|
||||
<< "Removing " << *this << "\n";
|
||||
}
|
||||
MutexHolder holder(manager->_lock);
|
||||
if (_chain->do_remove(this, true)) {
|
||||
return true;
|
||||
} else {
|
||||
if (task_cat.is_debug()) {
|
||||
task_cat.debug()
|
||||
<< " (unable to remove " << *this << ")\n";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -379,11 +394,26 @@ jump_to_task_chain(AsyncTaskManager *manager) {
|
|||
*/
|
||||
AsyncTask::DoneStatus AsyncTask::
|
||||
unlock_and_do_task() {
|
||||
nassertr(_manager != (AsyncTaskManager *)NULL, DS_done);
|
||||
nassertr(_manager != nullptr, DS_done);
|
||||
PT(ClockObject) clock = _manager->get_clock();
|
||||
|
||||
// Indicate that this task is now the current task running on the thread.
|
||||
Thread *current_thread = Thread::get_current_thread();
|
||||
record_task(current_thread);
|
||||
nassertr(current_thread->_current_task == nullptr, DS_interrupt);
|
||||
|
||||
void *ptr = AtomicAdjust::compare_and_exchange_ptr
|
||||
((void * TVOLATILE &)current_thread->_current_task,
|
||||
(void *)nullptr, (void *)this);
|
||||
|
||||
// If the return value is other than nullptr, someone else must have
|
||||
// assigned the task first, in another thread. That shouldn't be possible.
|
||||
|
||||
// But different versions of gcc appear to have problems compiling these
|
||||
// assertions correctly.
|
||||
#ifndef __GNUC__
|
||||
nassertr(ptr == nullptr, DS_interrupt);
|
||||
nassertr(current_thread->_current_task == this, DS_interrupt);
|
||||
#endif // __GNUC__
|
||||
|
||||
// It's important to release the lock while the task is being serviced.
|
||||
_manager->_lock.release();
|
||||
|
|
@ -403,11 +433,36 @@ unlock_and_do_task() {
|
|||
|
||||
_chain->_time_in_frame += _dt;
|
||||
|
||||
clear_task(current_thread);
|
||||
// Now indicate that this is no longer the current task.
|
||||
nassertr(current_thread->_current_task == this, status);
|
||||
|
||||
ptr = AtomicAdjust::compare_and_exchange_ptr
|
||||
((void * TVOLATILE &)current_thread->_current_task,
|
||||
(void *)this, (void *)nullptr);
|
||||
|
||||
// If the return value is other than this, someone else must have assigned
|
||||
// the task first, in another thread. That shouldn't be possible.
|
||||
|
||||
// But different versions of gcc appear to have problems compiling these
|
||||
// assertions correctly.
|
||||
#ifndef __GNUC__
|
||||
nassertr(ptr == this, DS_interrupt);
|
||||
nassertr(current_thread->_current_task == nullptr, DS_interrupt);
|
||||
#endif // __GNUC__
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels this task. This is equivalent to remove().
|
||||
*/
|
||||
bool AsyncTask::
|
||||
cancel() {
|
||||
bool result = remove();
|
||||
nassertr(done(), false);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this function to return true if the task can be successfully
|
||||
* executed, false if it cannot. Mainly intended as a sanity check when
|
||||
|
|
@ -477,22 +532,16 @@ upon_birth(AsyncTaskManager *manager) {
|
|||
* task has been removed because it exited normally (returning DS_done), or
|
||||
* false if it was removed for some other reason (e.g.
|
||||
* AsyncTaskManager::remove()). By the time this method is called, _manager
|
||||
* has been cleared, so the parameter manager indicates the original
|
||||
* may have been cleared, so the parameter manager indicates the original
|
||||
* AsyncTaskManager that owned this task.
|
||||
*
|
||||
* The normal behavior is to throw the done_event only if clean_exit is true.
|
||||
*
|
||||
* This function is called with the lock *not* held.
|
||||
*/
|
||||
void AsyncTask::
|
||||
upon_death(AsyncTaskManager *manager, bool clean_exit) {
|
||||
if (clean_exit && !_done_event.empty()) {
|
||||
PT_Event event = new Event(_done_event);
|
||||
event->add_parameter(EventParameter(this));
|
||||
throw_event(event);
|
||||
}
|
||||
//NB. done_event is now being thrown in AsyncFuture::notify_done().
|
||||
|
||||
// Also throw a generic remove event for the manager.
|
||||
// Throw a generic remove event for the manager.
|
||||
if (manager != (AsyncTaskManager *)NULL) {
|
||||
string remove_name = manager->get_name() + "-removeTask";
|
||||
PT_Event event = new Event(remove_name);
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@
|
|||
#define ASYNCTASK_H
|
||||
|
||||
#include "pandabase.h"
|
||||
|
||||
#include "asyncTaskBase.h"
|
||||
#include "asyncFuture.h"
|
||||
#include "namable.h"
|
||||
#include "pmutex.h"
|
||||
#include "conditionVar.h"
|
||||
#include "pStatCollector.h"
|
||||
|
|
@ -29,7 +29,7 @@ class AsyncTaskChain;
|
|||
* Normally, you would subclass from this class, and override do_task(), to
|
||||
* define the functionality you wish to have the task perform.
|
||||
*/
|
||||
class EXPCL_PANDA_EVENT AsyncTask : public AsyncTaskBase {
|
||||
class EXPCL_PANDA_EVENT AsyncTask : public AsyncFuture, public Namable {
|
||||
public:
|
||||
AsyncTask(const string &name = string());
|
||||
ALLOC_DELETED_CHAIN(AsyncTask);
|
||||
|
|
@ -62,7 +62,7 @@ PUBLISHED:
|
|||
INLINE bool is_alive() const;
|
||||
INLINE AsyncTaskManager *get_manager() const;
|
||||
|
||||
void remove();
|
||||
bool remove();
|
||||
|
||||
INLINE void set_delay(double delay);
|
||||
INLINE void clear_delay();
|
||||
|
|
@ -92,7 +92,6 @@ PUBLISHED:
|
|||
INLINE int get_priority() const;
|
||||
|
||||
INLINE void set_done_event(const string &done_event);
|
||||
INLINE const string &get_done_event() const;
|
||||
|
||||
INLINE double get_dt() const;
|
||||
INLINE double get_max_dt() const;
|
||||
|
|
@ -100,13 +99,13 @@ PUBLISHED:
|
|||
|
||||
virtual void output(ostream &out) const;
|
||||
|
||||
EXTENSION(static PyObject *__await__(PyObject *self));
|
||||
EXTENSION(static PyObject *__iter__(PyObject *self));
|
||||
|
||||
protected:
|
||||
void jump_to_task_chain(AsyncTaskManager *manager);
|
||||
DoneStatus unlock_and_do_task();
|
||||
|
||||
virtual bool cancel() FINAL;
|
||||
virtual bool is_task() const FINAL {return true;}
|
||||
|
||||
virtual bool is_runnable();
|
||||
virtual DoneStatus do_task();
|
||||
virtual void upon_birth(AsyncTaskManager *manager);
|
||||
|
|
@ -120,11 +119,9 @@ protected:
|
|||
double _wake_time;
|
||||
int _sort;
|
||||
int _priority;
|
||||
string _done_event;
|
||||
|
||||
State _state;
|
||||
Thread *_servicing_thread;
|
||||
AsyncTaskManager *_manager;
|
||||
AsyncTaskChain *_chain;
|
||||
|
||||
double _start_time;
|
||||
|
|
@ -135,9 +132,6 @@ protected:
|
|||
double _total_dt;
|
||||
int _num_frames;
|
||||
|
||||
// Tasks waiting for this one to complete.
|
||||
pvector<PT(AsyncTask)> _waiting_tasks;
|
||||
|
||||
static AtomicAdjust::Integer _next_task_id;
|
||||
|
||||
static PStatCollector _show_code_pcollector;
|
||||
|
|
@ -150,9 +144,9 @@ public:
|
|||
return _type_handle;
|
||||
}
|
||||
static void init_type() {
|
||||
AsyncTaskBase::init_type();
|
||||
AsyncFuture::init_type();
|
||||
register_type(_type_handle, "AsyncTask",
|
||||
AsyncTaskBase::get_class_type());
|
||||
AsyncFuture::get_class_type());
|
||||
}
|
||||
virtual TypeHandle get_type() const {
|
||||
return get_class_type();
|
||||
|
|
@ -162,6 +156,7 @@ public:
|
|||
private:
|
||||
static TypeHandle _type_handle;
|
||||
|
||||
friend class AsyncFuture;
|
||||
friend class AsyncTaskManager;
|
||||
friend class AsyncTaskChain;
|
||||
friend class AsyncTaskSequence;
|
||||
|
|
|
|||
|
|
@ -456,24 +456,21 @@ do_add(AsyncTask *task) {
|
|||
/**
|
||||
* Removes the indicated task from this chain. Returns true if removed, false
|
||||
* otherwise. Assumes the lock is already held. The task->upon_death()
|
||||
* method is *not* called.
|
||||
* method is called with clean_exit=false if upon_death is given.
|
||||
*/
|
||||
bool AsyncTaskChain::
|
||||
do_remove(AsyncTask *task) {
|
||||
bool removed = false;
|
||||
|
||||
do_remove(AsyncTask *task, bool upon_death) {
|
||||
nassertr(task->_chain == this, false);
|
||||
|
||||
switch (task->_state) {
|
||||
case AsyncTask::S_servicing:
|
||||
// This task is being serviced.
|
||||
// This task is being serviced. upon_death will be called afterwards.
|
||||
task->_state = AsyncTask::S_servicing_removed;
|
||||
removed = true;
|
||||
break;
|
||||
return true;
|
||||
|
||||
case AsyncTask::S_servicing_removed:
|
||||
// Being serviced, though it will be removed later.
|
||||
break;
|
||||
// Being serviced, though it is already marked to be removed afterwards.
|
||||
return false;
|
||||
|
||||
case AsyncTask::S_sleeping:
|
||||
// Sleeping, easy.
|
||||
|
|
@ -482,10 +479,9 @@ do_remove(AsyncTask *task) {
|
|||
nassertr(index != -1, false);
|
||||
_sleeping.erase(_sleeping.begin() + index);
|
||||
make_heap(_sleeping.begin(), _sleeping.end(), AsyncTaskSortWakeTime());
|
||||
removed = true;
|
||||
cleanup_task(task, false, false);
|
||||
cleanup_task(task, upon_death, false);
|
||||
}
|
||||
break;
|
||||
return true;
|
||||
|
||||
case AsyncTask::S_active:
|
||||
{
|
||||
|
|
@ -503,15 +499,15 @@ do_remove(AsyncTask *task) {
|
|||
nassertr(index != -1, false);
|
||||
}
|
||||
}
|
||||
removed = true;
|
||||
cleanup_task(task, false, false);
|
||||
cleanup_task(task, upon_death, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return removed;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -776,27 +772,19 @@ cleanup_task(AsyncTask *task, bool upon_death, bool clean_exit) {
|
|||
PT(AsyncTask) hold_task = task;
|
||||
|
||||
task->_state = AsyncTask::S_inactive;
|
||||
task->_chain = NULL;
|
||||
task->_manager = NULL;
|
||||
task->_chain = nullptr;
|
||||
--_num_tasks;
|
||||
--(_manager->_num_tasks);
|
||||
|
||||
_manager->remove_task_by_name(task);
|
||||
|
||||
// Activate the tasks that were waiting for this one to finish.
|
||||
if (upon_death) {
|
||||
pvector<PT(AsyncTask)>::iterator it;
|
||||
for (it = task->_waiting_tasks.begin(); it != task->_waiting_tasks.end(); ++it) {
|
||||
AsyncTask *task = *it;
|
||||
// Note that this task may not be on the same task chain.
|
||||
nassertd(task->_manager == _manager) continue;
|
||||
task->_state = AsyncTask::S_active;
|
||||
task->_chain->_active.push_back(task);
|
||||
--task->_chain->_num_awaiting_tasks;
|
||||
}
|
||||
task->_waiting_tasks.clear();
|
||||
if (upon_death && task->set_future_state(clean_exit ? AsyncFuture::FS_finished
|
||||
: AsyncFuture::FS_cancelled)) {
|
||||
task->notify_done(clean_exit);
|
||||
}
|
||||
|
||||
task->_manager = nullptr;
|
||||
|
||||
if (upon_death) {
|
||||
_manager->_lock.release();
|
||||
task->upon_death(_manager, clean_exit);
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ protected:
|
|||
typedef pvector< PT(AsyncTask) > TaskHeap;
|
||||
|
||||
void do_add(AsyncTask *task);
|
||||
bool do_remove(AsyncTask *task);
|
||||
bool do_remove(AsyncTask *task, bool upon_death=false);
|
||||
void do_wait_for_tasks();
|
||||
void do_cleanup();
|
||||
|
||||
|
|
@ -206,6 +206,7 @@ public:
|
|||
private:
|
||||
static TypeHandle _type_handle;
|
||||
|
||||
friend class AsyncFuture;
|
||||
friend class AsyncTaskChainThread;
|
||||
friend class AsyncTask;
|
||||
friend class AsyncTaskManager;
|
||||
|
|
|
|||
|
|
@ -63,14 +63,15 @@ add_task(AsyncTask *task) {
|
|||
*/
|
||||
bool AsyncTaskCollection::
|
||||
remove_task(AsyncTask *task) {
|
||||
int task_index = -1;
|
||||
for (int i = 0; task_index == -1 && i < (int)_tasks.size(); i++) {
|
||||
size_t task_index = (size_t)-1;
|
||||
for (size_t i = 0; i < _tasks.size(); ++i) {
|
||||
if (_tasks[i] == task) {
|
||||
task_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (task_index == -1) {
|
||||
if (task_index == (size_t)-1) {
|
||||
// The indicated task was not a member of the collection.
|
||||
return false;
|
||||
}
|
||||
|
|
@ -129,12 +130,12 @@ void AsyncTaskCollection::
|
|||
remove_duplicate_tasks() {
|
||||
AsyncTasks new_tasks;
|
||||
|
||||
int num_tasks = get_num_tasks();
|
||||
for (int i = 0; i < num_tasks; i++) {
|
||||
size_t num_tasks = get_num_tasks();
|
||||
for (size_t i = 0; i < num_tasks; i++) {
|
||||
PT(AsyncTask) task = get_task(i);
|
||||
bool duplicated = false;
|
||||
|
||||
for (int j = 0; j < i && !duplicated; j++) {
|
||||
for (size_t j = 0; j < i && !duplicated; j++) {
|
||||
duplicated = (task == get_task(j));
|
||||
}
|
||||
|
||||
|
|
@ -152,7 +153,7 @@ remove_duplicate_tasks() {
|
|||
*/
|
||||
bool AsyncTaskCollection::
|
||||
has_task(AsyncTask *task) const {
|
||||
for (int i = 0; i < get_num_tasks(); i++) {
|
||||
for (size_t i = 0; i < get_num_tasks(); i++) {
|
||||
if (task == get_task(i)) {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -174,8 +175,8 @@ clear() {
|
|||
*/
|
||||
AsyncTask *AsyncTaskCollection::
|
||||
find_task(const string &name) const {
|
||||
int num_tasks = get_num_tasks();
|
||||
for (int i = 0; i < num_tasks; i++) {
|
||||
size_t num_tasks = get_num_tasks();
|
||||
for (size_t i = 0; i < num_tasks; ++i) {
|
||||
AsyncTask *task = get_task(i);
|
||||
if (task->get_name() == name) {
|
||||
return task;
|
||||
|
|
@ -187,7 +188,7 @@ find_task(const string &name) const {
|
|||
/**
|
||||
* Returns the number of AsyncTasks in the collection.
|
||||
*/
|
||||
int AsyncTaskCollection::
|
||||
size_t AsyncTaskCollection::
|
||||
get_num_tasks() const {
|
||||
return _tasks.size();
|
||||
}
|
||||
|
|
@ -196,8 +197,8 @@ get_num_tasks() const {
|
|||
* Returns the nth AsyncTask in the collection.
|
||||
*/
|
||||
AsyncTask *AsyncTaskCollection::
|
||||
get_task(int index) const {
|
||||
nassertr(index >= 0 && index < (int)_tasks.size(), NULL);
|
||||
get_task(size_t index) const {
|
||||
nassertr(index < _tasks.size(), nullptr);
|
||||
|
||||
return _tasks[index];
|
||||
}
|
||||
|
|
@ -206,7 +207,7 @@ get_task(int index) const {
|
|||
* Removes the nth AsyncTask from the collection.
|
||||
*/
|
||||
void AsyncTaskCollection::
|
||||
remove_task(int index) {
|
||||
remove_task(size_t index) {
|
||||
// If the pointer to our internal array is shared by any other
|
||||
// AsyncTaskCollections, we have to copy the array now so we won't
|
||||
// inadvertently modify any of our brethren AsyncTaskCollection objects.
|
||||
|
|
@ -217,7 +218,7 @@ remove_task(int index) {
|
|||
_tasks.v() = old_tasks.v();
|
||||
}
|
||||
|
||||
nassertv(index >= 0 && index < (int)_tasks.size());
|
||||
nassertv(index < _tasks.size());
|
||||
_tasks.erase(_tasks.begin() + index);
|
||||
}
|
||||
|
||||
|
|
@ -226,9 +227,8 @@ remove_task(int index) {
|
|||
* get_task(), but it may be a more convenient way to access it.
|
||||
*/
|
||||
AsyncTask *AsyncTaskCollection::
|
||||
operator [] (int index) const {
|
||||
nassertr(index >= 0 && index < (int)_tasks.size(), NULL);
|
||||
|
||||
operator [] (size_t index) const {
|
||||
nassertr(index < _tasks.size(), nullptr);
|
||||
return _tasks[index];
|
||||
}
|
||||
|
||||
|
|
@ -236,7 +236,7 @@ operator [] (int index) const {
|
|||
* Returns the number of tasks in the collection. This is the same thing as
|
||||
* get_num_tasks().
|
||||
*/
|
||||
int AsyncTaskCollection::
|
||||
size_t AsyncTaskCollection::
|
||||
size() const {
|
||||
return _tasks.size();
|
||||
}
|
||||
|
|
@ -260,7 +260,7 @@ output(ostream &out) const {
|
|||
*/
|
||||
void AsyncTaskCollection::
|
||||
write(ostream &out, int indent_level) const {
|
||||
for (int i = 0; i < get_num_tasks(); i++) {
|
||||
for (size_t i = 0; i < get_num_tasks(); i++) {
|
||||
indent(out, indent_level) << *get_task(i) << "\n";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,12 +41,12 @@ PUBLISHED:
|
|||
|
||||
AsyncTask *find_task(const string &name) const;
|
||||
|
||||
int get_num_tasks() const;
|
||||
AsyncTask *get_task(int index) const;
|
||||
size_t get_num_tasks() const;
|
||||
AsyncTask *get_task(size_t index) const;
|
||||
MAKE_SEQ(get_tasks, get_num_tasks, get_task);
|
||||
void remove_task(int index);
|
||||
AsyncTask *operator [] (int index) const;
|
||||
int size() const;
|
||||
void remove_task(size_t index);
|
||||
AsyncTask *operator [] (size_t index) const;
|
||||
size_t size() const;
|
||||
INLINE void operator += (const AsyncTaskCollection &other);
|
||||
INLINE AsyncTaskCollection operator + (const AsyncTaskCollection &other) const;
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ get_clock() {
|
|||
* Returns the number of tasks that are currently active or sleeping within
|
||||
* the task manager.
|
||||
*/
|
||||
INLINE int AsyncTaskManager::
|
||||
INLINE size_t AsyncTaskManager::
|
||||
get_num_tasks() const {
|
||||
MutexHolder holder(_lock);
|
||||
return _num_tasks;
|
||||
|
|
|
|||
|
|
@ -307,25 +307,20 @@ find_tasks_matching(const GlobPattern &pattern) const {
|
|||
*/
|
||||
bool AsyncTaskManager::
|
||||
remove(AsyncTask *task) {
|
||||
// We pass this up to the multi-task remove() flavor. Do we care about the
|
||||
// tiny cost of creating an AsyncTaskCollection here? Probably not.
|
||||
AsyncTaskCollection tasks;
|
||||
tasks.add_task(task);
|
||||
return remove(tasks) != 0;
|
||||
return task->remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all of the tasks in the AsyncTaskCollection. Returns the number of
|
||||
* tasks removed.
|
||||
*/
|
||||
int AsyncTaskManager::
|
||||
size_t AsyncTaskManager::
|
||||
remove(const AsyncTaskCollection &tasks) {
|
||||
MutexHolder holder(_lock);
|
||||
int num_removed = 0;
|
||||
size_t num_removed = 0;
|
||||
|
||||
int num_tasks = tasks.get_num_tasks();
|
||||
int i;
|
||||
for (i = 0; i < num_tasks; ++i) {
|
||||
size_t num_tasks = tasks.get_num_tasks();
|
||||
for (size_t i = 0; i < num_tasks; ++i) {
|
||||
PT(AsyncTask) task = tasks.get_task(i);
|
||||
|
||||
if (task->_manager != this) {
|
||||
|
|
@ -337,10 +332,7 @@ remove(const AsyncTaskCollection &tasks) {
|
|||
task_cat.debug()
|
||||
<< "Removing " << *task << "\n";
|
||||
}
|
||||
if (task->_chain->do_remove(task)) {
|
||||
_lock.release();
|
||||
task->upon_death(this, false);
|
||||
_lock.acquire();
|
||||
if (task->_chain->do_remove(task, true)) {
|
||||
++num_removed;
|
||||
} else {
|
||||
if (task_cat.is_debug()) {
|
||||
|
|
|
|||
|
|
@ -71,13 +71,13 @@ PUBLISHED:
|
|||
AsyncTaskCollection find_tasks_matching(const GlobPattern &pattern) const;
|
||||
|
||||
bool remove(AsyncTask *task);
|
||||
int remove(const AsyncTaskCollection &tasks);
|
||||
size_t remove(const AsyncTaskCollection &tasks);
|
||||
|
||||
BLOCKING void wait_for_tasks();
|
||||
BLOCKING void stop_threads();
|
||||
void start_threads();
|
||||
|
||||
INLINE int get_num_tasks() const;
|
||||
INLINE size_t get_num_tasks() const;
|
||||
|
||||
AsyncTaskCollection get_tasks() const;
|
||||
AsyncTaskCollection get_active_tasks() const;
|
||||
|
|
@ -126,7 +126,7 @@ protected:
|
|||
typedef ov_set<PT(AsyncTaskChain), IndirectCompareNames<AsyncTaskChain> > TaskChains;
|
||||
TaskChains _task_chains;
|
||||
|
||||
int _num_tasks;
|
||||
size_t _num_tasks;
|
||||
TasksByName _tasks_by_name;
|
||||
PT(ClockObject) _clock;
|
||||
|
||||
|
|
@ -151,6 +151,7 @@ public:
|
|||
private:
|
||||
static TypeHandle _type_handle;
|
||||
|
||||
friend class AsyncFuture;
|
||||
friend class AsyncTaskChain;
|
||||
friend class AsyncTaskChain::AsyncTaskChainThread;
|
||||
friend class AsyncTask;
|
||||
|
|
|
|||
|
|
@ -1,75 +0,0 @@
|
|||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file asyncTask_ext.h
|
||||
* @author rdb
|
||||
* @date 2017-10-29
|
||||
*/
|
||||
|
||||
#include "asyncTask_ext.h"
|
||||
#include "nodePath.h"
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
|
||||
#ifndef CPPPARSER
|
||||
extern struct Dtool_PyTypedObject Dtool_AsyncTask;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Yields continuously until the task has finished.
|
||||
*/
|
||||
static PyObject *gen_next(PyObject *self) {
|
||||
const AsyncTask *request = nullptr;
|
||||
if (!Dtool_Call_ExtractThisPointer(self, Dtool_AsyncTask, (void **)&request)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (request->is_alive()) {
|
||||
// Continue awaiting the result.
|
||||
Py_INCREF(self);
|
||||
return self;
|
||||
} else {
|
||||
// It's done. Do we have a method like result(), eg. in the case of a
|
||||
// ModelLoadRequest? In that case we pass that value into the exception.
|
||||
PyObject *method = PyObject_GetAttrString(self, "result");
|
||||
PyObject *result = nullptr;
|
||||
if (method != nullptr) {
|
||||
if (PyCallable_Check(method)) {
|
||||
result = _PyObject_CallNoArg(method);
|
||||
Py_DECREF(method);
|
||||
if (result == nullptr) {
|
||||
// An exception happened. Pass it on.
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
Py_DECREF(method);
|
||||
}
|
||||
Py_INCREF(PyExc_StopIteration);
|
||||
PyErr_Restore(PyExc_StopIteration, result, nullptr);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a generator that continuously yields an awaitable until the task
|
||||
* has finished. This allows syntax like `model = await loader.load...` to be
|
||||
* used in a Python coroutine.
|
||||
*/
|
||||
PyObject *Extension<AsyncTask>::
|
||||
__await__(PyObject *self) {
|
||||
Dtool_GeneratorWrapper *gen;
|
||||
gen = (Dtool_GeneratorWrapper *)PyType_GenericAlloc(&Dtool_GeneratorWrapper_Type, 0);
|
||||
if (gen != nullptr) {
|
||||
Py_INCREF(self);
|
||||
gen->_base._self = self;
|
||||
gen->_iternext_func = &gen_next;
|
||||
}
|
||||
return (PyObject *)gen;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -12,6 +12,7 @@
|
|||
*/
|
||||
|
||||
#include "config_event.h"
|
||||
#include "asyncFuture.h"
|
||||
#include "asyncTask.h"
|
||||
#include "asyncTaskChain.h"
|
||||
#include "asyncTaskManager.h"
|
||||
|
|
@ -31,6 +32,8 @@ NotifyCategoryDef(event, "");
|
|||
NotifyCategoryDef(task, "");
|
||||
|
||||
ConfigureFn(config_event) {
|
||||
AsyncFuture::init_type();
|
||||
AsyncGatheringFuture::init_type();
|
||||
AsyncTask::init_type();
|
||||
AsyncTaskChain::init_type();
|
||||
AsyncTaskManager::init_type();
|
||||
|
|
|
|||
|
|
@ -27,6 +27,26 @@ EventHandler::
|
|||
EventHandler(EventQueue *ev_queue) : _queue(*ev_queue) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a pending future that will be marked as done when the event is next
|
||||
* fired.
|
||||
*/
|
||||
AsyncFuture *EventHandler::
|
||||
get_future(const string &event_name) {
|
||||
Futures::iterator fi;
|
||||
fi = _futures.find(event_name);
|
||||
|
||||
// If we already have a future, but someone cancelled it, we need to create
|
||||
// a new future instead.
|
||||
if (fi != _futures.end() && !fi->second->cancelled()) {
|
||||
return fi->second;
|
||||
} else {
|
||||
AsyncFuture *fut = new AsyncFuture;
|
||||
_futures[event_name] = fut;
|
||||
return fut;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The main processing loop of the EventHandler. This function must be called
|
||||
* periodically to service events. Walks through each pending event and calls
|
||||
|
|
@ -81,6 +101,18 @@ dispatch_event(const Event *event) {
|
|||
((*cfi).first)(event, (*cfi).second);
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, check for futures that need to be triggered.
|
||||
Futures::iterator fi;
|
||||
fi = _futures.find(event->get_name());
|
||||
|
||||
if (fi != _futures.end()) {
|
||||
AsyncFuture *fut = (*fi).second;
|
||||
if (!fut->done()) {
|
||||
fut->set_result((TypedReferenceCount *)event);
|
||||
}
|
||||
_futures.erase(fi);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
#include "event.h"
|
||||
#include "pt_Event.h"
|
||||
#include "asyncFuture.h"
|
||||
|
||||
#include "pset.h"
|
||||
#include "pmap.h"
|
||||
|
|
@ -41,10 +42,13 @@ public:
|
|||
|
||||
PUBLISHED:
|
||||
explicit EventHandler(EventQueue *ev_queue);
|
||||
~EventHandler() {}
|
||||
|
||||
AsyncFuture *get_future(const string &event_name);
|
||||
|
||||
void process_events();
|
||||
|
||||
virtual void dispatch_event(const Event *);
|
||||
virtual void dispatch_event(const Event *event);
|
||||
|
||||
void write(ostream &out) const;
|
||||
|
||||
|
|
@ -74,9 +78,11 @@ protected:
|
|||
typedef pair<EventCallbackFunction*, void*> CallbackFunction;
|
||||
typedef pset<CallbackFunction> CallbackFunctions;
|
||||
typedef pmap<string, CallbackFunctions> CallbackHooks;
|
||||
typedef pmap<string, PT(AsyncFuture)> Futures;
|
||||
|
||||
Hooks _hooks;
|
||||
CallbackHooks _cbhooks;
|
||||
Futures _futures;
|
||||
EventQueue &_queue;
|
||||
|
||||
static EventHandler *_global_event_handler;
|
||||
|
|
|
|||
|
|
@ -11,13 +11,6 @@
|
|||
* @date 1999-02-08
|
||||
*/
|
||||
|
||||
/**
|
||||
* Defines an EventParameter that stores nothing: the "empty" parameter.
|
||||
*/
|
||||
INLINE EventParameter::
|
||||
EventParameter() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines an EventParameter that stores a pointer to any kind of
|
||||
* TypedWritableReferenceCount object. This is the most general constructor.
|
||||
|
|
|
|||
|
|
@ -34,7 +34,8 @@
|
|||
*/
|
||||
class EXPCL_PANDA_EVENT EventParameter {
|
||||
PUBLISHED:
|
||||
INLINE EventParameter();
|
||||
INLINE EventParameter() DEFAULT_CTOR;
|
||||
INLINE EventParameter(nullptr_t) {};
|
||||
INLINE EventParameter(const TypedWritableReferenceCount *ptr);
|
||||
INLINE EventParameter(const TypedReferenceCount *ptr);
|
||||
INLINE EventParameter(int value);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
#include "asyncFuture.cxx"
|
||||
#include "asyncTask.cxx"
|
||||
#include "asyncTaskChain.cxx"
|
||||
#include "asyncTaskCollection.cxx"
|
||||
|
|
|
|||
|
|
@ -45,17 +45,12 @@ get_owner() const {
|
|||
*/
|
||||
INLINE void PythonTask::
|
||||
set_result(PyObject *result) {
|
||||
// Note that we don't call notify_done() here since the done status will be
|
||||
// automatically notified upon the task's completion.
|
||||
nassertv(is_alive());
|
||||
nassertv(!done());
|
||||
nassertv(_exception == nullptr);
|
||||
Py_INCREF(result);
|
||||
Py_XDECREF(_exc_value);
|
||||
_exc_value = result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as __await__, for backward compatibility with the old coroutine way.
|
||||
*/
|
||||
INLINE PyObject *PythonTask::
|
||||
__iter__(PyObject *self) {
|
||||
return __await__(self);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ TypeHandle PythonTask::_type_handle;
|
|||
|
||||
#ifndef CPPPARSER
|
||||
extern struct Dtool_PyTypedObject Dtool_TypedReferenceCount;
|
||||
extern struct Dtool_PyTypedObject Dtool_AsyncTask;
|
||||
extern struct Dtool_PyTypedObject Dtool_AsyncFuture;
|
||||
extern struct Dtool_PyTypedObject Dtool_PythonTask;
|
||||
#endif
|
||||
|
||||
|
|
@ -45,6 +45,7 @@ PythonTask(PyObject *func_or_coro, const string &name) :
|
|||
_exc_traceback(nullptr),
|
||||
_generator(nullptr),
|
||||
_future_done(nullptr),
|
||||
_ignore_return(false),
|
||||
_retrieved_exception(false) {
|
||||
|
||||
nassertv(func_or_coro != nullptr);
|
||||
|
|
@ -169,9 +170,7 @@ get_args() {
|
|||
}
|
||||
|
||||
this->ref();
|
||||
PyObject *self =
|
||||
DTool_CreatePyInstanceTyped(this, Dtool_TypedReferenceCount,
|
||||
true, false, get_type_index());
|
||||
PyObject *self = DTool_CreatePyInstance(this, Dtool_PythonTask, true, false);
|
||||
PyTuple_SET_ITEM(with_task, num_args, self);
|
||||
return with_task;
|
||||
|
||||
|
|
@ -238,8 +237,8 @@ set_owner(PyObject *owner) {
|
|||
* exception occurred within this task, it is raised instead.
|
||||
*/
|
||||
PyObject *PythonTask::
|
||||
result() const {
|
||||
nassertr(!is_alive(), nullptr);
|
||||
get_result() const {
|
||||
nassertr(done(), nullptr);
|
||||
|
||||
if (_exception == nullptr) {
|
||||
// The result of the call is stored in _exc_value.
|
||||
|
|
@ -273,49 +272,6 @@ exception() const {
|
|||
}
|
||||
}*/
|
||||
|
||||
/**
|
||||
* Returns an iterator that continuously yields an awaitable until the task
|
||||
* has finished.
|
||||
*/
|
||||
PyObject *PythonTask::
|
||||
__await__(PyObject *self) {
|
||||
Dtool_GeneratorWrapper *gen;
|
||||
gen = (Dtool_GeneratorWrapper *)PyType_GenericAlloc(&Dtool_GeneratorWrapper_Type, 0);
|
||||
if (gen != nullptr) {
|
||||
Py_INCREF(self);
|
||||
gen->_base._self = self;
|
||||
gen->_iternext_func = &gen_next;
|
||||
}
|
||||
return (PyObject *)gen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Yields continuously until a task has finished.
|
||||
*/
|
||||
PyObject *PythonTask::
|
||||
gen_next(PyObject *self) {
|
||||
const PythonTask *task = nullptr;
|
||||
if (!Dtool_Call_ExtractThisPointer(self, Dtool_PythonTask, (void **)&task)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (task->is_alive()) {
|
||||
Py_INCREF(self);
|
||||
return self;
|
||||
} else if (task->_exception != nullptr) {
|
||||
task->_retrieved_exception = true;
|
||||
Py_INCREF(task->_exception);
|
||||
Py_INCREF(task->_exc_value);
|
||||
Py_INCREF(task->_exc_traceback);
|
||||
PyErr_Restore(task->_exception, task->_exc_value, task->_exc_traceback);
|
||||
return nullptr;
|
||||
} else {
|
||||
// The result of the call is stored in _exc_value.
|
||||
PyErr_SetObject(PyExc_StopIteration, task->_exc_value);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps from an expression like "task.attr_name = v". This is customized here
|
||||
* so we can support some traditional task interfaces that supported directly
|
||||
|
|
@ -620,36 +576,39 @@ do_python_task() {
|
|||
}
|
||||
}
|
||||
|
||||
// Tell the task chain we want to kill ourselves. It doesn't really
|
||||
// matter what we return if we set S_servicing_removed. If we don't
|
||||
// set it, however, it will think this was a clean exit.
|
||||
_manager->_lock.acquire();
|
||||
_state = S_servicing_removed;
|
||||
_manager->_lock.release();
|
||||
return DS_interrupt;
|
||||
// Tell the task chain we want to kill ourselves. We indicate this is
|
||||
// a "clean exit" because we still want to run the done callbacks on
|
||||
// exception.
|
||||
return DS_done;
|
||||
}
|
||||
|
||||
} else if (DtoolCanThisBeAPandaInstance(result)) {
|
||||
// We are waiting for a task to finish.
|
||||
void *ptr = ((Dtool_PyInstDef *)result)->_My_Type->_Dtool_UpcastInterface(result, &Dtool_AsyncTask);
|
||||
if (ptr != nullptr) {
|
||||
} else if (DtoolInstance_Check(result)) {
|
||||
// We are waiting for an AsyncFuture (eg. other task) to finish.
|
||||
AsyncFuture *fut = (AsyncFuture *)DtoolInstance_UPCAST(result, Dtool_AsyncFuture);
|
||||
if (fut != nullptr) {
|
||||
// Suspend execution of this task until this other task has completed.
|
||||
AsyncTask *task = (AsyncTask *)ptr;
|
||||
AsyncTaskManager *manager = task->_manager;
|
||||
nassertr(manager != nullptr, DS_interrupt);
|
||||
nassertr(manager == _manager, DS_interrupt);
|
||||
manager->_lock.acquire();
|
||||
if (task != (AsyncTask *)this) {
|
||||
if (task->is_alive()) {
|
||||
if (fut != (AsyncFuture *)this && !fut->done()) {
|
||||
if (fut->is_task()) {
|
||||
// This is actually a task, do we need to schedule it with the
|
||||
// manager? This allows doing something like
|
||||
// await Task.pause(1.0)
|
||||
// directly instead of having to do:
|
||||
// await taskMgr.add(Task.pause(1.0))
|
||||
AsyncTask *task = (AsyncTask *)fut;
|
||||
_manager->add(task);
|
||||
}
|
||||
if (fut->add_waiting_task(this)) {
|
||||
if (task_cat.is_debug()) {
|
||||
task_cat.debug()
|
||||
<< *this << " is now awaiting <" << *task << ">.\n";
|
||||
<< *this << " is now awaiting <" << *fut << ">.\n";
|
||||
}
|
||||
task->_waiting_tasks.push_back(this);
|
||||
} else {
|
||||
// The task is already done. Continue at next opportunity.
|
||||
if (task_cat.is_debug()) {
|
||||
task_cat.debug()
|
||||
<< *this << " would await <" << *fut << ">, were it not already done.\n";
|
||||
}
|
||||
Py_DECREF(result);
|
||||
manager->_lock.release();
|
||||
return DS_cont;
|
||||
}
|
||||
} else {
|
||||
|
|
@ -658,14 +617,12 @@ do_python_task() {
|
|||
task_cat.error()
|
||||
<< *this << " cannot await itself\n";
|
||||
}
|
||||
task->_manager->_lock.release();
|
||||
Py_DECREF(result);
|
||||
return DS_await;
|
||||
}
|
||||
|
||||
} else {
|
||||
// We are waiting for a future to finish. We currently implement this
|
||||
// by simply checking every frame whether the future is done.
|
||||
// We are waiting for a non-Panda future to finish. We currently
|
||||
// implement this by checking every frame whether the future is done.
|
||||
PyObject *check = PyObject_GetAttrString(result, "_asyncio_future_blocking");
|
||||
if (check != nullptr && check != Py_None) {
|
||||
Py_DECREF(check);
|
||||
|
|
@ -680,7 +637,7 @@ do_python_task() {
|
|||
if (task_cat.is_debug()) {
|
||||
PyObject *str = PyObject_ASCII(result);
|
||||
task_cat.debug()
|
||||
<< *this << " is now awaiting " << PyUnicode_AsUTF8(str) << ".\n";
|
||||
<< *this << " is now polling " << PyUnicode_AsUTF8(str) << ".done()\n";
|
||||
Py_DECREF(str);
|
||||
}
|
||||
#endif
|
||||
|
|
@ -707,7 +664,7 @@ do_python_task() {
|
|||
return DS_interrupt;
|
||||
}
|
||||
|
||||
if (result == Py_None) {
|
||||
if (result == Py_None || _ignore_return) {
|
||||
Py_DECREF(result);
|
||||
return DS_done;
|
||||
}
|
||||
|
|
@ -746,6 +703,24 @@ do_python_task() {
|
|||
}
|
||||
}
|
||||
|
||||
// This is unfortunate, but some are returning task.done, which nowadays
|
||||
// conflicts with the AsyncFuture method. Check if that is being returned.
|
||||
PyMethodDef *meth = nullptr;
|
||||
if (PyCFunction_Check(result)) {
|
||||
meth = ((PyCFunctionObject *)result)->m_ml;
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
} else if (Py_TYPE(result) == &PyMethodDescr_Type) {
|
||||
#else
|
||||
} else if (strcmp(Py_TYPE(result)->tp_name, "method_descriptor") == 0) {
|
||||
#endif
|
||||
meth = ((PyMethodDescrObject *)result)->d_method;
|
||||
}
|
||||
|
||||
if (meth != nullptr && strcmp(meth->ml_name, "done") == 0) {
|
||||
Py_DECREF(result);
|
||||
return DS_done;
|
||||
}
|
||||
|
||||
ostringstream strm;
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
PyObject *str = PyObject_ASCII(result);
|
||||
|
|
@ -886,15 +861,10 @@ void PythonTask::
|
|||
call_function(PyObject *function) {
|
||||
if (function != Py_None) {
|
||||
this->ref();
|
||||
PyObject *self =
|
||||
DTool_CreatePyInstanceTyped(this, Dtool_TypedReferenceCount,
|
||||
true, false, get_type_index());
|
||||
PyObject *args = Py_BuildValue("(O)", self);
|
||||
Py_DECREF(self);
|
||||
|
||||
PyObject *result = PyObject_CallObject(function, args);
|
||||
PyObject *self = DTool_CreatePyInstance(this, Dtool_PythonTask, true, false);
|
||||
PyObject *result = PyObject_CallFunctionObjArgs(function, self, nullptr);
|
||||
Py_XDECREF(result);
|
||||
Py_DECREF(args);
|
||||
Py_DECREF(self);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,12 +20,13 @@
|
|||
|
||||
#ifdef HAVE_PYTHON
|
||||
#include "py_panda.h"
|
||||
#include "extension.h"
|
||||
|
||||
/**
|
||||
* This class exists to allow association of a Python function or coroutine
|
||||
* with the AsyncTaskManager.
|
||||
*/
|
||||
class PythonTask : public AsyncTask {
|
||||
class PythonTask FINAL : public AsyncTask {
|
||||
PUBLISHED:
|
||||
PythonTask(PyObject *function = Py_None, const string &name = string());
|
||||
virtual ~PythonTask();
|
||||
|
|
@ -44,12 +45,14 @@ PUBLISHED:
|
|||
INLINE PyObject *get_owner() const;
|
||||
|
||||
INLINE void set_result(PyObject *result);
|
||||
PyObject *result() const;
|
||||
|
||||
public:
|
||||
// This is exposed only for the result() function in asyncFuture_ext.cxx
|
||||
// to use, which is why it is not published.
|
||||
PyObject *get_result() const;
|
||||
//PyObject *exception() const;
|
||||
|
||||
static PyObject *__await__(PyObject *self);
|
||||
INLINE static PyObject *__iter__(PyObject *self);
|
||||
|
||||
PUBLISHED:
|
||||
int __setattr__(PyObject *self, PyObject *attr, PyObject *v);
|
||||
int __delattr__(PyObject *self, PyObject *attr);
|
||||
PyObject *__getattr__(PyObject *attr) const;
|
||||
|
|
@ -101,8 +104,6 @@ protected:
|
|||
virtual void upon_death(AsyncTaskManager *manager, bool clean_exit);
|
||||
|
||||
private:
|
||||
static PyObject *gen_next(PyObject *self);
|
||||
|
||||
void register_to_owner();
|
||||
void unregister_from_owner();
|
||||
void call_owner_method(const char *method_name);
|
||||
|
|
@ -122,9 +123,12 @@ private:
|
|||
PyObject *_future_done;
|
||||
|
||||
bool _append_task;
|
||||
bool _ignore_return;
|
||||
bool _registered_to_owner;
|
||||
mutable bool _retrieved_exception;
|
||||
|
||||
friend class Extension<AsyncFuture>;
|
||||
|
||||
public:
|
||||
static TypeHandle get_class_type() {
|
||||
return _type_handle;
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ __init__(PyObject *self, PyObject *source) {
|
|||
|
||||
// Now construct the internal list by copying the elements one-at-a-time
|
||||
// from Python.
|
||||
PyObject *dict = ((Dtool_PyInstDef *)self)->_My_Type->_PyType.tp_dict;
|
||||
PyObject *dict = DtoolInstance_TYPE(self)->_PyType.tp_dict;
|
||||
PyObject *push_back = PyDict_GetItemString(dict, "push_back");
|
||||
if (push_back == NULL) {
|
||||
PyErr_BadArgument();
|
||||
|
|
@ -105,7 +105,7 @@ __init__(PyObject *self, PyObject *source) {
|
|||
}
|
||||
|
||||
// We need to initialize the this pointer before we can call push_back.
|
||||
((Dtool_PyInstDef *)self)->_ptr_to_object = (void *)this->_this;
|
||||
DtoolInstance_INIT_PTR(self, this->_this);
|
||||
|
||||
Py_ssize_t size = PySequence_Size(source);
|
||||
this->_this->reserve(size);
|
||||
|
|
@ -203,34 +203,37 @@ set_data(PyObject *data) {
|
|||
}
|
||||
|
||||
PyBuffer_Release(&view);
|
||||
} else {
|
||||
Dtool_Raise_TypeError("PointerToArray.set_data() requires a buffer object");
|
||||
return;
|
||||
}
|
||||
#else
|
||||
// In Python 2.5 we didn't have the new buffer protocol, only str.
|
||||
if (PyString_CheckExact(data)) {
|
||||
int size = PyString_Size(data);
|
||||
if (size % sizeof(Element) != 0) {
|
||||
#endif
|
||||
|
||||
// In Python 2, there was also an older buffer protocol, supported by eg.
|
||||
// str and array objects.
|
||||
#if PY_MAJOR_VERSION < 3
|
||||
// The old, deprecated buffer interface, as used by eg. the array module.
|
||||
const void *buffer;
|
||||
Py_ssize_t buffer_len;
|
||||
if (!PyUnicode_CheckExact(data) &&
|
||||
PyObject_AsReadBuffer(data, &buffer, &buffer_len) == 0) {
|
||||
if (buffer_len % sizeof(Element) != 0) {
|
||||
PyErr_Format(PyExc_ValueError,
|
||||
"str object is not a multiple of %zu bytes",
|
||||
"byte buffer is not a multiple of %zu bytes",
|
||||
sizeof(Element));
|
||||
return;
|
||||
}
|
||||
|
||||
int num_elements = size / sizeof(Element);
|
||||
this->_this->insert(this->_this->begin(), num_elements, Element());
|
||||
|
||||
// Hope there aren't any constructors or destructors involved here.
|
||||
if (size != 0) {
|
||||
const char *ptr = PyString_AsString(data);
|
||||
memcpy(this->_this->p(), ptr, size);
|
||||
if (buffer_len > 0) {
|
||||
this->_this->resize(buffer_len / sizeof(Element));
|
||||
memcpy(this->_this->p(), buffer, buffer_len);
|
||||
} else {
|
||||
this->_this->clear();
|
||||
}
|
||||
} else {
|
||||
Dtool_Raise_TypeError("PointerToArray.set_data() requires a str");
|
||||
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
Dtool_Raise_TypeError("PointerToArray.set_data() requires a buffer object");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -213,12 +213,20 @@ begin_frame(FrameMode mode, Thread *current_thread) {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!_host->begin_frame(FM_parasite, current_thread)) {
|
||||
if (GLCAT.is_debug()) {
|
||||
GLCAT.debug()
|
||||
<< get_name() << "'s host is not ready\n";
|
||||
if (_host != nullptr) {
|
||||
if (!_host->begin_frame(FM_parasite, current_thread)) {
|
||||
if (GLCAT.is_debug()) {
|
||||
GLCAT.debug()
|
||||
<< get_name() << "'s host is not ready\n";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// We don't have a host window, which is possible for CocoaGraphicsBuffer.
|
||||
_gsg->set_current_properties(&get_fb_properties());
|
||||
if (!_gsg->begin_frame(current_thread)) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Figure out the desired size of the buffer.
|
||||
|
|
@ -235,7 +243,7 @@ begin_frame(FrameMode mode, Thread *current_thread) {
|
|||
}
|
||||
}
|
||||
if (_creation_flags & GraphicsPipe::BF_size_track_host) {
|
||||
if (_host->get_size() != _size) {
|
||||
if (_host != nullptr && _host->get_size() != _size) {
|
||||
// We also need to rebuild if we need to change size.
|
||||
_needs_rebuild = true;
|
||||
}
|
||||
|
|
@ -356,7 +364,7 @@ rebuild_bitplanes() {
|
|||
|
||||
// Calculate bitplane size. This can be larger than the buffer.
|
||||
if (_creation_flags & GraphicsPipe::BF_size_track_host) {
|
||||
if (_host->get_size() != _size) {
|
||||
if (_host != nullptr && _host->get_size() != _size) {
|
||||
set_size_and_recalc(_host->get_x_size(),
|
||||
_host->get_y_size());
|
||||
}
|
||||
|
|
@ -1253,7 +1261,11 @@ end_frame(FrameMode mode, Thread *current_thread) {
|
|||
generate_mipmaps();
|
||||
}
|
||||
|
||||
_host->end_frame(FM_parasite, current_thread);
|
||||
if (_host != nullptr) {
|
||||
_host->end_frame(FM_parasite, current_thread);
|
||||
} else {
|
||||
glgsg->end_frame(current_thread);
|
||||
}
|
||||
|
||||
if (mode == FM_render) {
|
||||
trigger_flip();
|
||||
|
|
@ -1315,8 +1327,11 @@ bool CLP(GraphicsBuffer)::
|
|||
open_buffer() {
|
||||
report_my_gl_errors();
|
||||
|
||||
// Double check that we have a host
|
||||
nassertr(_host != 0, false);
|
||||
// Double check that we have a valid gsg
|
||||
nassertr(_gsg != nullptr, false);
|
||||
if (!_gsg->is_valid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Count total color buffers.
|
||||
int totalcolor =
|
||||
|
|
@ -1491,8 +1506,10 @@ open_buffer() {
|
|||
_fb_properties.set_back_buffers(0);
|
||||
_fb_properties.set_indexed_color(0);
|
||||
_fb_properties.set_rgb_color(1);
|
||||
_fb_properties.set_force_hardware(_host->get_fb_properties().get_force_hardware());
|
||||
_fb_properties.set_force_software(_host->get_fb_properties().get_force_software());
|
||||
if (_host != nullptr) {
|
||||
_fb_properties.set_force_hardware(_host->get_fb_properties().get_force_hardware());
|
||||
_fb_properties.set_force_software(_host->get_fb_properties().get_force_software());
|
||||
}
|
||||
|
||||
_is_valid = true;
|
||||
_needs_rebuild = true;
|
||||
|
|
@ -1508,7 +1525,7 @@ open_buffer() {
|
|||
*/
|
||||
GraphicsOutput *CLP(GraphicsBuffer)::
|
||||
get_host() {
|
||||
return _host;
|
||||
return (_host != nullptr) ? _host : this;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1699,7 +1716,7 @@ report_my_errors(int line, const char *file) {
|
|||
*/
|
||||
void CLP(GraphicsBuffer)::
|
||||
check_host_valid() {
|
||||
if ((_host == 0)||(!_host->is_valid())) {
|
||||
if (_host != nullptr && !_host->is_valid()) {
|
||||
_rb_data_size_bytes = 0;
|
||||
if (_rb_context != NULL) {
|
||||
// We must delete this object first, because when the GSG destructs, so
|
||||
|
|
|
|||
|
|
@ -86,8 +86,6 @@ protected:
|
|||
|
||||
void report_my_errors(int line, const char *file);
|
||||
|
||||
private:
|
||||
|
||||
void bind_slot(int layer, bool rb_resize, Texture **attach,
|
||||
RenderTexturePlane plane, GLenum attachpoint);
|
||||
void bind_slot_multisample(bool rb_resize, Texture **attach,
|
||||
|
|
|
|||
|
|
@ -155,7 +155,11 @@ null_glBlendColor(GLclampf, GLclampf, GLclampf, GLclampf) {
|
|||
// drawing GUIs and such.
|
||||
static const string default_vshader =
|
||||
#ifndef OPENGLES
|
||||
#ifdef __APPLE__ // Apple's GL 3.2 contexts require at least GLSL 1.50.
|
||||
"#version 150\n"
|
||||
#else
|
||||
"#version 130\n"
|
||||
#endif
|
||||
"in vec4 p3d_Vertex;\n"
|
||||
"in vec4 p3d_Color;\n"
|
||||
"in vec2 p3d_MultiTexCoord0;\n"
|
||||
|
|
@ -179,7 +183,11 @@ static const string default_vshader =
|
|||
|
||||
static const string default_fshader =
|
||||
#ifndef OPENGLES
|
||||
#ifdef __APPLE__ // Apple's GL 3.2 contexts require at least GLSL 1.50.
|
||||
"#version 150\n"
|
||||
#else
|
||||
"#version 130\n"
|
||||
#endif
|
||||
"in vec2 texcoord;\n"
|
||||
"in vec4 color;\n"
|
||||
"out vec4 p3d_FragColor;\n"
|
||||
|
|
@ -6455,6 +6463,15 @@ framebuffer_copy_to_ram(Texture *tex, int view, int z,
|
|||
}
|
||||
break;
|
||||
|
||||
case Texture::F_depth_component16:
|
||||
component_type = Texture::T_unsigned_short;
|
||||
break;
|
||||
|
||||
case Texture::F_depth_component24:
|
||||
case Texture::F_depth_component32:
|
||||
component_type = Texture::T_float;
|
||||
break;
|
||||
|
||||
default:
|
||||
if (_current_properties->get_srgb_color()) {
|
||||
if (_current_properties->get_alpha_bits()) {
|
||||
|
|
@ -12314,20 +12331,20 @@ upload_texture_image(CLP(TextureContext) *gtc, bool needs_reload,
|
|||
if (_supports_clear_texture) {
|
||||
// We can do that with the convenient glClearTexImage
|
||||
// function.
|
||||
string clear_data = tex->get_clear_data();
|
||||
vector_uchar clear_data = tex->get_clear_data();
|
||||
|
||||
_glClearTexImage(gtc->_index, n - mipmap_bias, external_format,
|
||||
component_type, (void *)clear_data.data());
|
||||
component_type, (void *)&clear_data[0]);
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if (_supports_clear_buffer) {
|
||||
// For buffer textures we need to clear the underlying
|
||||
// storage.
|
||||
string clear_data = tex->get_clear_data();
|
||||
vector_uchar clear_data = tex->get_clear_data();
|
||||
|
||||
_glClearBufferData(GL_TEXTURE_BUFFER, internal_format, external_format,
|
||||
component_type, (const void *)clear_data.data());
|
||||
component_type, (const void *)&clear_data[0]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,15 +16,16 @@
|
|||
*/
|
||||
INLINE AnimateVerticesRequest::
|
||||
AnimateVerticesRequest(GeomVertexData *geom_vertex_data) :
|
||||
_geom_vertex_data(geom_vertex_data),
|
||||
_is_ready(false)
|
||||
_geom_vertex_data(geom_vertex_data)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this request has completed, false if it is still pending.
|
||||
* Equivalent to `req.done() and not req.cancelled()`.
|
||||
* @see done()
|
||||
*/
|
||||
INLINE bool AnimateVerticesRequest::
|
||||
is_ready() const {
|
||||
return _is_ready;
|
||||
return (FutureState)AtomicAdjust::get(_future_state) == FS_finished;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ do_task() {
|
|||
// There is no need to store or return a result. The GeomVertexData caches
|
||||
// the result and it will be used later in the rendering process.
|
||||
_geom_vertex_data->animate_vertices(true, current_thread);
|
||||
_is_ready = true;
|
||||
|
||||
// Don't continue the task; we're done.
|
||||
return AsyncTask::DS_done;
|
||||
|
|
|
|||
|
|
@ -40,11 +40,10 @@ PUBLISHED:
|
|||
INLINE bool is_ready() const;
|
||||
|
||||
protected:
|
||||
virtual AsyncTask::DoneStatus do_task();
|
||||
virtual AsyncTask::DoneStatus do_task();
|
||||
|
||||
private:
|
||||
PT(GeomVertexData) _geom_vertex_data;
|
||||
bool _is_ready;
|
||||
|
||||
public:
|
||||
static TypeHandle get_class_type() {
|
||||
|
|
|
|||
|
|
@ -572,6 +572,7 @@ ConfigureFn(config_gobj) {
|
|||
ParamTextureImage::init_type();
|
||||
ParamTextureSampler::init_type();
|
||||
PerspectiveLens::init_type();
|
||||
PreparedGraphicsObjects::EnqueuedObject::init_type();
|
||||
QueryContext::init_type();
|
||||
SamplerContext::init_type();
|
||||
SamplerState::init_type();
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@
|
|||
#include "config_gobj.h"
|
||||
#include "throw_event.h"
|
||||
|
||||
TypeHandle PreparedGraphicsObjects::EnqueuedObject::_type_handle;
|
||||
|
||||
int PreparedGraphicsObjects::_name_index = 0;
|
||||
|
||||
/**
|
||||
|
|
@ -191,7 +193,25 @@ void PreparedGraphicsObjects::
|
|||
enqueue_texture(Texture *tex) {
|
||||
ReMutexHolder holder(_lock);
|
||||
|
||||
_enqueued_textures.insert(tex);
|
||||
_enqueued_textures.insert(EnqueuedTextures::value_type(tex, nullptr));
|
||||
}
|
||||
|
||||
/**
|
||||
* Like enqueue_texture, but returns an AsyncFuture that can be used to query
|
||||
* the status of the texture's preparation.
|
||||
*/
|
||||
PT(PreparedGraphicsObjects::EnqueuedObject) PreparedGraphicsObjects::
|
||||
enqueue_texture_future(Texture *tex) {
|
||||
ReMutexHolder holder(_lock);
|
||||
|
||||
pair<EnqueuedTextures::iterator, bool> result =
|
||||
_enqueued_textures.insert(EnqueuedTextures::value_type(tex, nullptr));
|
||||
if (result.first->second == nullptr) {
|
||||
result.first->second = new EnqueuedObject(this, tex);
|
||||
}
|
||||
PT(EnqueuedObject) fut = result.first->second;
|
||||
nassertr(!fut->cancelled(), fut)
|
||||
return fut;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -220,6 +240,9 @@ dequeue_texture(Texture *tex) {
|
|||
|
||||
EnqueuedTextures::iterator qi = _enqueued_textures.find(tex);
|
||||
if (qi != _enqueued_textures.end()) {
|
||||
if (qi->second != nullptr) {
|
||||
qi->second->notify_removed();
|
||||
}
|
||||
_enqueued_textures.erase(qi);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -291,6 +314,17 @@ release_all_textures() {
|
|||
}
|
||||
|
||||
_prepared_textures.clear();
|
||||
|
||||
// Mark any futures as cancelled.
|
||||
EnqueuedTextures::iterator qti;
|
||||
for (qti = _enqueued_textures.begin();
|
||||
qti != _enqueued_textures.end();
|
||||
++qti) {
|
||||
if (qti->second != nullptr) {
|
||||
qti->second->notify_removed();
|
||||
}
|
||||
}
|
||||
|
||||
_enqueued_textures.clear();
|
||||
|
||||
return num_textures;
|
||||
|
|
@ -665,10 +699,28 @@ prepare_geom_now(Geom *geom, GraphicsStateGuardianBase *gsg) {
|
|||
* when the GSG is next ready to do this (presumably at the next frame).
|
||||
*/
|
||||
void PreparedGraphicsObjects::
|
||||
enqueue_shader(Shader *se) {
|
||||
enqueue_shader(Shader *shader) {
|
||||
ReMutexHolder holder(_lock);
|
||||
|
||||
_enqueued_shaders.insert(se);
|
||||
_enqueued_shaders.insert(EnqueuedShaders::value_type(shader, nullptr));
|
||||
}
|
||||
|
||||
/**
|
||||
* Like enqueue_shader, but returns an AsyncFuture that can be used to query
|
||||
* the status of the shader's preparation.
|
||||
*/
|
||||
PT(PreparedGraphicsObjects::EnqueuedObject) PreparedGraphicsObjects::
|
||||
enqueue_shader_future(Shader *shader) {
|
||||
ReMutexHolder holder(_lock);
|
||||
|
||||
pair<EnqueuedShaders::iterator, bool> result =
|
||||
_enqueued_shaders.insert(EnqueuedShaders::value_type(shader, nullptr));
|
||||
if (result.first->second == nullptr) {
|
||||
result.first->second = new EnqueuedObject(this, shader);
|
||||
}
|
||||
PT(EnqueuedObject) fut = result.first->second;
|
||||
nassertr(!fut->cancelled(), fut)
|
||||
return fut;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -697,6 +749,9 @@ dequeue_shader(Shader *se) {
|
|||
|
||||
EnqueuedShaders::iterator qi = _enqueued_shaders.find(se);
|
||||
if (qi != _enqueued_shaders.end()) {
|
||||
if (qi->second != nullptr) {
|
||||
qi->second->notify_removed();
|
||||
}
|
||||
_enqueued_shaders.erase(qi);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -759,6 +814,17 @@ release_all_shaders() {
|
|||
}
|
||||
|
||||
_prepared_shaders.clear();
|
||||
|
||||
// Mark any futures as cancelled.
|
||||
EnqueuedShaders::iterator qsi;
|
||||
for (qsi = _enqueued_shaders.begin();
|
||||
qsi != _enqueued_shaders.end();
|
||||
++qsi) {
|
||||
if (qsi->second != nullptr) {
|
||||
qsi->second->notify_removed();
|
||||
}
|
||||
}
|
||||
|
||||
_enqueued_shaders.clear();
|
||||
|
||||
return num_shaders;
|
||||
|
|
@ -1358,6 +1424,73 @@ prepare_shader_buffer_now(ShaderBuffer *data, GraphicsStateGuardianBase *gsg) {
|
|||
return bc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new future for the given object.
|
||||
*/
|
||||
PreparedGraphicsObjects::EnqueuedObject::
|
||||
EnqueuedObject(PreparedGraphicsObjects *pgo, TypedWritableReferenceCount *object) :
|
||||
_pgo(pgo),
|
||||
_object(object) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that the preparation request is done.
|
||||
*/
|
||||
void PreparedGraphicsObjects::EnqueuedObject::
|
||||
set_result(SavedContext *context) {
|
||||
nassertv(!done());
|
||||
AsyncFuture::set_result(context);
|
||||
_pgo = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PreparedGraphicsObjects to indicate that the preparation request
|
||||
* has been cancelled.
|
||||
*/
|
||||
void PreparedGraphicsObjects::EnqueuedObject::
|
||||
notify_removed() {
|
||||
_pgo = nullptr;
|
||||
nassertv_always(AsyncFuture::cancel());
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels the pending preparation request. Has no effect if the preparation
|
||||
* is already complete or was already cancelled.
|
||||
*/
|
||||
bool PreparedGraphicsObjects::EnqueuedObject::
|
||||
cancel() {
|
||||
PreparedGraphicsObjects *pgo = _pgo;
|
||||
if (_object == nullptr || pgo == nullptr) {
|
||||
nassertr(done(), false);
|
||||
return false;
|
||||
}
|
||||
|
||||
// We don't upcall here, because the dequeue function will end up calling
|
||||
// notify_removed().
|
||||
_result = nullptr;
|
||||
_pgo = nullptr;
|
||||
|
||||
if (_object->is_of_type(Texture::get_class_type())) {
|
||||
return pgo->dequeue_texture((Texture *)_object.p());
|
||||
|
||||
} else if (_object->is_of_type(Geom::get_class_type())) {
|
||||
return pgo->dequeue_geom((Geom *)_object.p());
|
||||
|
||||
} else if (_object->is_of_type(Shader::get_class_type())) {
|
||||
return pgo->dequeue_shader((Shader *)_object.p());
|
||||
|
||||
} else if (_object->is_of_type(GeomVertexArrayData::get_class_type())) {
|
||||
return pgo->dequeue_vertex_buffer((GeomVertexArrayData *)_object.p());
|
||||
|
||||
} else if (_object->is_of_type(GeomPrimitive::get_class_type())) {
|
||||
return pgo->dequeue_index_buffer((GeomPrimitive *)_object.p());
|
||||
|
||||
} else if (_object->is_of_type(ShaderBuffer::get_class_type())) {
|
||||
return pgo->dequeue_shader_buffer((ShaderBuffer *)_object.p());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called by the GraphicsStateGuardian to indicate that it is about to
|
||||
* begin processing of the frame.
|
||||
|
|
@ -1446,11 +1579,15 @@ begin_frame(GraphicsStateGuardianBase *gsg, Thread *current_thread) {
|
|||
for (qti = _enqueued_textures.begin();
|
||||
qti != _enqueued_textures.end();
|
||||
++qti) {
|
||||
Texture *tex = (*qti);
|
||||
Texture *tex = qti->first;
|
||||
TextureContext *first_tc = nullptr;
|
||||
for (int view = 0; view < tex->get_num_views(); ++view) {
|
||||
TextureContext *tc = tex->prepare_now(view, this, gsg);
|
||||
if (tc != (TextureContext *)NULL) {
|
||||
if (tc != nullptr) {
|
||||
gsg->update_texture(tc, true);
|
||||
if (view == 0 && qti->second != nullptr) {
|
||||
qti->second->set_result(tc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1481,8 +1618,11 @@ begin_frame(GraphicsStateGuardianBase *gsg, Thread *current_thread) {
|
|||
for (qsi = _enqueued_shaders.begin();
|
||||
qsi != _enqueued_shaders.end();
|
||||
++qsi) {
|
||||
Shader *shader = (*qsi);
|
||||
shader->prepare_now(this, gsg);
|
||||
Shader *shader = qsi->first;
|
||||
ShaderContext *sc = shader->prepare_now(this, gsg);
|
||||
if (qti->second != nullptr) {
|
||||
qti->second->set_result(sc);
|
||||
}
|
||||
}
|
||||
|
||||
_enqueued_shaders.clear();
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
#include "reMutex.h"
|
||||
#include "bufferResidencyTracker.h"
|
||||
#include "adaptiveLru.h"
|
||||
#include "asyncFuture.h"
|
||||
|
||||
class TextureContext;
|
||||
class SamplerContext;
|
||||
|
|
@ -38,6 +39,7 @@ class VertexBufferContext;
|
|||
class IndexBufferContext;
|
||||
class BufferContext;
|
||||
class GraphicsStateGuardianBase;
|
||||
class SavedContext;
|
||||
|
||||
/**
|
||||
* A table of objects that are saved within the graphics context for reference
|
||||
|
|
@ -158,6 +160,56 @@ PUBLISHED:
|
|||
GraphicsStateGuardianBase *gsg);
|
||||
|
||||
public:
|
||||
/**
|
||||
* This is a handle to an enqueued object, from which the result can be
|
||||
* obtained upon completion.
|
||||
*/
|
||||
class EXPCL_PANDA_GOBJ EnqueuedObject FINAL : public AsyncFuture {
|
||||
public:
|
||||
EnqueuedObject(PreparedGraphicsObjects *pgo, TypedWritableReferenceCount *object);
|
||||
|
||||
TypedWritableReferenceCount *get_object() { return _object.p(); }
|
||||
SavedContext *get_result() { return (SavedContext *)AsyncFuture::get_result(); }
|
||||
void set_result(SavedContext *result);
|
||||
|
||||
void notify_removed();
|
||||
virtual bool cancel() FINAL;
|
||||
|
||||
PUBLISHED:
|
||||
MAKE_PROPERTY(object, get_object);
|
||||
|
||||
private:
|
||||
PreparedGraphicsObjects *_pgo;
|
||||
PT(TypedWritableReferenceCount) const _object;
|
||||
|
||||
public:
|
||||
static TypeHandle get_class_type() {
|
||||
return _type_handle;
|
||||
}
|
||||
static void init_type() {
|
||||
AsyncFuture::init_type();
|
||||
register_type(_type_handle, "EnqueuedObject",
|
||||
AsyncFuture::get_class_type());
|
||||
}
|
||||
virtual TypeHandle get_type() const {
|
||||
return get_class_type();
|
||||
}
|
||||
virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
|
||||
|
||||
private:
|
||||
static TypeHandle _type_handle;
|
||||
};
|
||||
|
||||
// These are variations of enqueue_xxx that also return a future. They are
|
||||
// used to implement texture->prepare(), etc. They are only marked public
|
||||
// so we don't have to define a whole bunch of friend classes.
|
||||
PT(EnqueuedObject) enqueue_texture_future(Texture *tex);
|
||||
//PT(EnqueuedObject) enqueue_geom_future(Geom *geom);
|
||||
PT(EnqueuedObject) enqueue_shader_future(Shader *shader);
|
||||
//PT(EnqueuedObject) enqueue_vertex_buffer_future(GeomVertexArrayData *data);
|
||||
//PT(EnqueuedObject) enqueue_index_buffer_future(GeomPrimitive *data);
|
||||
//PT(EnqueuedObject) enqueue_shader_buffer_future(ShaderBuffer *data);
|
||||
|
||||
void begin_frame(GraphicsStateGuardianBase *gsg,
|
||||
Thread *current_thread);
|
||||
void end_frame(Thread *current_thread);
|
||||
|
|
@ -167,11 +219,11 @@ private:
|
|||
|
||||
private:
|
||||
typedef phash_set<TextureContext *, pointer_hash> Textures;
|
||||
typedef phash_set< PT(Texture) > EnqueuedTextures;
|
||||
typedef phash_map< PT(Texture), PT(EnqueuedObject) > EnqueuedTextures;
|
||||
typedef phash_set<GeomContext *, pointer_hash> Geoms;
|
||||
typedef phash_set< PT(Geom) > EnqueuedGeoms;
|
||||
typedef phash_set<ShaderContext *, pointer_hash> Shaders;
|
||||
typedef phash_set< PT(Shader) > EnqueuedShaders;
|
||||
typedef phash_map< PT(Shader), PT(EnqueuedObject) > EnqueuedShaders;
|
||||
typedef phash_set<BufferContext *, pointer_hash> Buffers;
|
||||
typedef phash_set< PT(GeomVertexArrayData) > EnqueuedVertexBuffers;
|
||||
typedef phash_set< PT(GeomPrimitive) > EnqueuedIndexBuffers;
|
||||
|
|
|
|||
|
|
@ -3407,9 +3407,10 @@ parse_eof() {
|
|||
* Use this function instead of prepare_now() to preload textures from a user
|
||||
* interface standpoint.
|
||||
*/
|
||||
void Shader::
|
||||
PT(AsyncFuture) Shader::
|
||||
prepare(PreparedGraphicsObjects *prepared_objects) {
|
||||
prepared_objects->enqueue_shader(this);
|
||||
PT(PreparedGraphicsObjects::EnqueuedObject) obj = prepared_objects->enqueue_shader_future(this);
|
||||
return obj.p();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@
|
|||
#include "pta_LVecBase3.h"
|
||||
#include "pta_LVecBase2.h"
|
||||
#include "epvector.h"
|
||||
#include "asyncFuture.h"
|
||||
|
||||
#ifdef HAVE_CG
|
||||
// I don't want to include the Cg header file into panda as a whole. Instead,
|
||||
|
|
@ -109,7 +110,7 @@ PUBLISHED:
|
|||
INLINE bool get_cache_compiled_shader() const;
|
||||
INLINE void set_cache_compiled_shader(bool flag);
|
||||
|
||||
void prepare(PreparedGraphicsObjects *prepared_objects);
|
||||
PT(AsyncFuture) prepare(PreparedGraphicsObjects *prepared_objects);
|
||||
bool is_prepared(PreparedGraphicsObjects *prepared_objects) const;
|
||||
bool release(PreparedGraphicsObjects *prepared_objects);
|
||||
int release_all();
|
||||
|
|
|
|||
|
|
@ -279,12 +279,12 @@ clear_clear_color() {
|
|||
* Returns the raw image data for a single pixel if it were set to the clear
|
||||
* color.
|
||||
*/
|
||||
INLINE string Texture::
|
||||
INLINE vector_uchar Texture::
|
||||
get_clear_data() const {
|
||||
CDReader cdata(_cycler);
|
||||
unsigned char data[16];
|
||||
size_t size = do_get_clear_data(cdata, data);
|
||||
return string((char *)data, size);
|
||||
vector_uchar data(16);
|
||||
data.resize(do_get_clear_data(cdata, &data[0]));
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2324,6 +2324,61 @@ get_unsigned_short(const unsigned char *&p) {
|
|||
return (double)v.us / 65535.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used by store() to retrieve the next consecutive component value
|
||||
* from the indicated element of the array, which is taken to be an array of
|
||||
* unsigned ints.
|
||||
*/
|
||||
INLINE double Texture::
|
||||
get_unsigned_int(const unsigned char *&p) {
|
||||
union {
|
||||
unsigned int ui;
|
||||
uchar uc[4];
|
||||
} v;
|
||||
v.uc[0] = (*p++);
|
||||
v.uc[1] = (*p++);
|
||||
v.uc[2] = (*p++);
|
||||
v.uc[3] = (*p++);
|
||||
return (double)v.ui / 4294967295.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used by store() to retrieve the next consecutive component value
|
||||
* from the indicated element of the array, which is taken to be an array of
|
||||
* floats.
|
||||
*/
|
||||
INLINE double Texture::
|
||||
get_float(const unsigned char *&p) {
|
||||
double v = *((float *)p);
|
||||
p += 4;
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used by store() to retrieve the next consecutive component value
|
||||
* from the indicated element of the array, which is taken to be an array of
|
||||
* half-floats.
|
||||
*/
|
||||
INLINE double Texture::
|
||||
get_half_float(const unsigned char *&p) {
|
||||
union {
|
||||
uint32_t ui;
|
||||
float uf;
|
||||
} v;
|
||||
uint16_t in = *(uint16_t *)p;
|
||||
p += 2;
|
||||
uint32_t t1 = in & 0x7fff; // Non-sign bits
|
||||
uint32_t t2 = in & 0x8000; // Sign bit
|
||||
uint32_t t3 = in & 0x7c00; // Exponent
|
||||
t1 <<= 13; // Align mantissa on MSB
|
||||
t2 <<= 16; // Shift sign bit into position
|
||||
t1 += 0x38000000; // Adjust bias
|
||||
t1 = (t3 == 0 ? 0 : t1); // Denormals-as-zero
|
||||
t1 |= t2; // Re-insert sign bit
|
||||
v.ui = t1;
|
||||
return v.uf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the indicated filename ends in .txo or .txo.pz or .txo.gz,
|
||||
* false otherwise.
|
||||
|
|
|
|||
|
|
@ -1417,9 +1417,10 @@ peek() {
|
|||
* Use this function instead of prepare_now() to preload textures from a user
|
||||
* interface standpoint.
|
||||
*/
|
||||
void Texture::
|
||||
PT(AsyncFuture) Texture::
|
||||
prepare(PreparedGraphicsObjects *prepared_objects) {
|
||||
prepared_objects->enqueue_texture(this);
|
||||
PT(PreparedGraphicsObjects::EnqueuedObject) obj = prepared_objects->enqueue_texture_future(this);
|
||||
return obj.p();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -5087,7 +5088,8 @@ do_store_one(CData *cdata, PNMImage &pnmimage, int z, int n) {
|
|||
return convert_to_pnmimage(pnmimage,
|
||||
do_get_expected_mipmap_x_size(cdata, n),
|
||||
do_get_expected_mipmap_y_size(cdata, n),
|
||||
cdata->_num_components, cdata->_component_width,
|
||||
cdata->_num_components, cdata->_component_type,
|
||||
is_srgb(cdata->_format),
|
||||
cdata->_ram_images[n]._image,
|
||||
do_get_ram_mipmap_page_size(cdata, n), z);
|
||||
}
|
||||
|
|
@ -5110,12 +5112,14 @@ do_store_one(CData *cdata, PfmFile &pfm, int z, int n) {
|
|||
if (cdata->_component_type != T_float) {
|
||||
// PfmFile by way of PNMImage.
|
||||
PNMImage pnmimage;
|
||||
bool success = convert_to_pnmimage(pnmimage,
|
||||
do_get_expected_mipmap_x_size(cdata, n),
|
||||
do_get_expected_mipmap_y_size(cdata, n),
|
||||
cdata->_num_components, cdata->_component_width,
|
||||
cdata->_ram_images[n]._image,
|
||||
do_get_ram_mipmap_page_size(cdata, n), z);
|
||||
bool success =
|
||||
convert_to_pnmimage(pnmimage,
|
||||
do_get_expected_mipmap_x_size(cdata, n),
|
||||
do_get_expected_mipmap_y_size(cdata, n),
|
||||
cdata->_num_components, cdata->_component_type,
|
||||
is_srgb(cdata->_format),
|
||||
cdata->_ram_images[n]._image,
|
||||
do_get_ram_mipmap_page_size(cdata, n), z);
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -5583,10 +5587,28 @@ do_get_clear_data(const CData *cdata, unsigned char *into) const {
|
|||
nassertr(cdata->_has_clear_color, 0);
|
||||
nassertr(cdata->_num_components <= 4, 0);
|
||||
|
||||
// TODO: encode the color into the sRGB color space if used
|
||||
switch (cdata->_component_type) {
|
||||
case T_unsigned_byte:
|
||||
{
|
||||
if (is_srgb(cdata->_format)) {
|
||||
xel color;
|
||||
xelval alpha;
|
||||
encode_sRGB_uchar(cdata->_clear_color, color, alpha);
|
||||
switch (cdata->_num_components) {
|
||||
case 2:
|
||||
into[1] = (unsigned char)color.g;
|
||||
case 1:
|
||||
into[0] = (unsigned char)color.r;
|
||||
break;
|
||||
case 4:
|
||||
into[3] = (unsigned char)alpha;
|
||||
case 3: // BGR <-> RGB
|
||||
into[0] = (unsigned char)color.b;
|
||||
into[1] = (unsigned char)color.g;
|
||||
into[2] = (unsigned char)color.r;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
LColor scaled = cdata->_clear_color.fmin(LColor(1)).fmax(LColor::zero());
|
||||
scaled *= 255;
|
||||
switch (cdata->_num_components) {
|
||||
|
|
@ -8036,25 +8058,28 @@ convert_from_pfm(PTA_uchar &image, size_t page_size, int z,
|
|||
*/
|
||||
bool Texture::
|
||||
convert_to_pnmimage(PNMImage &pnmimage, int x_size, int y_size,
|
||||
int num_components, int component_width,
|
||||
CPTA_uchar image, size_t page_size, int z) {
|
||||
int num_components, ComponentType component_type,
|
||||
bool is_srgb, CPTA_uchar image, size_t page_size, int z) {
|
||||
xelval maxval = 0xff;
|
||||
if (component_width > 1) {
|
||||
if (component_type != T_unsigned_byte && component_type != T_byte) {
|
||||
maxval = 0xffff;
|
||||
}
|
||||
pnmimage.clear(x_size, y_size, num_components, maxval);
|
||||
ColorSpace color_space = is_srgb ? CS_sRGB : CS_linear;
|
||||
pnmimage.clear(x_size, y_size, num_components, maxval, nullptr, color_space);
|
||||
bool has_alpha = pnmimage.has_alpha();
|
||||
bool is_grayscale = pnmimage.is_grayscale();
|
||||
|
||||
int idx = page_size * z;
|
||||
nassertr(idx + page_size <= image.size(), false);
|
||||
const unsigned char *p = &image[idx];
|
||||
|
||||
if (component_width == 1) {
|
||||
xel *array = pnmimage.get_array();
|
||||
xel *array = pnmimage.get_array();
|
||||
xelval *alpha = pnmimage.get_alpha_array();
|
||||
|
||||
switch (component_type) {
|
||||
case T_unsigned_byte:
|
||||
if (is_grayscale) {
|
||||
const unsigned char *p = &image[idx];
|
||||
if (has_alpha) {
|
||||
xelval *alpha = pnmimage.get_alpha_array();
|
||||
for (int j = y_size-1; j >= 0; j--) {
|
||||
xel *row = array + j * x_size;
|
||||
xelval *alpha_row = alpha + j * x_size;
|
||||
|
|
@ -8071,9 +8096,10 @@ convert_to_pnmimage(PNMImage &pnmimage, int x_size, int y_size,
|
|||
}
|
||||
}
|
||||
}
|
||||
nassertr(p == &image[idx] + page_size, false);
|
||||
} else {
|
||||
const unsigned char *p = &image[idx];
|
||||
if (has_alpha) {
|
||||
xelval *alpha = pnmimage.get_alpha_array();
|
||||
for (int j = y_size-1; j >= 0; j--) {
|
||||
xel *row = array + j * x_size;
|
||||
xelval *alpha_row = alpha + j * x_size;
|
||||
|
|
@ -8094,29 +8120,78 @@ convert_to_pnmimage(PNMImage &pnmimage, int x_size, int y_size,
|
|||
}
|
||||
}
|
||||
}
|
||||
nassertr(p == &image[idx] + page_size, false);
|
||||
}
|
||||
break;
|
||||
|
||||
} else if (component_width == 2) {
|
||||
for (int j = y_size-1; j >= 0; j--) {
|
||||
for (int i = 0; i < x_size; i++) {
|
||||
if (is_grayscale) {
|
||||
pnmimage.set_gray(i, j, get_unsigned_short(p));
|
||||
} else {
|
||||
pnmimage.set_blue(i, j, get_unsigned_short(p));
|
||||
pnmimage.set_green(i, j, get_unsigned_short(p));
|
||||
pnmimage.set_red(i, j, get_unsigned_short(p));
|
||||
}
|
||||
if (has_alpha) {
|
||||
pnmimage.set_alpha(i, j, get_unsigned_short(p));
|
||||
case T_unsigned_short:
|
||||
{
|
||||
const uint16_t *p = (const uint16_t *)&image[idx];
|
||||
|
||||
for (int j = y_size-1; j >= 0; j--) {
|
||||
xel *row = array + j * x_size;
|
||||
xelval *alpha_row = alpha + j * x_size;
|
||||
for (int i = 0; i < x_size; i++) {
|
||||
PPM_PUTB(row[i], *p++);
|
||||
if (!is_grayscale) {
|
||||
PPM_PUTG(row[i], *p++);
|
||||
PPM_PUTR(row[i], *p++);
|
||||
}
|
||||
if (has_alpha) {
|
||||
alpha_row[i] = *p++;
|
||||
}
|
||||
}
|
||||
}
|
||||
nassertr((const unsigned char *)p == &image[idx] + page_size, false);
|
||||
}
|
||||
break;
|
||||
|
||||
} else {
|
||||
case T_unsigned_int:
|
||||
{
|
||||
const uint32_t *p = (const uint32_t *)&image[idx];
|
||||
|
||||
for (int j = y_size-1; j >= 0; j--) {
|
||||
xel *row = array + j * x_size;
|
||||
xelval *alpha_row = alpha + j * x_size;
|
||||
for (int i = 0; i < x_size; i++) {
|
||||
PPM_PUTB(row[i], (*p++) >> 16u);
|
||||
if (!is_grayscale) {
|
||||
PPM_PUTG(row[i], (*p++) >> 16u);
|
||||
PPM_PUTR(row[i], (*p++) >> 16u);
|
||||
}
|
||||
if (has_alpha) {
|
||||
alpha_row[i] = (*p++) >> 16u;
|
||||
}
|
||||
}
|
||||
}
|
||||
nassertr((const unsigned char *)p == &image[idx] + page_size, false);
|
||||
}
|
||||
break;
|
||||
|
||||
case T_half_float:
|
||||
{
|
||||
const unsigned char *p = &image[idx];
|
||||
|
||||
for (int j = y_size-1; j >= 0; j--) {
|
||||
for (int i = 0; i < x_size; i++) {
|
||||
pnmimage.set_blue(i, j, get_half_float(p));
|
||||
if (!is_grayscale) {
|
||||
pnmimage.set_green(i, j, get_half_float(p));
|
||||
pnmimage.set_red(i, j, get_half_float(p));
|
||||
}
|
||||
if (has_alpha) {
|
||||
pnmimage.set_alpha(i, j, get_half_float(p));
|
||||
}
|
||||
}
|
||||
}
|
||||
nassertr(p == &image[idx] + page_size, false);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
nassertr(p == &image[idx] + page_size, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -43,9 +43,10 @@
|
|||
#include "colorSpace.h"
|
||||
#include "geomEnums.h"
|
||||
#include "bamCacheRecord.h"
|
||||
#include "pnmImage.h"
|
||||
#include "pfmFile.h"
|
||||
#include "asyncFuture.h"
|
||||
|
||||
class PNMImage;
|
||||
class PfmFile;
|
||||
class TextureContext;
|
||||
class FactoryParams;
|
||||
class PreparedGraphicsObjects;
|
||||
|
|
@ -264,7 +265,7 @@ PUBLISHED:
|
|||
INLINE LColor get_clear_color() const;
|
||||
INLINE void set_clear_color(const LColor &color);
|
||||
INLINE void clear_clear_color();
|
||||
INLINE string get_clear_data() const;
|
||||
INLINE vector_uchar get_clear_data() const;
|
||||
MAKE_PROPERTY2(clear_color, has_clear_color, get_clear_color,
|
||||
set_clear_color, clear_clear_color);
|
||||
|
||||
|
|
@ -522,7 +523,7 @@ PUBLISHED:
|
|||
MAKE_PROPERTY(auto_texture_scale, get_auto_texture_scale,
|
||||
set_auto_texture_scale);
|
||||
|
||||
void prepare(PreparedGraphicsObjects *prepared_objects);
|
||||
PT(AsyncFuture) prepare(PreparedGraphicsObjects *prepared_objects);
|
||||
bool is_prepared(PreparedGraphicsObjects *prepared_objects) const;
|
||||
bool was_image_modified(PreparedGraphicsObjects *prepared_objects) const;
|
||||
size_t get_data_size_bytes(PreparedGraphicsObjects *prepared_objects) const;
|
||||
|
|
@ -800,7 +801,8 @@ private:
|
|||
int z, const PfmFile &pfm,
|
||||
int num_components, int component_width);
|
||||
static bool convert_to_pnmimage(PNMImage &pnmimage, int x_size, int y_size,
|
||||
int num_components, int component_width,
|
||||
int num_components,
|
||||
ComponentType component_type, bool is_srgb,
|
||||
CPTA_uchar image, size_t page_size,
|
||||
int z);
|
||||
static bool convert_to_pfm(PfmFile &pfm, int x_size, int y_size,
|
||||
|
|
@ -855,6 +857,9 @@ private:
|
|||
INLINE static void store_scaled_short(unsigned char *&p, int value, double scale);
|
||||
INLINE static double get_unsigned_byte(const unsigned char *&p);
|
||||
INLINE static double get_unsigned_short(const unsigned char *&p);
|
||||
INLINE static double get_unsigned_int(const unsigned char *&p);
|
||||
INLINE static double get_float(const unsigned char *&p);
|
||||
INLINE static double get_half_float(const unsigned char *&p);
|
||||
|
||||
INLINE static bool is_txo_filename(const Filename &fullpath);
|
||||
INLINE static bool is_dds_filename(const Filename &fullpath);
|
||||
|
|
|
|||
|
|
@ -82,6 +82,18 @@ TexturePeeker(Texture *tex, Texture::CData *cdata) {
|
|||
_get_component = Texture::get_unsigned_short;
|
||||
break;
|
||||
|
||||
case Texture::T_unsigned_int:
|
||||
_get_component = Texture::get_unsigned_int;
|
||||
break;
|
||||
|
||||
case Texture::T_float:
|
||||
_get_component = Texture::get_float;
|
||||
break;
|
||||
|
||||
case Texture::T_half_float:
|
||||
_get_component = Texture::get_half_float;
|
||||
break;
|
||||
|
||||
default:
|
||||
// Not supported.
|
||||
_image.clear();
|
||||
|
|
@ -123,7 +135,6 @@ TexturePeeker(Texture *tex, Texture::CData *cdata) {
|
|||
break;
|
||||
|
||||
case Texture::F_rgb:
|
||||
case Texture::F_srgb:
|
||||
case Texture::F_rgb5:
|
||||
case Texture::F_rgb8:
|
||||
case Texture::F_rgb12:
|
||||
|
|
@ -135,7 +146,6 @@ TexturePeeker(Texture *tex, Texture::CData *cdata) {
|
|||
break;
|
||||
|
||||
case Texture::F_rgba:
|
||||
case Texture::F_srgb_alpha:
|
||||
case Texture::F_rgbm:
|
||||
case Texture::F_rgba4:
|
||||
case Texture::F_rgba5:
|
||||
|
|
@ -146,6 +156,25 @@ TexturePeeker(Texture *tex, Texture::CData *cdata) {
|
|||
case Texture::F_rgb10_a2:
|
||||
_get_texel = get_texel_rgba;
|
||||
break;
|
||||
|
||||
case Texture::F_srgb:
|
||||
if (_component_type == Texture::T_unsigned_byte) {
|
||||
_get_texel = get_texel_srgb;
|
||||
} else {
|
||||
gobj_cat.error()
|
||||
<< "sRGB texture should have component type T_unsigned_byte\n";
|
||||
}
|
||||
break;
|
||||
|
||||
case Texture::F_srgb_alpha:
|
||||
if (_component_type == Texture::T_unsigned_byte) {
|
||||
_get_texel = get_texel_srgba;
|
||||
} else {
|
||||
gobj_cat.error()
|
||||
<< "sRGB texture should have component type T_unsigned_byte\n";
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// Not supported.
|
||||
gobj_cat.error() << "Unsupported texture peeker format: "
|
||||
|
|
@ -570,3 +599,27 @@ get_texel_rgba(LColor &color, const unsigned char *&p, GetComponentFunc *get_com
|
|||
color[0] = (*get_component)(p);
|
||||
color[3] = (*get_component)(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the color of the texel at byte p, given that the texture is in format
|
||||
* F_srgb or similar.
|
||||
*/
|
||||
void TexturePeeker::
|
||||
get_texel_srgb(LColor &color, const unsigned char *&p, GetComponentFunc *get_component) {
|
||||
color[2] = decode_sRGB_float(*p++);
|
||||
color[1] = decode_sRGB_float(*p++);
|
||||
color[0] = decode_sRGB_float(*p++);
|
||||
color[3] = 1.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the color of the texel at byte p, given that the texture is in format
|
||||
* F_srgb_alpha or similar.
|
||||
*/
|
||||
void TexturePeeker::
|
||||
get_texel_srgba(LColor &color, const unsigned char *&p, GetComponentFunc *get_component) {
|
||||
color[2] = decode_sRGB_float(*p++);
|
||||
color[1] = decode_sRGB_float(*p++);
|
||||
color[0] = decode_sRGB_float(*p++);
|
||||
color[3] = (*get_component)(p);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,6 +79,8 @@ private:
|
|||
static void get_texel_la(LColor &color, const unsigned char *&p, GetComponentFunc *get_component);
|
||||
static void get_texel_rgb(LColor &color, const unsigned char *&p, GetComponentFunc *get_component);
|
||||
static void get_texel_rgba(LColor &color, const unsigned char *&p, GetComponentFunc *get_component);
|
||||
static void get_texel_srgb(LColor &color, const unsigned char *&p, GetComponentFunc *get_component);
|
||||
static void get_texel_srgba(LColor &color, const unsigned char *&p, GetComponentFunc *get_component);
|
||||
|
||||
int _x_size;
|
||||
int _y_size;
|
||||
|
|
|
|||
|
|
@ -22,8 +22,7 @@ TextureReloadRequest(const string &name,
|
|||
AsyncTask(name),
|
||||
_pgo(pgo),
|
||||
_texture(texture),
|
||||
_allow_compressed(allow_compressed),
|
||||
_is_ready(false)
|
||||
_allow_compressed(allow_compressed)
|
||||
{
|
||||
nassertv(_pgo != (PreparedGraphicsObjects *)NULL);
|
||||
nassertv(_texture != (Texture *)NULL);
|
||||
|
|
@ -58,8 +57,10 @@ get_allow_compressed() const {
|
|||
|
||||
/**
|
||||
* Returns true if this request has completed, false if it is still pending.
|
||||
* Equivalent to `req.done() and not req.cancelled()`.
|
||||
* @see done()
|
||||
*/
|
||||
INLINE bool TextureReloadRequest::
|
||||
is_ready() const {
|
||||
return _is_ready;
|
||||
return (FutureState)AtomicAdjust::get(_future_state) == FS_finished;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,6 @@ do_task() {
|
|||
_texture->prepare(_pgo);
|
||||
}
|
||||
}
|
||||
_is_ready = true;
|
||||
|
||||
// Don't continue the task; we're done.
|
||||
return DS_done;
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@ PUBLISHED:
|
|||
INLINE bool get_allow_compressed() const;
|
||||
INLINE bool is_ready() const;
|
||||
|
||||
MAKE_PROPERTY(texture, get_texture);
|
||||
|
||||
protected:
|
||||
virtual DoneStatus do_task();
|
||||
|
||||
|
|
@ -50,7 +52,6 @@ private:
|
|||
PT(PreparedGraphicsObjects) _pgo;
|
||||
PT(Texture) _texture;
|
||||
bool _allow_compressed;
|
||||
bool _is_ready;
|
||||
|
||||
public:
|
||||
static TypeHandle get_class_type() {
|
||||
|
|
|
|||
|
|
@ -33,15 +33,12 @@ set_ram_image(PyObject *image, Texture::CompressionMode compression,
|
|||
nassertv(compression != Texture::CM_default);
|
||||
|
||||
// Check if perhaps a PointerToArray object was passed in.
|
||||
if (DtoolCanThisBeAPandaInstance(image)) {
|
||||
Dtool_PyInstDef *inst = (Dtool_PyInstDef *)image;
|
||||
|
||||
if (inst->_My_Type == &Dtool_ConstPointerToArray_unsigned_char) {
|
||||
_this->set_ram_image(*(const CPTA_uchar *)inst->_ptr_to_object, compression, page_size);
|
||||
if (DtoolInstance_Check(image)) {
|
||||
if (DtoolInstance_TYPE(image) == &Dtool_ConstPointerToArray_unsigned_char) {
|
||||
_this->set_ram_image(*(const CPTA_uchar *)DtoolInstance_VOID_PTR(image), compression, page_size);
|
||||
return;
|
||||
|
||||
} else if (inst->_My_Type == &Dtool_PointerToArray_unsigned_char) {
|
||||
_this->set_ram_image(*(const PTA_uchar *)inst->_ptr_to_object, compression, page_size);
|
||||
} else if (DtoolInstance_TYPE(image) == &Dtool_PointerToArray_unsigned_char) {
|
||||
_this->set_ram_image(*(const PTA_uchar *)DtoolInstance_VOID_PTR(image), compression, page_size);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -87,6 +84,29 @@ set_ram_image(PyObject *image, Texture::CompressionMode compression,
|
|||
}
|
||||
#endif
|
||||
|
||||
#if PY_MAJOR_VERSION < 3
|
||||
// The old, deprecated buffer interface, as used by eg. the array module.
|
||||
const void *buffer;
|
||||
Py_ssize_t buffer_len;
|
||||
if (!PyUnicode_CheckExact(image) &&
|
||||
PyObject_AsReadBuffer(image, &buffer, &buffer_len) == 0) {
|
||||
if (compression == Texture::CM_off) {
|
||||
int component_width = _this->get_component_width();
|
||||
if (buffer_len % component_width != 0) {
|
||||
PyErr_Format(PyExc_ValueError,
|
||||
"byte buffer is not a multiple of %d bytes",
|
||||
component_width);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
PTA_uchar data = PTA_uchar::empty_array(buffer_len, Texture::get_class_type());
|
||||
memcpy(data.p(), buffer, buffer_len);
|
||||
_this->set_ram_image(MOVE(data), compression, page_size);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
Dtool_Raise_ArgTypeError(image, 0, "Texture.set_ram_image", "CPTA_uchar or buffer");
|
||||
}
|
||||
|
||||
|
|
@ -99,15 +119,12 @@ set_ram_image(PyObject *image, Texture::CompressionMode compression,
|
|||
void Extension<Texture>::
|
||||
set_ram_image_as(PyObject *image, const string &provided_format) {
|
||||
// Check if perhaps a PointerToArray object was passed in.
|
||||
if (DtoolCanThisBeAPandaInstance(image)) {
|
||||
Dtool_PyInstDef *inst = (Dtool_PyInstDef *)image;
|
||||
|
||||
if (inst->_My_Type == &Dtool_ConstPointerToArray_unsigned_char) {
|
||||
_this->set_ram_image_as(*(const CPTA_uchar *)inst->_ptr_to_object, provided_format);
|
||||
if (DtoolInstance_Check(image)) {
|
||||
if (DtoolInstance_TYPE(image) == &Dtool_ConstPointerToArray_unsigned_char) {
|
||||
_this->set_ram_image_as(*(const CPTA_uchar *)DtoolInstance_VOID_PTR(image), provided_format);
|
||||
return;
|
||||
|
||||
} else if (inst->_My_Type == &Dtool_PointerToArray_unsigned_char) {
|
||||
_this->set_ram_image_as(*(const PTA_uchar *)inst->_ptr_to_object, provided_format);
|
||||
} else if (DtoolInstance_TYPE(image) == &Dtool_PointerToArray_unsigned_char) {
|
||||
_this->set_ram_image_as(*(const PTA_uchar *)DtoolInstance_VOID_PTR(image), provided_format);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -105,6 +105,8 @@ add_object(CullableObject *object, const CullTraverser *traverser) {
|
|||
static const LColor flash_multisample_color(0.78f, 0.05f, 0.81f, 1.0f);
|
||||
static const LColor flash_dual_color(0.92, 0.01f, 0.01f, 1.0f);
|
||||
|
||||
nassertv(object->_draw_callback != nullptr || object->_geom != nullptr);
|
||||
|
||||
bool force = !traverser->get_effective_incomplete_render();
|
||||
Thread *current_thread = traverser->get_current_thread();
|
||||
CullBinManager *bin_manager = CullBinManager::get_global_ptr();
|
||||
|
|
|
|||
|
|
@ -232,7 +232,7 @@ draw_bounding_volume(const BoundingVolume *vol,
|
|||
if (bounds_viz != (Geom *)NULL) {
|
||||
_geoms_pcollector.add_level(2);
|
||||
CullableObject *outer_viz =
|
||||
new CullableObject(move(bounds_viz), get_bounds_outer_viz_state(),
|
||||
new CullableObject(bounds_viz, get_bounds_outer_viz_state(),
|
||||
internal_transform);
|
||||
_cull_handler->record_object(outer_viz, this);
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@
|
|||
#include "boundingBox.h"
|
||||
#include "boundingSphere.h"
|
||||
#include "config_mathutil.h"
|
||||
#include "preparedGraphicsObjects.h"
|
||||
|
||||
|
||||
bool allow_flatten_color = ConfigVariableBool
|
||||
|
|
@ -382,14 +383,14 @@ r_prepare_scene(GraphicsStateGuardianBase *gsg, const RenderState *node_state,
|
|||
int num_arrays = vdata_reader.get_num_arrays();
|
||||
for (int i = 0; i < num_arrays; ++i) {
|
||||
CPT(GeomVertexArrayData) array = vdata_reader.get_array(i);
|
||||
((GeomVertexArrayData *)array.p())->prepare(prepared_objects);
|
||||
prepared_objects->enqueue_vertex_buffer((GeomVertexArrayData *)array.p());
|
||||
}
|
||||
|
||||
// And also each of the index arrays.
|
||||
int num_primitives = geom->get_num_primitives();
|
||||
for (int i = 0; i < num_primitives; ++i) {
|
||||
CPT(GeomPrimitive) prim = geom->get_primitive(i);
|
||||
((GeomPrimitive *)prim.p())->prepare(prepared_objects);
|
||||
prepared_objects->enqueue_index_buffer((GeomPrimitive *)prim.p());
|
||||
}
|
||||
|
||||
if (munger->is_of_type(StateMunger::get_class_type())) {
|
||||
|
|
@ -405,7 +406,7 @@ r_prepare_scene(GraphicsStateGuardianBase *gsg, const RenderState *node_state,
|
|||
Texture *texture = ta->get_on_texture(ta->get_on_stage(i));
|
||||
// TODO: prepare the sampler states, if specified.
|
||||
if (texture != nullptr) {
|
||||
texture->prepare(prepared_objects);
|
||||
prepared_objects->enqueue_texture(texture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -415,7 +416,7 @@ r_prepare_scene(GraphicsStateGuardianBase *gsg, const RenderState *node_state,
|
|||
if (geom_state->get_attrib(sa)) {
|
||||
Shader *shader = (Shader *)sa->get_shader();
|
||||
if (shader != nullptr) {
|
||||
shader->prepare(prepared_objects);
|
||||
prepared_objects->enqueue_shader(shader);
|
||||
}
|
||||
// TODO: prepare the shader inputs.
|
||||
}
|
||||
|
|
|
|||
|
|
@ -135,6 +135,7 @@ stop_threads() {
|
|||
/**
|
||||
* Removes a pending asynchronous load request. Returns true if successful,
|
||||
* false otherwise.
|
||||
* @deprecated use task.cancel() to cancel the request instead.
|
||||
*/
|
||||
INLINE bool Loader::
|
||||
remove(AsyncTask *task) {
|
||||
|
|
|
|||
|
|
@ -18,8 +18,7 @@
|
|||
INLINE ModelFlattenRequest::
|
||||
ModelFlattenRequest(PandaNode *orig) :
|
||||
AsyncTask(orig->get_name()),
|
||||
_orig(orig),
|
||||
_is_ready(false)
|
||||
_orig(orig)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -35,32 +34,21 @@ get_orig() const {
|
|||
* Returns true if this request has completed, false if it is still pending.
|
||||
* When this returns true, you may retrieve the model loaded by calling
|
||||
* result().
|
||||
* Equivalent to `req.done() and not req.cancelled()`.
|
||||
* @see done()
|
||||
*/
|
||||
INLINE bool ModelFlattenRequest::
|
||||
is_ready() const {
|
||||
return _is_ready;
|
||||
return (FutureState)AtomicAdjust::get(_future_state) == FS_finished;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the flattened copy of the model. It is an error to call this
|
||||
* unless is_ready() returns true.
|
||||
* unless done() returns true.
|
||||
* @deprecated Use result() instead.
|
||||
*/
|
||||
INLINE PandaNode *ModelFlattenRequest::
|
||||
get_model() const {
|
||||
nassertr(_is_ready, nullptr);
|
||||
return _model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the flattened copy of the model wrapped in a NodePath. It is an
|
||||
* error to call this unless is_ready() returns true.
|
||||
*/
|
||||
INLINE NodePath ModelFlattenRequest::
|
||||
result() const {
|
||||
nassertr(_is_ready, NodePath::fail());
|
||||
if (_model != nullptr) {
|
||||
return NodePath(_model);
|
||||
} else {
|
||||
return NodePath::fail();
|
||||
}
|
||||
nassertr_always(done(), nullptr);
|
||||
return (PandaNode *)_result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,8 +35,8 @@ do_task() {
|
|||
np.attach_new_node(_orig);
|
||||
}
|
||||
np.flatten_strong();
|
||||
_model = np.get_child(0).node();
|
||||
_is_ready = true;
|
||||
|
||||
set_result(np.get_child(0).node());
|
||||
|
||||
// Don't continue the task; we're done.
|
||||
return DS_done;
|
||||
|
|
|
|||
|
|
@ -39,18 +39,13 @@ PUBLISHED:
|
|||
INLINE bool is_ready() const;
|
||||
INLINE PandaNode *get_model() const;
|
||||
|
||||
INLINE NodePath result() const;
|
||||
|
||||
MAKE_PROPERTY(orig, get_orig);
|
||||
MAKE_PROPERTY(ready, is_ready);
|
||||
|
||||
protected:
|
||||
virtual DoneStatus do_task();
|
||||
|
||||
private:
|
||||
PT(PandaNode) _orig;
|
||||
bool _is_ready;
|
||||
PT(PandaNode) _model;
|
||||
|
||||
public:
|
||||
static TypeHandle get_class_type() {
|
||||
|
|
|
|||
|
|
@ -38,31 +38,24 @@ get_loader() const {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the model that was loaded asynchronously as a NodePath, if any, or
|
||||
* the empty NodePath if there was an error.
|
||||
*/
|
||||
INLINE NodePath ModelLoadRequest::
|
||||
result() const {
|
||||
nassertr_always(_is_ready, NodePath::fail());
|
||||
return NodePath(_model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this request has completed, false if it is still pending.
|
||||
* When this returns true, you may retrieve the model loaded by calling
|
||||
* get_model().
|
||||
* Returns true if this request has completed, false if it is still pending or
|
||||
* if it has been cancelled. When this returns true, you may retrieve the
|
||||
* model loaded by calling get_model().
|
||||
* Equivalent to `req.done() and not req.cancelled()`.
|
||||
* @see done()
|
||||
*/
|
||||
INLINE bool ModelLoadRequest::
|
||||
is_ready() const {
|
||||
return _is_ready;
|
||||
return (FutureState)AtomicAdjust::get(_future_state) == FS_finished;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the model that was loaded asynchronously, if any, or NULL if there
|
||||
* was an error. It is an error to call this unless is_ready() returns true.
|
||||
* Returns the model that was loaded asynchronously, if any, or null if there
|
||||
* was an error. It is an error to call this unless done() returns true.
|
||||
* @deprecated Use result() instead.
|
||||
*/
|
||||
INLINE PandaNode *ModelLoadRequest::
|
||||
get_model() const {
|
||||
nassertr(_is_ready, NULL);
|
||||
return _model;
|
||||
nassertr_always(done(), nullptr);
|
||||
return (PandaNode *)_result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,8 +28,7 @@ ModelLoadRequest(const string &name,
|
|||
AsyncTask(name),
|
||||
_filename(filename),
|
||||
_options(options),
|
||||
_loader(loader),
|
||||
_is_ready(false)
|
||||
_loader(loader)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -43,8 +42,8 @@ do_task() {
|
|||
Thread::sleep(delay);
|
||||
}
|
||||
|
||||
_model = _loader->load_sync(_filename, _options);
|
||||
_is_ready = true;
|
||||
PT(PandaNode) model = _loader->load_sync(_filename, _options);
|
||||
set_result(model);
|
||||
|
||||
// Don't continue the task; we're done.
|
||||
return DS_done;
|
||||
|
|
|
|||
|
|
@ -43,15 +43,12 @@ PUBLISHED:
|
|||
INLINE const LoaderOptions &get_options() const;
|
||||
INLINE Loader *get_loader() const;
|
||||
|
||||
INLINE NodePath result() const;
|
||||
|
||||
INLINE bool is_ready() const;
|
||||
INLINE PandaNode *get_model() const;
|
||||
|
||||
MAKE_PROPERTY(filename, get_filename);
|
||||
MAKE_PROPERTY(options, get_options);
|
||||
MAKE_PROPERTY(loader, get_loader);
|
||||
MAKE_PROPERTY(ready, is_ready);
|
||||
|
||||
protected:
|
||||
virtual DoneStatus do_task();
|
||||
|
|
@ -60,8 +57,6 @@ private:
|
|||
Filename _filename;
|
||||
LoaderOptions _options;
|
||||
PT(Loader) _loader;
|
||||
bool _is_ready;
|
||||
PT(PandaNode) _model;
|
||||
|
||||
public:
|
||||
static TypeHandle get_class_type() {
|
||||
|
|
|
|||
|
|
@ -49,28 +49,20 @@ get_loader() const {
|
|||
* Returns true if this request has completed, false if it is still pending.
|
||||
* When this returns true, you may retrieve the success flag with
|
||||
* get_success().
|
||||
* Equivalent to `req.done() and not req.cancelled()`.
|
||||
* @see done()
|
||||
*/
|
||||
INLINE bool ModelSaveRequest::
|
||||
is_ready() const {
|
||||
return _is_ready;
|
||||
return (FutureState)AtomicAdjust::get(_future_state) == FS_finished;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the true if the model was saved successfully, false otherwise. It
|
||||
* is an error to call this unless is_ready() returns true.
|
||||
* is an error to call this unless done() returns true.
|
||||
*/
|
||||
INLINE bool ModelSaveRequest::
|
||||
get_success() const {
|
||||
nassertr(_is_ready, false);
|
||||
return _success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a boolean indicating whether the model saved correctly. It is an
|
||||
* error to call this unless is_ready() returns true.
|
||||
*/
|
||||
INLINE bool ModelSaveRequest::
|
||||
result() const {
|
||||
nassertr(_is_ready, false);
|
||||
nassertr_always(done(), false);
|
||||
return _success;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ ModelSaveRequest(const string &name,
|
|||
_options(options),
|
||||
_node(node),
|
||||
_loader(loader),
|
||||
_is_ready(false),
|
||||
_success(false)
|
||||
{
|
||||
}
|
||||
|
|
@ -46,7 +45,6 @@ do_task() {
|
|||
}
|
||||
|
||||
_success = _loader->save_sync(_filename, _options, _node);
|
||||
_is_ready = true;
|
||||
|
||||
// Don't continue the task; we're done.
|
||||
return DS_done;
|
||||
|
|
|
|||
|
|
@ -46,13 +46,10 @@ PUBLISHED:
|
|||
INLINE bool is_ready() const;
|
||||
INLINE bool get_success() const;
|
||||
|
||||
INLINE bool result() const;
|
||||
|
||||
MAKE_PROPERTY(filename, get_filename);
|
||||
MAKE_PROPERTY(options, get_options);
|
||||
MAKE_PROPERTY(node, get_node);
|
||||
MAKE_PROPERTY(loader, get_loader);
|
||||
MAKE_PROPERTY(ready, is_ready);
|
||||
|
||||
protected:
|
||||
virtual DoneStatus do_task();
|
||||
|
|
@ -62,7 +59,6 @@ private:
|
|||
LoaderOptions _options;
|
||||
PT(PandaNode) _node;
|
||||
PT(Loader) _loader;
|
||||
bool _is_ready;
|
||||
bool _success;
|
||||
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -46,8 +46,7 @@ __init__(PyObject *self, PyObject *sequence) {
|
|||
}
|
||||
|
||||
NodePath *path;
|
||||
DTOOL_Call_ExtractThisPointerForType(item, &Dtool_NodePath, (void **)&path);
|
||||
if (path == (NodePath *)NULL) {
|
||||
if (!DtoolInstance_GetPointer(item, path, Dtool_NodePath)) {
|
||||
// Unable to add item--probably it wasn't of the appropriate type.
|
||||
ostringstream stream;
|
||||
stream << "Element " << i << " in sequence passed to NodePathCollection constructor is not a NodePath";
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ __reduce_persist__(PyObject *self, PyObject *pickler) const {
|
|||
// It's OK if there's no bamWriter.
|
||||
PyErr_Clear();
|
||||
} else {
|
||||
DTOOL_Call_ExtractThisPointerForType(py_writer, &Dtool_BamWriter, (void **)&writer);
|
||||
DtoolInstance_GetPointer(py_writer, writer, Dtool_BamWriter);
|
||||
Py_DECREF(py_writer);
|
||||
}
|
||||
}
|
||||
|
|
@ -228,7 +228,7 @@ py_decode_NodePath_from_bam_stream_persist(PyObject *unpickler, vector_uchar dat
|
|||
// It's OK if there's no bamReader.
|
||||
PyErr_Clear();
|
||||
} else {
|
||||
DTOOL_Call_ExtractThisPointerForType(py_reader, &Dtool_BamReader, (void **)&reader);
|
||||
DtoolInstance_GetPointer(py_reader, reader, Dtool_BamReader);
|
||||
Py_DECREF(py_reader);
|
||||
}
|
||||
}
|
||||
|
|
@ -251,7 +251,7 @@ set_shader_input(CPT_InternalName name, PyObject *value, int priority) {
|
|||
}
|
||||
|
||||
ShaderInput &input = attrib->_inputs[name];
|
||||
invoke_extension(&input).__init__(move(name), value);
|
||||
invoke_extension(&input).__init__(move(name), value, priority);
|
||||
|
||||
if (!_PyErr_OCCURRED()) {
|
||||
node->set_attrib(ShaderAttrib::return_new(attrib));
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue