diff --git a/direct/src/interval/Interval.py b/direct/src/interval/Interval.py index 21a79f016a..02a5f4bc00 100644 --- a/direct/src/interval/Interval.py +++ b/direct/src/interval/Interval.py @@ -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 diff --git a/direct/src/showbase/Loader.py b/direct/src/showbase/Loader.py index 64fa593940..17c9cea25e 100644 --- a/direct/src/showbase/Loader.py +++ b/direct/src/showbase/Loader.py @@ -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 diff --git a/direct/src/showbase/Messenger.py b/direct/src/showbase/Messenger.py index 08062e4a3e..4ac1b96f56 100644 --- a/direct/src/showbase/Messenger.py +++ b/direct/src/showbase/Messenger.py @@ -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 diff --git a/direct/src/showbase/ShowBase.py b/direct/src/showbase/ShowBase.py index 2d43dba169..99a8b77abf 100644 --- a/direct/src/showbase/ShowBase.py +++ b/direct/src/showbase/ShowBase.py @@ -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") diff --git a/direct/src/task/Task.py b/direct/src/task/Task.py index 08baa924a5..8645e5bc43 100644 --- a/direct/src/task/Task.py +++ b/direct/src/task/Task.py @@ -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: diff --git a/dtool/src/dtoolutil/filename_ext.cxx b/dtool/src/dtoolutil/filename_ext.cxx index 1c9bf78e7e..3d71317786 100644 --- a/dtool/src/dtoolutil/filename_ext.cxx +++ b/dtool/src/dtoolutil/filename_ext.cxx @@ -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; } diff --git a/dtool/src/interrogate/functionRemap.cxx b/dtool/src/interrogate/functionRemap.cxx index 27ec829479..42a420644f 100644 --- a/dtool/src/interrogate/functionRemap.cxx +++ b/dtool/src/interrogate/functionRemap.cxx @@ -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; diff --git a/dtool/src/interrogate/interfaceMakerPythonNative.cxx b/dtool/src/interrogate/interfaceMakerPythonNative.cxx index 904df4c153..3a861a4844 100644 --- a/dtool/src/interrogate/interfaceMakerPythonNative.cxx +++ b/dtool/src/interrogate/interfaceMakerPythonNative.cxx @@ -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"; diff --git a/dtool/src/interrogatedb/p3interrogatedb_composite2.cxx b/dtool/src/interrogatedb/p3interrogatedb_composite2.cxx index a46ce81558..fda72f2ce8 100644 --- a/dtool/src/interrogatedb/p3interrogatedb_composite2.cxx +++ b/dtool/src/interrogatedb/p3interrogatedb_composite2.cxx @@ -5,4 +5,5 @@ #include "interrogate_interface.cxx" #include "interrogate_request.cxx" #include "py_panda.cxx" +#include "py_compat.cxx" #include "py_wrappers.cxx" diff --git a/dtool/src/interrogatedb/py_compat.cxx b/dtool/src/interrogatedb/py_compat.cxx new file mode 100755 index 0000000000..f7f02607c0 --- /dev/null +++ b/dtool/src/interrogatedb/py_compat.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 diff --git a/dtool/src/interrogatedb/py_compat.h b/dtool/src/interrogatedb/py_compat.h new file mode 100755 index 0000000000..46537114aa --- /dev/null +++ b/dtool/src/interrogatedb/py_compat.h @@ -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 diff --git a/dtool/src/interrogatedb/py_panda.I b/dtool/src/interrogatedb/py_panda.I index 53d59a9639..1986a128c5 100644 --- a/dtool/src/interrogatedb/py_panda.I +++ b/dtool/src/interrogatedb/py_panda.I @@ -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 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 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); } /** diff --git a/dtool/src/interrogatedb/py_panda.cxx b/dtool/src/interrogatedb/py_panda.cxx index f0031d747b..4f03f59749 100644 --- a/dtool/src/interrogatedb/py_panda.cxx +++ b/dtool/src/interrogatedb/py_panda.cxx @@ -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) { diff --git a/dtool/src/interrogatedb/py_panda.h b/dtool/src/interrogatedb/py_panda.h index adcce27b9c..74f13680f8 100644 --- a/dtool/src/interrogatedb/py_panda.h +++ b/dtool/src/interrogatedb/py_panda.h @@ -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 INLINE bool DTOOL_Call_ExtractThisPointer(PyObject *self, T *&into); +template INLINE bool DtoolInstance_GetPointer(PyObject *self, T *&into); +template 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) diff --git a/dtool/src/interrogatedb/py_wrappers.cxx b/dtool/src/interrogatedb/py_wrappers.cxx index db4e229348..4d56eee41a 100755 --- a/dtool/src/interrogatedb/py_wrappers.cxx +++ b/dtool/src/interrogatedb/py_wrappers.cxx @@ -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 diff --git a/dtool/src/interrogatedb/py_wrappers.h b/dtool/src/interrogatedb/py_wrappers.h index 66f2183299..7bf2c2e19f 100755 --- a/dtool/src/interrogatedb/py_wrappers.h +++ b/dtool/src/interrogatedb/py_wrappers.h @@ -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 diff --git a/makepanda/installer.nsi b/makepanda/installer.nsi index 8cfe6eba9a..ccf3b5b1b7 100755 --- a/makepanda/installer.nsi +++ b/makepanda/installer.nsi @@ -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 diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index b6b4c1648c..a13bd0e443 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -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 = [] diff --git a/makepanda/makewheel.py b/makepanda/makewheel.py index ae6a7183a0..b9b187c90f 100644 --- a/makepanda/makewheel.py +++ b/makepanda/makewheel.py @@ -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() diff --git a/panda/src/audio/audioLoadRequest.I b/panda/src/audio/audioLoadRequest.I index 0daf2df619..647692315d 100644 --- a/panda/src/audio/audioLoadRequest.I +++ b/panda/src/audio/audioLoadRequest.I @@ -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; } diff --git a/panda/src/audio/audioLoadRequest.cxx b/panda/src/audio/audioLoadRequest.cxx index c44780b354..e3a04d38b8 100644 --- a/panda/src/audio/audioLoadRequest.cxx +++ b/panda/src/audio/audioLoadRequest.cxx @@ -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; diff --git a/panda/src/audio/audioLoadRequest.h b/panda/src/audio/audioLoadRequest.h index d70a37d5b7..6d0e59bf4c 100644 --- a/panda/src/audio/audioLoadRequest.h +++ b/panda/src/audio/audioLoadRequest.h @@ -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; diff --git a/panda/src/pipeline/asyncTaskBase.I b/panda/src/cocoadisplay/cocoaGraphicsBuffer.I similarity index 81% rename from panda/src/pipeline/asyncTaskBase.I rename to panda/src/cocoadisplay/cocoaGraphicsBuffer.I index 09abcb3a3d..1611af94d1 100644 --- a/panda/src/pipeline/asyncTaskBase.I +++ b/panda/src/cocoadisplay/cocoaGraphicsBuffer.I @@ -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 */ diff --git a/panda/src/cocoadisplay/cocoaGraphicsBuffer.h b/panda/src/cocoadisplay/cocoaGraphicsBuffer.h new file mode 100644 index 0000000000..ab666b6f75 --- /dev/null +++ b/panda/src/cocoadisplay/cocoaGraphicsBuffer.h @@ -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 diff --git a/panda/src/cocoadisplay/cocoaGraphicsBuffer.mm b/panda/src/cocoadisplay/cocoaGraphicsBuffer.mm new file mode 100644 index 0000000000..ea6fb61e4e --- /dev/null +++ b/panda/src/cocoadisplay/cocoaGraphicsBuffer.mm @@ -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 + +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(); + } +} diff --git a/panda/src/cocoadisplay/cocoaGraphicsPipe.I b/panda/src/cocoadisplay/cocoaGraphicsPipe.I index 874bf12fea..0ac4a79d83 100644 --- a/panda/src/cocoadisplay/cocoaGraphicsPipe.I +++ b/panda/src/cocoadisplay/cocoaGraphicsPipe.I @@ -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; -} diff --git a/panda/src/cocoadisplay/cocoaGraphicsPipe.h b/panda/src/cocoadisplay/cocoaGraphicsPipe.h index d53456e332..6f2f863e9c 100644 --- a/panda/src/cocoadisplay/cocoaGraphicsPipe.h +++ b/panda/src/cocoadisplay/cocoaGraphicsPipe.h @@ -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; diff --git a/panda/src/cocoadisplay/cocoaGraphicsPipe.mm b/panda/src/cocoadisplay/cocoaGraphicsPipe.mm index 10bef93e2e..0baa94f8ed 100644 --- a/panda/src/cocoadisplay/cocoaGraphicsPipe.mm +++ b/panda/src/cocoadisplay/cocoaGraphicsPipe.mm @@ -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; diff --git a/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.I b/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.I index d593a3fe86..586df26850 100644 --- a/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.I +++ b/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.I @@ -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]); +} diff --git a/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.h b/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.h index a2420e55c2..2e83e60c34 100644 --- a/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.h +++ b/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.h @@ -19,6 +19,7 @@ #include "glgsg.h" #import +#import /** * 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; diff --git a/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.mm b/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.mm index 30630036ab..ec25b94486 100644 --- a/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.mm +++ b/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.mm @@ -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); diff --git a/panda/src/cocoadisplay/cocoaGraphicsWindow.mm b/panda/src/cocoadisplay/cocoaGraphicsWindow.mm index ebc9255d63..426c06cc78 100644 --- a/panda/src/cocoadisplay/cocoaGraphicsWindow.mm +++ b/panda/src/cocoadisplay/cocoaGraphicsWindow.mm @@ -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(); } diff --git a/panda/src/cocoadisplay/config_cocoadisplay.mm b/panda/src/cocoadisplay/config_cocoadisplay.mm index 4f67bc1438..42f2f98a8f 100644 --- a/panda/src/cocoadisplay/config_cocoadisplay.mm +++ b/panda/src/cocoadisplay/config_cocoadisplay.mm @@ -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(); diff --git a/panda/src/cocoadisplay/p3cocoadisplay_composite1.mm b/panda/src/cocoadisplay/p3cocoadisplay_composite1.mm index 28755a66dd..e85d1e08eb 100644 --- a/panda/src/cocoadisplay/p3cocoadisplay_composite1.mm +++ b/panda/src/cocoadisplay/p3cocoadisplay_composite1.mm @@ -1,4 +1,5 @@ #include "config_cocoadisplay.mm" +#include "cocoaGraphicsBuffer.mm" #include "cocoaGraphicsPipe.mm" #include "cocoaGraphicsStateGuardian.mm" #include "cocoaGraphicsWindow.mm" diff --git a/panda/src/display/graphicsOutput.I b/panda/src/display/graphicsOutput.I index 40a90af57e..393dbaadb5 100644 --- a/panda/src/display/graphicsOutput.I +++ b/panda/src/display/graphicsOutput.I @@ -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; } /** diff --git a/panda/src/display/graphicsOutput.cxx b/panda/src/display/graphicsOutput.cxx index 40895cff1f..4d8a1e27a1 100644 --- a/panda/src/display/graphicsOutput.cxx +++ b/panda/src/display/graphicsOutput.cxx @@ -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; } diff --git a/panda/src/display/graphicsOutput.h b/panda/src/display/graphicsOutput.h index 295467f458..2f9eff8683 100644 --- a/panda/src/display/graphicsOutput.h +++ b/panda/src/display/graphicsOutput.h @@ -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: diff --git a/panda/src/display/graphicsStateGuardian.cxx b/panda/src/display/graphicsStateGuardian.cxx index f4672a0198..2d07f2c971 100644 --- a/panda/src/display/graphicsStateGuardian.cxx +++ b/panda/src/display/graphicsStateGuardian.cxx @@ -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(); } /** diff --git a/panda/src/display/graphicsStateGuardian.h b/panda/src/display/graphicsStateGuardian.h index 52cae8ff28..8a0ca541d0 100644 --- a/panda/src/display/graphicsStateGuardian.h +++ b/panda/src/display/graphicsStateGuardian.h @@ -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; diff --git a/panda/src/event/asyncFuture.I b/panda/src/event/asyncFuture.I new file mode 100644 index 0000000000..37dca79038 --- /dev/null +++ b/panda/src/event/asyncFuture.I @@ -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(); +} diff --git a/panda/src/event/asyncFuture.cxx b/panda/src/event/asyncFuture.cxx new file mode 100644 index 0000000000..f1aaaba842 --- /dev/null +++ b/panda/src/event/asyncFuture.cxx @@ -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; + } +} diff --git a/panda/src/event/asyncFuture.h b/panda/src/event/asyncFuture.h new file mode 100644 index 0000000000..e3a835eea3 --- /dev/null +++ b/panda/src/event/asyncFuture.h @@ -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 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 diff --git a/panda/src/event/asyncFuture_ext.cxx b/panda/src/event/asyncFuture_ext.cxx new file mode 100644 index 0000000000..3150c09d7d --- /dev/null +++ b/panda/src/event/asyncFuture_ext.cxx @@ -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:: +__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:: +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:: +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:: +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 diff --git a/panda/src/event/asyncTask_ext.h b/panda/src/event/asyncFuture_ext.h old mode 100755 new mode 100644 similarity index 61% rename from panda/src/event/asyncTask_ext.h rename to panda/src/event/asyncFuture_ext.h index 89fd4cb8f4..21786ee17b --- a/panda/src/event/asyncTask_ext.h +++ b/panda/src/event/asyncFuture_ext.h @@ -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 : public ExtensionBase { +class Extension : public ExtensionBase { 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 diff --git a/panda/src/event/asyncTask.I b/panda/src/event/asyncTask.I index 3b4476afb7..869b0b33f7 100644 --- a/panda/src/event/asyncTask.I +++ b/panda/src/event/asyncTask.I @@ -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. diff --git a/panda/src/event/asyncTask.cxx b/panda/src/event/asyncTask.cxx index fb251aebd6..17f0a0630d 100644 --- a/panda/src/event/asyncTask.cxx +++ b/panda/src/event/asyncTask.cxx @@ -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); diff --git a/panda/src/event/asyncTask.h b/panda/src/event/asyncTask.h index b50a4a6e54..d514b2f5a3 100644 --- a/panda/src/event/asyncTask.h +++ b/panda/src/event/asyncTask.h @@ -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 _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; diff --git a/panda/src/event/asyncTaskChain.cxx b/panda/src/event/asyncTaskChain.cxx index 46975efb7c..47299d31ef 100644 --- a/panda/src/event/asyncTaskChain.cxx +++ b/panda/src/event/asyncTaskChain.cxx @@ -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::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); diff --git a/panda/src/event/asyncTaskChain.h b/panda/src/event/asyncTaskChain.h index 3dded59317..512a7b9fa0 100644 --- a/panda/src/event/asyncTaskChain.h +++ b/panda/src/event/asyncTaskChain.h @@ -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; diff --git a/panda/src/event/asyncTaskCollection.cxx b/panda/src/event/asyncTaskCollection.cxx index 38a07c6875..80999362e1 100644 --- a/panda/src/event/asyncTaskCollection.cxx +++ b/panda/src/event/asyncTaskCollection.cxx @@ -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"; } } diff --git a/panda/src/event/asyncTaskCollection.h b/panda/src/event/asyncTaskCollection.h index 824ec433c2..4ed6f6a59b 100644 --- a/panda/src/event/asyncTaskCollection.h +++ b/panda/src/event/asyncTaskCollection.h @@ -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; diff --git a/panda/src/event/asyncTaskManager.I b/panda/src/event/asyncTaskManager.I index 20294f63dc..c1e5775a55 100644 --- a/panda/src/event/asyncTaskManager.I +++ b/panda/src/event/asyncTaskManager.I @@ -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; diff --git a/panda/src/event/asyncTaskManager.cxx b/panda/src/event/asyncTaskManager.cxx index b95ddbf109..5e1bf9601c 100644 --- a/panda/src/event/asyncTaskManager.cxx +++ b/panda/src/event/asyncTaskManager.cxx @@ -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()) { diff --git a/panda/src/event/asyncTaskManager.h b/panda/src/event/asyncTaskManager.h index 8432b217a9..d843508de7 100644 --- a/panda/src/event/asyncTaskManager.h +++ b/panda/src/event/asyncTaskManager.h @@ -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 > 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; diff --git a/panda/src/event/asyncTask_ext.cxx b/panda/src/event/asyncTask_ext.cxx deleted file mode 100755 index bbfb46d2c1..0000000000 --- a/panda/src/event/asyncTask_ext.cxx +++ /dev/null @@ -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:: -__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 diff --git a/panda/src/event/config_event.cxx b/panda/src/event/config_event.cxx index 0f335981aa..85727d2d7c 100644 --- a/panda/src/event/config_event.cxx +++ b/panda/src/event/config_event.cxx @@ -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(); diff --git a/panda/src/event/eventHandler.cxx b/panda/src/event/eventHandler.cxx index d67d0661dd..020ba905d5 100644 --- a/panda/src/event/eventHandler.cxx +++ b/panda/src/event/eventHandler.cxx @@ -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); + } } diff --git a/panda/src/event/eventHandler.h b/panda/src/event/eventHandler.h index 05952a5082..39d84475a4 100644 --- a/panda/src/event/eventHandler.h +++ b/panda/src/event/eventHandler.h @@ -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 CallbackFunction; typedef pset CallbackFunctions; typedef pmap CallbackHooks; + typedef pmap Futures; Hooks _hooks; CallbackHooks _cbhooks; + Futures _futures; EventQueue &_queue; static EventHandler *_global_event_handler; diff --git a/panda/src/event/eventParameter.I b/panda/src/event/eventParameter.I index a6f512d72f..14727106e6 100644 --- a/panda/src/event/eventParameter.I +++ b/panda/src/event/eventParameter.I @@ -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. diff --git a/panda/src/event/eventParameter.h b/panda/src/event/eventParameter.h index ea960c156e..b0cbcc1376 100644 --- a/panda/src/event/eventParameter.h +++ b/panda/src/event/eventParameter.h @@ -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); diff --git a/panda/src/event/p3event_composite1.cxx b/panda/src/event/p3event_composite1.cxx index 57cc83159c..dc72d2b03f 100644 --- a/panda/src/event/p3event_composite1.cxx +++ b/panda/src/event/p3event_composite1.cxx @@ -1,3 +1,4 @@ +#include "asyncFuture.cxx" #include "asyncTask.cxx" #include "asyncTaskChain.cxx" #include "asyncTaskCollection.cxx" diff --git a/panda/src/event/pythonTask.I b/panda/src/event/pythonTask.I index 7280b4e7d6..78f8e30cc7 100644 --- a/panda/src/event/pythonTask.I +++ b/panda/src/event/pythonTask.I @@ -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); -} diff --git a/panda/src/event/pythonTask.cxx b/panda/src/event/pythonTask.cxx index 1b4adc0921..996587885e 100644 --- a/panda/src/event/pythonTask.cxx +++ b/panda/src/event/pythonTask.cxx @@ -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); } } diff --git a/panda/src/event/pythonTask.h b/panda/src/event/pythonTask.h index 59c3c65c5f..48826769de 100644 --- a/panda/src/event/pythonTask.h +++ b/panda/src/event/pythonTask.h @@ -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; + public: static TypeHandle get_class_type() { return _type_handle; diff --git a/panda/src/express/pointerToArray_ext.I b/panda/src/express/pointerToArray_ext.I index 6b8a9d54d0..644b5f0645 100644 --- a/panda/src/express/pointerToArray_ext.I +++ b/panda/src/express/pointerToArray_ext.I @@ -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"); } /** diff --git a/panda/src/glstuff/glGraphicsBuffer_src.cxx b/panda/src/glstuff/glGraphicsBuffer_src.cxx index e76f8fccb9..43f3dbd809 100644 --- a/panda/src/glstuff/glGraphicsBuffer_src.cxx +++ b/panda/src/glstuff/glGraphicsBuffer_src.cxx @@ -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 diff --git a/panda/src/glstuff/glGraphicsBuffer_src.h b/panda/src/glstuff/glGraphicsBuffer_src.h index d119148247..31f7c91912 100644 --- a/panda/src/glstuff/glGraphicsBuffer_src.h +++ b/panda/src/glstuff/glGraphicsBuffer_src.h @@ -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, diff --git a/panda/src/glstuff/glGraphicsStateGuardian_src.cxx b/panda/src/glstuff/glGraphicsStateGuardian_src.cxx index 45450ae98d..fab18bcb62 100644 --- a/panda/src/glstuff/glGraphicsStateGuardian_src.cxx +++ b/panda/src/glstuff/glGraphicsStateGuardian_src.cxx @@ -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; } } diff --git a/panda/src/gobj/animateVerticesRequest.I b/panda/src/gobj/animateVerticesRequest.I index 01226e4694..eb6ee589d0 100644 --- a/panda/src/gobj/animateVerticesRequest.I +++ b/panda/src/gobj/animateVerticesRequest.I @@ -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; } diff --git a/panda/src/gobj/animateVerticesRequest.cxx b/panda/src/gobj/animateVerticesRequest.cxx index 7c152406ed..f2ded8cdd1 100644 --- a/panda/src/gobj/animateVerticesRequest.cxx +++ b/panda/src/gobj/animateVerticesRequest.cxx @@ -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; diff --git a/panda/src/gobj/animateVerticesRequest.h b/panda/src/gobj/animateVerticesRequest.h index 8b20c4fe9d..be20651a13 100644 --- a/panda/src/gobj/animateVerticesRequest.h +++ b/panda/src/gobj/animateVerticesRequest.h @@ -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() { diff --git a/panda/src/gobj/config_gobj.cxx b/panda/src/gobj/config_gobj.cxx index 4df77f0527..84876a2b8f 100644 --- a/panda/src/gobj/config_gobj.cxx +++ b/panda/src/gobj/config_gobj.cxx @@ -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(); diff --git a/panda/src/gobj/preparedGraphicsObjects.cxx b/panda/src/gobj/preparedGraphicsObjects.cxx index 5c9ec855e3..233edb46b3 100644 --- a/panda/src/gobj/preparedGraphicsObjects.cxx +++ b/panda/src/gobj/preparedGraphicsObjects.cxx @@ -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 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 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(); diff --git a/panda/src/gobj/preparedGraphicsObjects.h b/panda/src/gobj/preparedGraphicsObjects.h index 1dbde2d305..3eaed37167 100644 --- a/panda/src/gobj/preparedGraphicsObjects.h +++ b/panda/src/gobj/preparedGraphicsObjects.h @@ -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 Textures; - typedef phash_set< PT(Texture) > EnqueuedTextures; + typedef phash_map< PT(Texture), PT(EnqueuedObject) > EnqueuedTextures; typedef phash_set Geoms; typedef phash_set< PT(Geom) > EnqueuedGeoms; typedef phash_set Shaders; - typedef phash_set< PT(Shader) > EnqueuedShaders; + typedef phash_map< PT(Shader), PT(EnqueuedObject) > EnqueuedShaders; typedef phash_set Buffers; typedef phash_set< PT(GeomVertexArrayData) > EnqueuedVertexBuffers; typedef phash_set< PT(GeomPrimitive) > EnqueuedIndexBuffers; diff --git a/panda/src/gobj/shader.cxx b/panda/src/gobj/shader.cxx index d60e51838f..a2c54a5d0b 100644 --- a/panda/src/gobj/shader.cxx +++ b/panda/src/gobj/shader.cxx @@ -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(); } /** diff --git a/panda/src/gobj/shader.h b/panda/src/gobj/shader.h index 6226b02960..3b23b2d698 100644 --- a/panda/src/gobj/shader.h +++ b/panda/src/gobj/shader.h @@ -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(); diff --git a/panda/src/gobj/texture.I b/panda/src/gobj/texture.I index 29b1702280..8f3a91a54c 100644 --- a/panda/src/gobj/texture.I +++ b/panda/src/gobj/texture.I @@ -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. diff --git a/panda/src/gobj/texture.cxx b/panda/src/gobj/texture.cxx index d3dd47dbb9..c4a8149ab4 100644 --- a/panda/src/gobj/texture.cxx +++ b/panda/src/gobj/texture.cxx @@ -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; } diff --git a/panda/src/gobj/texture.h b/panda/src/gobj/texture.h index 15dae5f02d..5816b8c538 100644 --- a/panda/src/gobj/texture.h +++ b/panda/src/gobj/texture.h @@ -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); diff --git a/panda/src/gobj/texturePeeker.cxx b/panda/src/gobj/texturePeeker.cxx index 42e619e501..b4e1be9224 100644 --- a/panda/src/gobj/texturePeeker.cxx +++ b/panda/src/gobj/texturePeeker.cxx @@ -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); +} diff --git a/panda/src/gobj/texturePeeker.h b/panda/src/gobj/texturePeeker.h index cb503bf041..ffe3aba2b1 100644 --- a/panda/src/gobj/texturePeeker.h +++ b/panda/src/gobj/texturePeeker.h @@ -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; diff --git a/panda/src/gobj/textureReloadRequest.I b/panda/src/gobj/textureReloadRequest.I index 4cc22d6d9c..d17bdc72b4 100644 --- a/panda/src/gobj/textureReloadRequest.I +++ b/panda/src/gobj/textureReloadRequest.I @@ -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; } diff --git a/panda/src/gobj/textureReloadRequest.cxx b/panda/src/gobj/textureReloadRequest.cxx index 0b5964e2ef..4c17b550b7 100644 --- a/panda/src/gobj/textureReloadRequest.cxx +++ b/panda/src/gobj/textureReloadRequest.cxx @@ -43,7 +43,6 @@ do_task() { _texture->prepare(_pgo); } } - _is_ready = true; // Don't continue the task; we're done. return DS_done; diff --git a/panda/src/gobj/textureReloadRequest.h b/panda/src/gobj/textureReloadRequest.h index ce0684a135..1e97c99ea2 100644 --- a/panda/src/gobj/textureReloadRequest.h +++ b/panda/src/gobj/textureReloadRequest.h @@ -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() { diff --git a/panda/src/gobj/texture_ext.cxx b/panda/src/gobj/texture_ext.cxx index 507fa41fc4..e81d40c15f 100644 --- a/panda/src/gobj/texture_ext.cxx +++ b/panda/src/gobj/texture_ext.cxx @@ -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:: 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; } } diff --git a/panda/src/pgraph/cullResult.cxx b/panda/src/pgraph/cullResult.cxx index 53751f07ca..0118888108 100644 --- a/panda/src/pgraph/cullResult.cxx +++ b/panda/src/pgraph/cullResult.cxx @@ -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(); diff --git a/panda/src/pgraph/cullTraverser.cxx b/panda/src/pgraph/cullTraverser.cxx index a796718828..7ed990637b 100644 --- a/panda/src/pgraph/cullTraverser.cxx +++ b/panda/src/pgraph/cullTraverser.cxx @@ -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); diff --git a/panda/src/pgraph/geomNode.cxx b/panda/src/pgraph/geomNode.cxx index 11d319212f..8c9da5d331 100644 --- a/panda/src/pgraph/geomNode.cxx +++ b/panda/src/pgraph/geomNode.cxx @@ -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. } diff --git a/panda/src/pgraph/loader.I b/panda/src/pgraph/loader.I index 25295c32dc..c86753b3be 100644 --- a/panda/src/pgraph/loader.I +++ b/panda/src/pgraph/loader.I @@ -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) { diff --git a/panda/src/pgraph/modelFlattenRequest.I b/panda/src/pgraph/modelFlattenRequest.I index 84e58744da..75f177e6a8 100644 --- a/panda/src/pgraph/modelFlattenRequest.I +++ b/panda/src/pgraph/modelFlattenRequest.I @@ -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; } diff --git a/panda/src/pgraph/modelFlattenRequest.cxx b/panda/src/pgraph/modelFlattenRequest.cxx index 2b02b58f00..0af2cafa15 100644 --- a/panda/src/pgraph/modelFlattenRequest.cxx +++ b/panda/src/pgraph/modelFlattenRequest.cxx @@ -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; diff --git a/panda/src/pgraph/modelFlattenRequest.h b/panda/src/pgraph/modelFlattenRequest.h index d3ff059482..a166490edb 100644 --- a/panda/src/pgraph/modelFlattenRequest.h +++ b/panda/src/pgraph/modelFlattenRequest.h @@ -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() { diff --git a/panda/src/pgraph/modelLoadRequest.I b/panda/src/pgraph/modelLoadRequest.I index 2b8ef41544..3fb37a1bdc 100644 --- a/panda/src/pgraph/modelLoadRequest.I +++ b/panda/src/pgraph/modelLoadRequest.I @@ -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; } diff --git a/panda/src/pgraph/modelLoadRequest.cxx b/panda/src/pgraph/modelLoadRequest.cxx index bc1b767b5c..4feba702ef 100644 --- a/panda/src/pgraph/modelLoadRequest.cxx +++ b/panda/src/pgraph/modelLoadRequest.cxx @@ -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; diff --git a/panda/src/pgraph/modelLoadRequest.h b/panda/src/pgraph/modelLoadRequest.h index 946a9db503..e743623fa3 100644 --- a/panda/src/pgraph/modelLoadRequest.h +++ b/panda/src/pgraph/modelLoadRequest.h @@ -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() { diff --git a/panda/src/pgraph/modelSaveRequest.I b/panda/src/pgraph/modelSaveRequest.I index 14940aaf0f..e84b8929af 100644 --- a/panda/src/pgraph/modelSaveRequest.I +++ b/panda/src/pgraph/modelSaveRequest.I @@ -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; } diff --git a/panda/src/pgraph/modelSaveRequest.cxx b/panda/src/pgraph/modelSaveRequest.cxx index 45f985454a..31c50cfdf6 100644 --- a/panda/src/pgraph/modelSaveRequest.cxx +++ b/panda/src/pgraph/modelSaveRequest.cxx @@ -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; diff --git a/panda/src/pgraph/modelSaveRequest.h b/panda/src/pgraph/modelSaveRequest.h index 5fe3c683ae..7bbe1e331d 100644 --- a/panda/src/pgraph/modelSaveRequest.h +++ b/panda/src/pgraph/modelSaveRequest.h @@ -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: diff --git a/panda/src/pgraph/nodePathCollection_ext.cxx b/panda/src/pgraph/nodePathCollection_ext.cxx index c6f17b7aa4..32f5ac0d13 100644 --- a/panda/src/pgraph/nodePathCollection_ext.cxx +++ b/panda/src/pgraph/nodePathCollection_ext.cxx @@ -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"; diff --git a/panda/src/pgraph/nodePath_ext.cxx b/panda/src/pgraph/nodePath_ext.cxx index db517c8453..1c7576290a 100644 --- a/panda/src/pgraph/nodePath_ext.cxx +++ b/panda/src/pgraph/nodePath_ext.cxx @@ -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)); diff --git a/panda/src/pgraph/shaderInput_ext.cxx b/panda/src/pgraph/shaderInput_ext.cxx index 00f42d7d53..d09a971b46 100644 --- a/panda/src/pgraph/shaderInput_ext.cxx +++ b/panda/src/pgraph/shaderInput_ext.cxx @@ -91,80 +91,79 @@ __init__(CPT_InternalName name, PyObject *value, int priority) { _this->_stored_vector = LCAST(PN_stdfloat, vec); } - } else if (DtoolCanThisBeAPandaInstance(value)) { - Dtool_PyInstDef *inst = (Dtool_PyInstDef *)value; + } else if (DtoolInstance_Check(value)) { void *ptr; - if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_Texture))) { + if ((ptr = DtoolInstance_UPCAST(value, Dtool_Texture))) { _this->_type = ShaderInput::M_texture; _this->_value = (Texture *)ptr; - } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_NodePath))) { + } else if ((ptr = DtoolInstance_UPCAST(value, Dtool_NodePath))) { _this->_type = ShaderInput::M_nodepath; _this->_value = new ParamNodePath(*(const NodePath *)ptr); - } else if (inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToVoid)) { - if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_float))) { + } else if (DtoolInstance_UPCAST(value, Dtool_PointerToVoid)) { + if ((ptr = DtoolInstance_UPCAST(value, Dtool_PointerToArray_float))) { _this->_type = ShaderInput::M_numeric; _this->_stored_ptr = *(const PTA_float *)ptr; - } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_double))) { + } else if ((ptr = DtoolInstance_UPCAST(value, Dtool_PointerToArray_double))) { _this->_type = ShaderInput::M_numeric; _this->_stored_ptr = *(const PTA_double *)ptr; - } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_int))) { + } else if ((ptr = DtoolInstance_UPCAST(value, Dtool_PointerToArray_int))) { _this->_type = ShaderInput::M_numeric; _this->_stored_ptr = *(const PTA_int *)ptr; - } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_UnalignedLVecBase4f))) { + } else if ((ptr = DtoolInstance_UPCAST(value, Dtool_PointerToArray_UnalignedLVecBase4f))) { _this->_type = ShaderInput::M_numeric; _this->_stored_ptr = *(const PTA_LVecBase4f *)ptr; - } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_LVecBase3f))) { + } else if ((ptr = DtoolInstance_UPCAST(value, Dtool_PointerToArray_LVecBase3f))) { _this->_type = ShaderInput::M_numeric; _this->_stored_ptr = *(const PTA_LVecBase3f *)ptr; - } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_LVecBase2f))) { + } else if ((ptr = DtoolInstance_UPCAST(value, Dtool_PointerToArray_LVecBase2f))) { _this->_type = ShaderInput::M_numeric; _this->_stored_ptr = *(const PTA_LVecBase2f *)ptr; - } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_UnalignedLMatrix4f))) { + } else if ((ptr = DtoolInstance_UPCAST(value, Dtool_PointerToArray_UnalignedLMatrix4f))) { _this->_type = ShaderInput::M_numeric; _this->_stored_ptr = *(const PTA_LMatrix4f *)ptr; - } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_LMatrix3f))) { + } else if ((ptr = DtoolInstance_UPCAST(value, Dtool_PointerToArray_LMatrix3f))) { _this->_type = ShaderInput::M_numeric; _this->_stored_ptr = *(const PTA_LMatrix3f *)ptr; - } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_UnalignedLVecBase4d))) { + } else if ((ptr = DtoolInstance_UPCAST(value, Dtool_PointerToArray_UnalignedLVecBase4d))) { _this->_type = ShaderInput::M_numeric; _this->_stored_ptr = *(const PTA_LVecBase4d *)ptr; - } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_LVecBase3d))) { + } else if ((ptr = DtoolInstance_UPCAST(value, Dtool_PointerToArray_LVecBase3d))) { _this->_type = ShaderInput::M_numeric; _this->_stored_ptr = *(const PTA_LVecBase3d *)ptr; - } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_LVecBase2d))) { + } else if ((ptr = DtoolInstance_UPCAST(value, Dtool_PointerToArray_LVecBase2d))) { _this->_type = ShaderInput::M_numeric; _this->_stored_ptr = *(const PTA_LVecBase2d *)ptr; - } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_UnalignedLMatrix4d))) { + } else if ((ptr = DtoolInstance_UPCAST(value, Dtool_PointerToArray_UnalignedLMatrix4d))) { _this->_type = ShaderInput::M_numeric; _this->_stored_ptr = *(const PTA_LMatrix4d *)ptr; - } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_LMatrix3d))) { + } else if ((ptr = DtoolInstance_UPCAST(value, Dtool_PointerToArray_LMatrix3d))) { _this->_type = ShaderInput::M_numeric; _this->_stored_ptr = *(const PTA_LMatrix3d *)ptr; - } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_UnalignedLVecBase4i))) { + } else if ((ptr = DtoolInstance_UPCAST(value, Dtool_PointerToArray_UnalignedLVecBase4i))) { _this->_type = ShaderInput::M_numeric; _this->_stored_ptr = *(const PTA_LVecBase4i *)ptr; - } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_LVecBase3i))) { + } else if ((ptr = DtoolInstance_UPCAST(value, Dtool_PointerToArray_LVecBase3i))) { _this->_type = ShaderInput::M_numeric; _this->_stored_ptr = *(const PTA_LVecBase3i *)ptr; - } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_LVecBase2i))) { + } else if ((ptr = DtoolInstance_UPCAST(value, Dtool_PointerToArray_LVecBase2i))) { _this->_type = ShaderInput::M_numeric; _this->_stored_ptr = *(const PTA_LVecBase2i *)ptr; @@ -173,81 +172,81 @@ __init__(CPT_InternalName name, PyObject *value, int priority) { return; } - } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_LMatrix4f))) { + } else if ((ptr = DtoolInstance_UPCAST(value, Dtool_LMatrix4f))) { _this->_type = ShaderInput::M_numeric; _this->_stored_ptr = *(const LMatrix4f *)ptr; - } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_LMatrix3f))) { + } else if ((ptr = DtoolInstance_UPCAST(value, Dtool_LMatrix3f))) { _this->_type = ShaderInput::M_numeric; _this->_stored_ptr = *(const LMatrix3f *)ptr; - } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_LMatrix4d))) { + } else if ((ptr = DtoolInstance_UPCAST(value, Dtool_LMatrix4d))) { _this->_type = ShaderInput::M_numeric; _this->_stored_ptr = *(const LMatrix4d *)ptr; - } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_LMatrix3d))) { + } else if ((ptr = DtoolInstance_UPCAST(value, Dtool_LMatrix3d))) { _this->_type = ShaderInput::M_numeric; _this->_stored_ptr = *(const LMatrix3d *)ptr; - } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_LVecBase4f))) { + } else if ((ptr = DtoolInstance_UPCAST(value, Dtool_LVecBase4f))) { const LVecBase4f &vec = *(const LVecBase4f *)ptr; _this->_type = ShaderInput::M_vector; _this->_stored_ptr = vec; _this->_stored_vector = LCAST(PN_stdfloat, vec); - } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_LVecBase3f))) { + } else if ((ptr = DtoolInstance_UPCAST(value, Dtool_LVecBase3f))) { const LVecBase3f &vec = *(const LVecBase3f *)ptr; _this->_type = ShaderInput::M_vector; _this->_stored_ptr = vec; _this->_stored_vector.set(vec[0], vec[1], vec[2], 0); - } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_LVecBase2f))) { + } else if ((ptr = DtoolInstance_UPCAST(value, Dtool_LVecBase2f))) { const LVecBase2f &vec = *(const LVecBase2f *)ptr; _this->_type = ShaderInput::M_vector; _this->_stored_ptr = vec; _this->_stored_vector.set(vec[0], vec[1], 0, 0); - } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_LVecBase4d))) { + } else if ((ptr = DtoolInstance_UPCAST(value, Dtool_LVecBase4d))) { const LVecBase4d &vec = *(const LVecBase4d *)ptr; _this->_type = ShaderInput::M_vector; _this->_stored_ptr = vec; _this->_stored_vector = LCAST(PN_stdfloat, vec); - } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_LVecBase3d))) { + } else if ((ptr = DtoolInstance_UPCAST(value, Dtool_LVecBase3d))) { const LVecBase3d &vec = *(const LVecBase3d *)ptr; _this->_type = ShaderInput::M_vector; _this->_stored_ptr = vec; _this->_stored_vector.set(vec[0], vec[1], vec[2], 0); - } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_LVecBase2d))) { + } else if ((ptr = DtoolInstance_UPCAST(value, Dtool_LVecBase2d))) { const LVecBase2d &vec = *(const LVecBase2d *)ptr; _this->_type = ShaderInput::M_vector; _this->_stored_ptr = vec; _this->_stored_vector.set(vec[0], vec[1], 0, 0); - } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_LVecBase4i))) { + } else if ((ptr = DtoolInstance_UPCAST(value, Dtool_LVecBase4i))) { const LVecBase4i &vec = *(const LVecBase4i *)ptr; _this->_type = ShaderInput::M_numeric; _this->_stored_ptr = vec; _this->_stored_vector = LCAST(PN_stdfloat, vec); - } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_LVecBase3i))) { + } else if ((ptr = DtoolInstance_UPCAST(value, Dtool_LVecBase3i))) { const LVecBase3i &vec = *(const LVecBase3i *)ptr; _this->_type = ShaderInput::M_numeric; _this->_stored_ptr = vec; _this->_stored_vector.set(vec[0], vec[1], vec[2], 0); - } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_LVecBase2i))) { + } else if ((ptr = DtoolInstance_UPCAST(value, Dtool_LVecBase2i))) { const LVecBase2i &vec = *(const LVecBase2i *)ptr; _this->_type = ShaderInput::M_numeric; _this->_stored_ptr = vec; _this->_stored_vector.set(vec[0], vec[1], 0, 0); - } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_ShaderBuffer))) { + } else if ((ptr = DtoolInstance_UPCAST(value, Dtool_ShaderBuffer))) { _this->_type = ShaderInput::M_buffer; _this->_value = (ShaderBuffer *)ptr; - } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_ParamValueBase))) { + } else if ((ptr = DtoolInstance_UPCAST(value, Dtool_ParamValueBase))) { _this->_type = ShaderInput::M_param; _this->_value = (ParamValueBase *)ptr; diff --git a/panda/src/pipeline/asyncTaskBase.cxx b/panda/src/pipeline/asyncTaskBase.cxx deleted file mode 100644 index 4d91d83764..0000000000 --- a/panda/src/pipeline/asyncTaskBase.cxx +++ /dev/null @@ -1,77 +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 asyncTaskBase.cxx - * @author drose - * @date 2010-02-09 - */ - -#include "asyncTaskBase.h" -#include "thread.h" -#include "atomicAdjust.h" - -TypeHandle AsyncTaskBase::_type_handle; - -/** - * - */ -AsyncTaskBase:: -AsyncTaskBase() { -} - -/** - * - */ -AsyncTaskBase:: -~AsyncTaskBase() { -} - -/** - * Indicates that this task is now the current task running on the indicated - * thread, presumably the current thread. - */ -void AsyncTaskBase:: -record_task(Thread *current_thread) { - nassertv(current_thread->_current_task == NULL); - - void *result = AtomicAdjust::compare_and_exchange_ptr - ((void * TVOLATILE &)current_thread->_current_task, - (void *)NULL, (void *)this); - - // If the return value is other than NULL, 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__ - nassertv(result == NULL); - nassertv(current_thread->_current_task == this); -#endif // __GNUC__ -} - -/** - * Indicates that this task is no longer running on the indicated thread. - */ -void AsyncTaskBase:: -clear_task(Thread *current_thread) { - nassertv(current_thread->_current_task == this); - - void *result = AtomicAdjust::compare_and_exchange_ptr - ((void * TVOLATILE &)current_thread->_current_task, - (void *)this, (void *)NULL); - - // 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__ - nassertv(result == this); - nassertv(current_thread->_current_task == NULL); -#endif // __GNUC__ -} diff --git a/panda/src/pipeline/asyncTaskBase.h b/panda/src/pipeline/asyncTaskBase.h deleted file mode 100644 index cdf3884b87..0000000000 --- a/panda/src/pipeline/asyncTaskBase.h +++ /dev/null @@ -1,61 +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 asyncTaskBase.h - * @author drose - * @date 2010-02-09 - */ - -#ifndef ASYNCTASKBASE_H -#define ASYNCTASKBASE_H - -#include "pandabase.h" - -#include "typedReferenceCount.h" -#include "namable.h" - -class Thread; - -/** - * The abstract base class for AsyncTask. This is defined here only so we can - * store a pointer to the current task on the Thread. - */ -class EXPCL_PANDA_PIPELINE AsyncTaskBase : public TypedReferenceCount, public Namable { -protected: - AsyncTaskBase(); -public: - ALLOC_DELETED_CHAIN(AsyncTaskBase); - -PUBLISHED: - virtual ~AsyncTaskBase(); - -protected: - void record_task(Thread *current_thread); - void clear_task(Thread *current_thread); - -public: - static TypeHandle get_class_type() { - return _type_handle; - } - static void init_type() { - TypedReferenceCount::init_type(); - register_type(_type_handle, "AsyncTaskBase", - 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; -}; - -#include "asyncTaskBase.I" - -#endif diff --git a/panda/src/pipeline/config_pipeline.cxx b/panda/src/pipeline/config_pipeline.cxx index 24b23da485..6dc24178f0 100644 --- a/panda/src/pipeline/config_pipeline.cxx +++ b/panda/src/pipeline/config_pipeline.cxx @@ -12,7 +12,6 @@ */ #include "config_pipeline.h" -#include "asyncTaskBase.h" #include "mainThread.h" #include "externalThread.h" #include "genericThread.h" @@ -67,7 +66,6 @@ init_libpipeline() { } initialized = true; - AsyncTaskBase::init_type(); MainThread::init_type(); ExternalThread::init_type(); GenericThread::init_type(); diff --git a/panda/src/pipeline/p3pipeline_composite1.cxx b/panda/src/pipeline/p3pipeline_composite1.cxx index 40dd5b3647..487c5ca0ad 100644 --- a/panda/src/pipeline/p3pipeline_composite1.cxx +++ b/panda/src/pipeline/p3pipeline_composite1.cxx @@ -1,4 +1,3 @@ -#include "asyncTaskBase.cxx" #include "conditionVar.cxx" #include "conditionVarDebug.cxx" #include "conditionVarDirect.cxx" diff --git a/panda/src/pipeline/thread.I b/panda/src/pipeline/thread.I index 321d24da6a..12a8607e63 100644 --- a/panda/src/pipeline/thread.I +++ b/panda/src/pipeline/thread.I @@ -277,7 +277,7 @@ preempt() { * AsyncTaskManager), if any, or NULL if the thread is not currently servicing * a task. */ -INLINE AsyncTaskBase *Thread:: +INLINE AsyncTask *Thread:: get_current_task() const { return _current_task; } diff --git a/panda/src/pipeline/thread.h b/panda/src/pipeline/thread.h index 80844ea409..4dbebacc8b 100644 --- a/panda/src/pipeline/thread.h +++ b/panda/src/pipeline/thread.h @@ -28,7 +28,7 @@ class ReMutex; class MutexDebug; class ConditionVarDebug; class ConditionVarFullDebug; -class AsyncTaskBase; +class AsyncTask; /** * A thread; that is, a lightweight process. This is an abstract base class; @@ -89,7 +89,7 @@ PUBLISHED: BLOCKING INLINE void join(); INLINE void preempt(); - INLINE AsyncTaskBase *get_current_task() const; + INLINE AsyncTask *get_current_task() const; INLINE void set_python_index(int index); @@ -142,7 +142,7 @@ private: int _pipeline_stage; PStatsCallback *_pstats_callback; bool _joinable; - AsyncTaskBase *_current_task; + AsyncTask *_current_task; int _python_index; @@ -184,7 +184,7 @@ private: friend class ThreadPosixImpl; friend class ThreadSimpleImpl; friend class MainThread; - friend class AsyncTaskBase; + friend class AsyncTask; }; INLINE ostream &operator << (ostream &out, const Thread &thread); diff --git a/panda/src/pnmimage/convert_srgb.I b/panda/src/pnmimage/convert_srgb.I index 57d8d42170..f49cc38203 100644 --- a/panda/src/pnmimage/convert_srgb.I +++ b/panda/src/pnmimage/convert_srgb.I @@ -153,3 +153,17 @@ encode_sRGB_uchar(const LColorf &color, xel &into, xelval &into_alpha) { into_alpha = (xelval) (color[3] * 255.f + 0.5f); #endif } + + +/** + * Double-precision versions of the above. + */ +INLINE void +encode_sRGB_uchar(const LColord &color, xel &into) { + return encode_sRGB_uchar(LCAST(float, color), into); +} + +INLINE void +encode_sRGB_uchar(const LColord &color, xel &into, xelval &into_alpha) { + return encode_sRGB_uchar(LCAST(float, color), into, into_alpha); +} diff --git a/panda/src/pnmimage/convert_srgb.h b/panda/src/pnmimage/convert_srgb.h index dada8918b3..86bc8b07bf 100644 --- a/panda/src/pnmimage/convert_srgb.h +++ b/panda/src/pnmimage/convert_srgb.h @@ -46,6 +46,11 @@ EXPCL_PANDA_PNMIMAGE INLINE void encode_sRGB_uchar(const LColorf &from, EXPCL_PANDA_PNMIMAGE INLINE void encode_sRGB_uchar(const LColorf &from, xel &into, xelval &into_alpha); +EXPCL_PANDA_PNMIMAGE INLINE void encode_sRGB_uchar(const LColord &from, + xel &into); +EXPCL_PANDA_PNMIMAGE INLINE void encode_sRGB_uchar(const LColord &from, + xel &into, xelval &into_alpha); + // Use these functions if you know that SSE2 support is available. Otherwise, // they will crash! #if defined(__SSE2__) || defined(__i386__) || defined(_M_IX86) || defined(_M_X64) || defined(_M_AMD64) diff --git a/panda/src/pnmimage/pnmImage.I b/panda/src/pnmimage/pnmImage.I index 173f543e1e..d4a7026483 100644 --- a/panda/src/pnmimage/pnmImage.I +++ b/panda/src/pnmimage/pnmImage.I @@ -80,7 +80,7 @@ to_val(float input_value) const { switch (_xel_encoding) { case XE_generic: case XE_generic_alpha: - return clamp_val((int)(input_value * get_maxval() + 0.5f)); + return (int)(min(1.0f, max(0.0f, input_value)) * get_maxval() + 0.5f); case XE_generic_sRGB: case XE_generic_sRGB_alpha: diff --git a/panda/src/putil/typedWritable_ext.cxx b/panda/src/putil/typedWritable_ext.cxx index 6cc04c54d7..efaaac63fb 100644 --- a/panda/src/putil/typedWritable_ext.cxx +++ b/panda/src/putil/typedWritable_ext.cxx @@ -15,6 +15,8 @@ #ifdef HAVE_PYTHON +#include "bamWriter.h" + #ifndef CPPPARSER extern Dtool_PyTypedObject Dtool_BamWriter; #endif // CPPPARSER @@ -65,7 +67,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); } } diff --git a/tests/event/test_futures.py b/tests/event/test_futures.py new file mode 100644 index 0000000000..0778605da4 --- /dev/null +++ b/tests/event/test_futures.py @@ -0,0 +1,311 @@ +from panda3d import core +import pytest +import threading +import time +import sys + +if sys.version_info >= (3,): + from concurrent.futures._base import TimeoutError, CancelledError +else: + TimeoutError = Exception + CancelledError = Exception + + +def test_future_cancelled(): + fut = core.AsyncFuture() + + assert not fut.done() + assert not fut.cancelled() + fut.cancel() + assert fut.done() + assert fut.cancelled() + + with pytest.raises(CancelledError): + fut.result() + + # Works more than once + with pytest.raises(CancelledError): + fut.result() + + +def test_future_timeout(): + fut = core.AsyncFuture() + + with pytest.raises(TimeoutError): + fut.result(0.001) + + # Works more than once + with pytest.raises(TimeoutError): + fut.result(0.001) + + +def test_future_wait(): + fut = core.AsyncFuture() + + # Launch a thread to set the result value. + def thread_main(): + time.sleep(0.001) + fut.set_result(None) + + thread = threading.Thread(target=thread_main) + thread.start() + + # Make sure it didn't sneakily already run the thread + assert not fut.done() + + assert fut.result() is None + + assert fut.done() + assert not fut.cancelled() + assert fut.result() is None + + +def test_future_wait_cancel(): + fut = core.AsyncFuture() + + # Launch a thread to cancel the future. + def thread_main(): + time.sleep(0.001) + fut.cancel() + + thread = threading.Thread(target=thread_main) + thread.start() + + # Make sure it didn't sneakily already run the thread + assert not fut.done() + + with pytest.raises(CancelledError): + fut.result() + + assert fut.done() + assert fut.cancelled() + with pytest.raises(CancelledError): + fut.result() + + +def test_task_cancel(): + task_mgr = core.AsyncTaskManager.get_global_ptr() + task = core.PythonTask(lambda task: task.done) + task_mgr.add(task) + + assert not task.done() + task_mgr.remove(task) + assert task.done() + assert task.cancelled() + + with pytest.raises(CancelledError): + task.result() + + +def test_task_cancel_during_run(): + task_mgr = core.AsyncTaskManager.get_global_ptr() + task_chain = task_mgr.make_task_chain("test_task_cancel_during_run") + + def task_main(task): + task.remove() + + # It won't yet be marked done until after it returns. + assert not task.done() + return task.done + + task = core.PythonTask(task_main) + task.set_task_chain(task_chain.name) + task_mgr.add(task) + task_chain.wait_for_tasks() + + assert task.done() + assert task.cancelled() + with pytest.raises(CancelledError): + task.result() + + +def test_task_result(): + task_mgr = core.AsyncTaskManager.get_global_ptr() + task_chain = task_mgr.make_task_chain("test_task_result") + + def task_main(task): + task.set_result(42) + + # It won't yet be marked done until after it returns. + assert not task.done() + return core.PythonTask.done + + task = core.PythonTask(task_main) + task.set_task_chain(task_chain.name) + task_mgr.add(task) + task_chain.wait_for_tasks() + + assert task.done() + assert not task.cancelled() + assert task.result() == 42 + + +def test_coro_exception(): + task_mgr = core.AsyncTaskManager.get_global_ptr() + task_chain = task_mgr.make_task_chain("test_coro_exception") + + def coro_main(): + raise RuntimeError + yield None + + task = core.PythonTask(coro_main()) + task.set_task_chain(task_chain.name) + task_mgr.add(task) + task_chain.wait_for_tasks() + + assert task.done() + assert not task.cancelled() + with pytest.raises(RuntimeError): + task.result() + + +def test_future_gather(): + fut1 = core.AsyncFuture() + fut2 = core.AsyncFuture() + + # 0 and 1 arguments are special + assert core.AsyncFuture.gather().done() + assert core.AsyncFuture.gather(fut1) == fut1 + + # Gathering not-done futures + gather = core.AsyncFuture.gather(fut1, fut2) + assert not gather.done() + + # One future done + fut1.set_result(1) + assert not gather.done() + + # Two futures done + fut2.set_result(2) + assert gather.done() + + assert not gather.cancelled() + assert tuple(gather.result()) == (1, 2) + + +def test_future_gather_cancel_inner(): + fut1 = core.AsyncFuture() + fut2 = core.AsyncFuture() + + # Gathering not-done futures + gather = core.AsyncFuture.gather(fut1, fut2) + assert not gather.done() + + # One future cancelled + fut1.cancel() + assert not gather.done() + + # Two futures cancelled + fut2.set_result(2) + assert gather.done() + + assert not gather.cancelled() + with pytest.raises(CancelledError): + assert gather.result() + + +def test_future_gather_cancel_outer(): + fut1 = core.AsyncFuture() + fut2 = core.AsyncFuture() + + # Gathering not-done futures + gather = core.AsyncFuture.gather(fut1, fut2) + assert not gather.done() + + assert gather.cancel() + assert gather.done() + assert gather.cancelled() + + with pytest.raises(CancelledError): + assert gather.result() + + +def test_future_done_callback(): + fut = core.AsyncFuture() + + # Use the list hack since Python 2 doesn't have the "nonlocal" keyword. + called = [False] + def on_done(arg): + assert arg == fut + called[0] = True + + fut.add_done_callback(on_done) + fut.cancel() + assert fut.done() + + task_mgr = core.AsyncTaskManager.get_global_ptr() + task_mgr.poll() + assert called[0] + + +def test_future_done_callback_already_done(): + # Same as above, but with the future already done when add_done_callback + # is called. + fut = core.AsyncFuture() + fut.cancel() + assert fut.done() + + # Use the list hack since Python 2 doesn't have the "nonlocal" keyword. + called = [False] + def on_done(arg): + assert arg == fut + called[0] = True + + fut.add_done_callback(on_done) + + task_mgr = core.AsyncTaskManager.get_global_ptr() + task_mgr.poll() + assert called[0] + + +def test_event_future(): + queue = core.EventQueue() + handler = core.EventHandler(queue) + + fut = handler.get_future("test") + + # If we ask again, we should get the same one. + assert handler.get_future("test") == fut + + event = core.Event("test") + handler.dispatch_event(event) + + assert fut.done() + assert not fut.cancelled() + assert fut.result() == event + + +def test_event_future_cancel(): + # This is a very strange thing to do, but it's possible, so let's make + # sure it gives defined behavior. + queue = core.EventQueue() + handler = core.EventHandler(queue) + + fut = handler.get_future("test") + fut.cancel() + + assert fut.done() + assert fut.cancelled() + + event = core.Event("test") + handler.dispatch_event(event) + + assert fut.done() + assert fut.cancelled() + + +def test_event_future_cancel2(): + queue = core.EventQueue() + handler = core.EventHandler(queue) + + # Make sure we get a new future if we cancelled the first one. + fut = handler.get_future("test") + fut.cancel() + fut2 = handler.get_future("test") + + assert fut != fut2 + assert fut.done() + assert fut.cancelled() + assert not fut2.done() + assert not fut2.cancelled() + diff --git a/tests/gobj/test_texture.py b/tests/gobj/test_texture.py new file mode 100644 index 0000000000..8fa0ed27c8 --- /dev/null +++ b/tests/gobj/test_texture.py @@ -0,0 +1,90 @@ +from panda3d.core import Texture, PNMImage +from array import array + + +def image_from_stored_pixel(component_type, format, data): + """ Creates a 1-pixel texture with the given settings and pixel data, + then returns a PNMImage as result of calling texture.store(). """ + + tex = Texture("") + tex.setup_1d_texture(1, component_type, format) + tex.set_ram_image(data) + + img = PNMImage() + assert tex.store(img) + return img + + +def test_texture_store_unsigned_byte(): + data = array('B', (2, 1, 0, 0xff)) + img = image_from_stored_pixel(Texture.T_unsigned_byte, Texture.F_rgba, data) + + assert img.maxval == 0xff + pix = img.get_pixel(0, 0) + assert tuple(pix) == (0, 1, 2, 0xff) + + +def test_texture_store_unsigned_short(): + data = array('H', (2, 1, 0, 0xffff)) + img = image_from_stored_pixel(Texture.T_unsigned_short, Texture.F_rgba, data) + + assert img.maxval == 0xffff + pix = img.get_pixel(0, 0) + assert tuple(pix) == (0, 1, 2, 0xffff) + + +def test_texture_store_unsigned_int(): + data = array('I', (0x30000, 2, 0, 0xffffffff)) + img = image_from_stored_pixel(Texture.T_unsigned_int, Texture.F_rgba, data) + + assert img.maxval == 0xffff + pix = img.get_pixel(0, 0) + assert tuple(pix) == (0, 0, 3, 0xffff) + + +def test_texture_store_float(): + data = array('f', (0.5, 0.0, -2.0, 10000.0)) + img = image_from_stored_pixel(Texture.T_float, Texture.F_rgba, data) + + assert img.maxval == 0xffff + col = img.get_xel_a(0, 0) + assert col.almost_equal((0.0, 0.0, 0.5, 1.0), 1 / 255.0) + + +def test_texture_store_half(): + # Python's array class doesn't support half floats, so we hardcode the + # binary representation of these numbers: + data = array('H', ( + # -[exp][mantissa] + 0b0011110000000000, # 1.0 + 0b1100000000000000, # -2.0 + 0b0111101111111111, # 65504.0 + 0b0011010101010101, # 0.333251953125 + )) + img = image_from_stored_pixel(Texture.T_half_float, Texture.F_rgba, data) + + assert img.maxval == 0xffff + col = img.get_xel_a(0, 0) + assert col.almost_equal((1.0, 0.0, 1.0, 0.333251953125), 1 / 255.0) + + +def test_texture_store_srgb(): + # 188 = roughly middle gray + data = array('B', [188, 188, 188]) + img = image_from_stored_pixel(Texture.T_unsigned_byte, Texture.F_srgb, data) + + # We allow some imprecision. + assert img.maxval == 0xff + col = img.get_xel(0, 0) + assert col.almost_equal((0.5, 0.5, 0.5), 1 / 255.0) + + +def test_texture_store_srgb_alpha(): + # 188 = middle gray + data = array('B', [188, 188, 188, 188]) + img = image_from_stored_pixel(Texture.T_unsigned_byte, Texture.F_srgb_alpha, data) + + # We allow some imprecision. + assert img.maxval == 0xff + col = img.get_xel_a(0, 0) + assert col.almost_equal((0.5, 0.5, 0.5, 188 / 255.0), 1 / 255.0) diff --git a/tests/gobj/test_texture_peek.py b/tests/gobj/test_texture_peek.py new file mode 100644 index 0000000000..accd1b02d0 --- /dev/null +++ b/tests/gobj/test_texture_peek.py @@ -0,0 +1,96 @@ +from panda3d.core import Texture, LColor +from array import array + + +def peeker_from_pixel(component_type, format, data): + """ Creates a 1-pixel texture with the given settings and pixel data, + then returns a TexturePeeker as result of calling texture.peek(). """ + + tex = Texture("") + tex.setup_1d_texture(1, component_type, format) + tex.set_ram_image(data) + peeker = tex.peek() + assert peeker.has_pixel(0, 0) + return peeker + + +def test_texture_peek_ubyte(): + maxval = 255 + data = array('B', (2, 1, 0, maxval)) + peeker = peeker_from_pixel(Texture.T_unsigned_byte, Texture.F_rgba, data) + + col = LColor() + peeker.fetch_pixel(col, 0, 0) + col *= maxval + assert col == (0, 1, 2, maxval) + + +def test_texture_peek_ushort(): + maxval = 65535 + data = array('H', (2, 1, 0, maxval)) + peeker = peeker_from_pixel(Texture.T_unsigned_short, Texture.F_rgba, data) + + col = LColor() + peeker.fetch_pixel(col, 0, 0) + col *= maxval + assert col == (0, 1, 2, maxval) + + +def test_texture_peek_uint(): + maxval = 4294967295 + data = array('I', (2, 1, 0, maxval)) + peeker = peeker_from_pixel(Texture.T_unsigned_int, Texture.F_rgba, data) + + col = LColor() + peeker.fetch_pixel(col, 0, 0) + col *= maxval + assert col == (0, 1, 2, maxval) + + +def test_texture_peek_float(): + data = array('f', (1.0, 0.0, -2.0, 10000.0)) + peeker = peeker_from_pixel(Texture.T_float, Texture.F_rgba, data) + + col = LColor() + peeker.fetch_pixel(col, 0, 0) + assert col == (-2.0, 0.0, 1.0, 10000.0) + + +def test_texture_peek_half(): + # Python's array class doesn't support half floats, so we hardcode the + # binary representation of these numbers: + data = array('H', ( + 0b0011110000000000, # 1.0 + 0b1100000000000000, # -2.0 + 0b0111101111111111, # 65504.0 + 0b0011010101010101, # 0.333251953125 + )) + peeker = peeker_from_pixel(Texture.T_half_float, Texture.F_rgba, data) + + col = LColor() + peeker.fetch_pixel(col, 0, 0) + assert col == (65504.0, -2.0, 1.0, 0.333251953125) + + +def test_texture_peek_srgb(): + # 188 = roughly middle gray + data = array('B', [188, 188, 188]) + peeker = peeker_from_pixel(Texture.T_unsigned_byte, Texture.F_srgb, data) + + col = LColor() + peeker.fetch_pixel(col, 0, 0) + + # We allow some imprecision. + assert col.almost_equal((0.5, 0.5, 0.5, 1.0), 1 / 255.0) + + +def test_texture_peek_srgba(): + # 188 = middle gray + data = array('B', [188, 188, 188, 188]) + peeker = peeker_from_pixel(Texture.T_unsigned_byte, Texture.F_srgb_alpha, data) + + col = LColor() + peeker.fetch_pixel(col, 0, 0) + + # We allow some imprecision. + assert col.almost_equal((0.5, 0.5, 0.5, 188 / 255.0), 1 / 255.0)