mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-01 09:23:03 -04:00
Support coroutines and async/await in the task manager and loader
This commit is contained in:
parent
a7d68a8412
commit
0c0f9adab9
@ -21,31 +21,103 @@ class Loader(DirectObject):
|
|||||||
loaderIndex = 0
|
loaderIndex = 0
|
||||||
|
|
||||||
class Callback:
|
class Callback:
|
||||||
def __init__(self, numObjects, gotList, callback, extraArgs):
|
"""Returned by loadModel when used asynchronously. This class is
|
||||||
|
modelled after Future, and can be awaited."""
|
||||||
|
|
||||||
|
# This indicates that this class behaves like a Future.
|
||||||
|
_asyncio_future_blocking = False
|
||||||
|
|
||||||
|
def __init__(self, loader, numObjects, gotList, callback, extraArgs):
|
||||||
|
self._loader = loader
|
||||||
self.objects = [None] * numObjects
|
self.objects = [None] * numObjects
|
||||||
self.gotList = gotList
|
self.gotList = gotList
|
||||||
self.callback = callback
|
self.callback = callback
|
||||||
self.extraArgs = extraArgs
|
self.extraArgs = extraArgs
|
||||||
self.numRemaining = numObjects
|
|
||||||
self.cancelled = False
|
|
||||||
self.requests = set()
|
self.requests = set()
|
||||||
|
self.requestList = []
|
||||||
|
|
||||||
def gotObject(self, index, object):
|
def gotObject(self, index, object):
|
||||||
self.objects[index] = object
|
self.objects[index] = object
|
||||||
self.numRemaining -= 1
|
|
||||||
|
|
||||||
if self.numRemaining == 0:
|
if not self.requests:
|
||||||
if self.gotList:
|
self._loader = None
|
||||||
self.callback(self.objects, *self.extraArgs)
|
if self.callback:
|
||||||
else:
|
if self.gotList:
|
||||||
self.callback(*(self.objects + self.extraArgs))
|
self.callback(self.objects, *self.extraArgs)
|
||||||
|
else:
|
||||||
|
self.callback(*(self.objects + self.extraArgs))
|
||||||
|
|
||||||
|
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.requests = None
|
||||||
|
self.requestList = None
|
||||||
|
|
||||||
|
def cancelled(self):
|
||||||
|
"Returns true if the request was cancelled."
|
||||||
|
return self.requestList is None
|
||||||
|
|
||||||
|
def done(self):
|
||||||
|
"Returns true if all the requests were finished or cancelled."
|
||||||
|
return not self.requests
|
||||||
|
|
||||||
|
def result(self):
|
||||||
|
assert not self.requests, "Result is not ready."
|
||||||
|
if self.gotList:
|
||||||
|
return self.objects
|
||||||
|
else:
|
||||||
|
return self.objects[0]
|
||||||
|
|
||||||
|
def exception(self):
|
||||||
|
assert self.done() and not self.cancelled()
|
||||||
|
return None
|
||||||
|
|
||||||
|
def __await__(self):
|
||||||
|
""" Returns a generator that raises StopIteration when the loading
|
||||||
|
is complete. This allows this class to be used with 'await'."""
|
||||||
|
if self.requests:
|
||||||
|
self._asyncio_future_blocking = True
|
||||||
|
yield self
|
||||||
|
|
||||||
|
# This should be a simple return, but older versions of Python
|
||||||
|
# don't allow return statements with arguments.
|
||||||
|
result = self.result()
|
||||||
|
exc = StopIteration(result)
|
||||||
|
exc.value = result
|
||||||
|
raise exc
|
||||||
|
|
||||||
|
def __aiter__(self):
|
||||||
|
""" This allows using `async for` to iterate asynchronously over
|
||||||
|
the results of this class. It does guarantee to return the
|
||||||
|
results in order, though, even though they may not be loaded in
|
||||||
|
that order. """
|
||||||
|
requestList = self.requestList
|
||||||
|
assert requestList is not None, "Request was cancelled."
|
||||||
|
|
||||||
|
class AsyncIter:
|
||||||
|
index = 0
|
||||||
|
def __anext__(self):
|
||||||
|
if self.index < len(requestList):
|
||||||
|
i = self.index
|
||||||
|
self.index = i + 1
|
||||||
|
return requestList[i]
|
||||||
|
else:
|
||||||
|
raise StopAsyncIteration
|
||||||
|
|
||||||
|
iter = AsyncIter()
|
||||||
|
iter.objects = self.objects
|
||||||
|
return iter
|
||||||
|
|
||||||
# special methods
|
# special methods
|
||||||
def __init__(self, base):
|
def __init__(self, base):
|
||||||
self.base = base
|
self.base = base
|
||||||
self.loader = PandaLoader.getGlobalPtr()
|
self.loader = PandaLoader.getGlobalPtr()
|
||||||
|
|
||||||
self.__requests = {}
|
self._requests = {}
|
||||||
|
|
||||||
self.hook = "async_loader_%s" % (Loader.loaderIndex)
|
self.hook = "async_loader_%s" % (Loader.loaderIndex)
|
||||||
Loader.loaderIndex += 1
|
Loader.loaderIndex += 1
|
||||||
@ -180,7 +252,7 @@ class Loader(DirectObject):
|
|||||||
# requested models have been loaded, we'll invoke the
|
# requested models have been loaded, we'll invoke the
|
||||||
# callback (passing it the models on the parameter list).
|
# callback (passing it the models on the parameter list).
|
||||||
|
|
||||||
cb = Loader.Callback(len(modelList), gotList, callback, extraArgs)
|
cb = Loader.Callback(self, len(modelList), gotList, callback, extraArgs)
|
||||||
i = 0
|
i = 0
|
||||||
for modelPath in modelList:
|
for modelPath in modelList:
|
||||||
request = self.loader.makeAsyncRequest(Filename(modelPath), loaderOptions)
|
request = self.loader.makeAsyncRequest(Filename(modelPath), loaderOptions)
|
||||||
@ -189,26 +261,26 @@ class Loader(DirectObject):
|
|||||||
request.setDoneEvent(self.hook)
|
request.setDoneEvent(self.hook)
|
||||||
self.loader.loadAsync(request)
|
self.loader.loadAsync(request)
|
||||||
cb.requests.add(request)
|
cb.requests.add(request)
|
||||||
self.__requests[request] = (cb, i)
|
cb.requestList.append(request)
|
||||||
|
self._requests[request] = (cb, i)
|
||||||
i += 1
|
i += 1
|
||||||
return cb
|
return cb
|
||||||
|
|
||||||
def cancelRequest(self, cb):
|
def cancelRequest(self, cb):
|
||||||
"""Cancels an aysynchronous loading or flatten request issued
|
"""Cancels an aysynchronous loading or flatten request issued
|
||||||
earlier. The callback associated with the request will not be
|
earlier. The callback associated with the request will not be
|
||||||
called after cancelRequest() has been performed. """
|
called after cancelRequest() has been performed.
|
||||||
|
|
||||||
if not cb.cancelled:
|
This is now deprecated: call cb.cancel() instead. """
|
||||||
cb.cancelled = True
|
|
||||||
for request in cb.requests:
|
cb.cancel()
|
||||||
self.loader.remove(request)
|
|
||||||
del self.__requests[request]
|
|
||||||
cb.requests = None
|
|
||||||
|
|
||||||
def isRequestPending(self, cb):
|
def isRequestPending(self, cb):
|
||||||
""" Returns true if an asynchronous loading or flatten request
|
""" Returns true if an asynchronous loading or flatten request
|
||||||
issued earlier is still pending, or false if it has completed or
|
issued earlier is still pending, or false if it has completed or
|
||||||
been cancelled. """
|
been cancelled.
|
||||||
|
|
||||||
|
This is now deprecated: call cb.done() instead. """
|
||||||
|
|
||||||
return bool(cb.requests)
|
return bool(cb.requests)
|
||||||
|
|
||||||
@ -344,7 +416,7 @@ class Loader(DirectObject):
|
|||||||
# requested models have been saved, we'll invoke the
|
# requested models have been saved, we'll invoke the
|
||||||
# callback (passing it the models on the parameter list).
|
# callback (passing it the models on the parameter list).
|
||||||
|
|
||||||
cb = Loader.Callback(len(modelList), gotList, callback, extraArgs)
|
cb = Loader.Callback(self, len(modelList), gotList, callback, extraArgs)
|
||||||
i = 0
|
i = 0
|
||||||
for modelPath, node in modelList:
|
for modelPath, node in modelList:
|
||||||
request = self.loader.makeAsyncSaveRequest(Filename(modelPath), loaderOptions, node)
|
request = self.loader.makeAsyncSaveRequest(Filename(modelPath), loaderOptions, node)
|
||||||
@ -353,7 +425,8 @@ class Loader(DirectObject):
|
|||||||
request.setDoneEvent(self.hook)
|
request.setDoneEvent(self.hook)
|
||||||
self.loader.saveAsync(request)
|
self.loader.saveAsync(request)
|
||||||
cb.requests.add(request)
|
cb.requests.add(request)
|
||||||
self.__requests[request] = (cb, i)
|
cb.requestList.append(request)
|
||||||
|
self._requests[request] = (cb, i)
|
||||||
i += 1
|
i += 1
|
||||||
return cb
|
return cb
|
||||||
|
|
||||||
@ -880,13 +953,14 @@ class Loader(DirectObject):
|
|||||||
# requested sounds have been loaded, we'll invoke the
|
# requested sounds have been loaded, we'll invoke the
|
||||||
# callback (passing it the sounds on the parameter list).
|
# callback (passing it the sounds on the parameter list).
|
||||||
|
|
||||||
cb = Loader.Callback(len(soundList), gotList, callback, extraArgs)
|
cb = Loader.Callback(self, len(soundList), gotList, callback, extraArgs)
|
||||||
for i, soundPath in enumerate(soundList):
|
for i, soundPath in enumerate(soundList):
|
||||||
request = AudioLoadRequest(manager, soundPath, positional)
|
request = AudioLoadRequest(manager, soundPath, positional)
|
||||||
request.setDoneEvent(self.hook)
|
request.setDoneEvent(self.hook)
|
||||||
self.loader.loadAsync(request)
|
self.loader.loadAsync(request)
|
||||||
cb.requests.add(request)
|
cb.requests.add(request)
|
||||||
self.__requests[request] = (cb, i)
|
cb.requestList.append(request)
|
||||||
|
self._requests[request] = (cb, i)
|
||||||
return cb
|
return cb
|
||||||
|
|
||||||
def unloadSfx(self, sfx):
|
def unloadSfx(self, sfx):
|
||||||
@ -944,14 +1018,15 @@ class Loader(DirectObject):
|
|||||||
callback = self.__asyncFlattenDone
|
callback = self.__asyncFlattenDone
|
||||||
gotList = True
|
gotList = True
|
||||||
|
|
||||||
cb = Loader.Callback(len(modelList), gotList, callback, extraArgs)
|
cb = Loader.Callback(self, len(modelList), gotList, callback, extraArgs)
|
||||||
i = 0
|
i = 0
|
||||||
for model in modelList:
|
for model in modelList:
|
||||||
request = ModelFlattenRequest(model.node())
|
request = ModelFlattenRequest(model.node())
|
||||||
request.setDoneEvent(self.hook)
|
request.setDoneEvent(self.hook)
|
||||||
self.loader.loadAsync(request)
|
self.loader.loadAsync(request)
|
||||||
cb.requests.add(request)
|
cb.requests.add(request)
|
||||||
self.__requests[request] = (cb, i)
|
cb.requestList.append(request)
|
||||||
|
self._requests[request] = (cb, i)
|
||||||
i += 1
|
i += 1
|
||||||
return cb
|
return cb
|
||||||
|
|
||||||
@ -980,36 +1055,22 @@ class Loader(DirectObject):
|
|||||||
of loaded objects, and call the appropriate callback when it's
|
of loaded objects, and call the appropriate callback when it's
|
||||||
time."""
|
time."""
|
||||||
|
|
||||||
if request not in self.__requests:
|
if request not in self._requests:
|
||||||
return
|
return
|
||||||
|
|
||||||
cb, i = self.__requests[request]
|
cb, i = self._requests[request]
|
||||||
if cb.cancelled:
|
if cb.cancelled():
|
||||||
# Shouldn't be here.
|
# Shouldn't be here.
|
||||||
del self.__requests[request]
|
del self._requests[request]
|
||||||
return
|
return
|
||||||
|
|
||||||
cb.requests.discard(request)
|
cb.requests.discard(request)
|
||||||
if not cb.requests:
|
if not cb.requests:
|
||||||
del self.__requests[request]
|
del self._requests[request]
|
||||||
|
|
||||||
object = None
|
cb.gotObject(i, request.result() or None)
|
||||||
if hasattr(request, "getModel"):
|
|
||||||
node = request.getModel()
|
|
||||||
if node is not None:
|
|
||||||
object = NodePath(node)
|
|
||||||
|
|
||||||
elif hasattr(request, "getSound"):
|
|
||||||
object = request.getSound()
|
|
||||||
|
|
||||||
elif hasattr(request, "getSuccess"):
|
|
||||||
object = request.getSuccess()
|
|
||||||
|
|
||||||
cb.gotObject(i, object)
|
|
||||||
|
|
||||||
load_model = loadModel
|
load_model = loadModel
|
||||||
cancel_request = cancelRequest
|
|
||||||
is_request_pending = isRequestPending
|
|
||||||
unload_model = unloadModel
|
unload_model = unloadModel
|
||||||
save_model = saveModel
|
save_model = saveModel
|
||||||
load_font = loadFont
|
load_font = loadFont
|
||||||
|
@ -333,6 +333,7 @@ class TaskManager:
|
|||||||
funcOrTask - either an existing Task object (not already added
|
funcOrTask - either an existing Task object (not already added
|
||||||
to the task manager), or a callable function object. If this
|
to the task manager), or a callable function object. If this
|
||||||
is a function, a new Task object will be created and returned.
|
is a function, a new Task object will be created and returned.
|
||||||
|
You may also pass in a coroutine object.
|
||||||
|
|
||||||
name - the name to assign to the Task. Required, unless you
|
name - the name to assign to the Task. Required, unless you
|
||||||
are passing in a Task object that already has a name.
|
are passing in a Task object that already has a name.
|
||||||
@ -385,6 +386,15 @@ class TaskManager:
|
|||||||
task = funcOrTask
|
task = funcOrTask
|
||||||
elif hasattr(funcOrTask, '__call__'):
|
elif hasattr(funcOrTask, '__call__'):
|
||||||
task = PythonTask(funcOrTask)
|
task = PythonTask(funcOrTask)
|
||||||
|
if name is None:
|
||||||
|
name = getattr(funcOrTask, '__qualname__', None) or \
|
||||||
|
getattr(funcOrTask, '__name__', None)
|
||||||
|
elif hasattr(funcOrTask, 'cr_await') or type(funcOrTask) == types.GeneratorType:
|
||||||
|
# It's a coroutine, or something emulating one.
|
||||||
|
task = PythonTask(funcOrTask)
|
||||||
|
if name is None:
|
||||||
|
name = getattr(funcOrTask, '__qualname__', None) or \
|
||||||
|
getattr(funcOrTask, '__name__', None)
|
||||||
else:
|
else:
|
||||||
self.notify.error(
|
self.notify.error(
|
||||||
'add: Tried to add a task that was not a Task or a func')
|
'add: Tried to add a task that was not a Task or a func')
|
||||||
|
@ -846,7 +846,7 @@ setup_properties(const InterrogateFunction &ifunc, InterfaceMaker *interface_mak
|
|||||||
}
|
}
|
||||||
|
|
||||||
} else if (fname == "__iter__") {
|
} else if (fname == "__iter__") {
|
||||||
if (_has_this && _parameters.size() == 1 &&
|
if ((int)_parameters.size() == first_param &&
|
||||||
TypeManager::is_pointer(_return_type->get_new_type())) {
|
TypeManager::is_pointer(_return_type->get_new_type())) {
|
||||||
// It receives no parameters, and returns a pointer.
|
// It receives no parameters, and returns a pointer.
|
||||||
_flags |= F_iter;
|
_flags |= F_iter;
|
||||||
|
@ -499,6 +499,24 @@ get_slotted_function_def(Object *obj, Function *func, FunctionRemap *remap,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (method_name == "__await__") {
|
||||||
|
def._answer_location = "am_await";
|
||||||
|
def._wrapper_type = WT_no_params;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method_name == "__aiter__") {
|
||||||
|
def._answer_location = "am_aiter";
|
||||||
|
def._wrapper_type = WT_no_params;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method_name == "__anext__") {
|
||||||
|
def._answer_location = "am_anext";
|
||||||
|
def._wrapper_type = WT_no_params;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (method_name == "operator ()") {
|
if (method_name == "operator ()") {
|
||||||
def._answer_location = "tp_call";
|
def._answer_location = "tp_call";
|
||||||
def._wrapper_type = WT_none;
|
def._wrapper_type = WT_none;
|
||||||
@ -2798,6 +2816,20 @@ write_module_class(ostream &out, Object *obj) {
|
|||||||
out << "};\n\n";
|
out << "};\n\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool have_async = false;
|
||||||
|
if (has_parent_class || slots.count("am_await") != 0 ||
|
||||||
|
slots.count("am_aiter") != 0 ||
|
||||||
|
slots.count("am_anext") != 0) {
|
||||||
|
out << "#if PY_VERSION_HEX >= 0x03050000\n";
|
||||||
|
out << "static PyAsyncMethods Dtool_AsyncMethods_" << ClassName << " = {\n";
|
||||||
|
write_function_slot(out, 2, slots, "am_await");
|
||||||
|
write_function_slot(out, 2, slots, "am_aiter");
|
||||||
|
write_function_slot(out, 2, slots, "am_anext");
|
||||||
|
out << "};\n";
|
||||||
|
out << "#endif\n\n";
|
||||||
|
have_async = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Output the actual PyTypeObject definition.
|
// Output the actual PyTypeObject definition.
|
||||||
out << "struct Dtool_PyTypedObject Dtool_" << ClassName << " = {\n";
|
out << "struct Dtool_PyTypedObject Dtool_" << ClassName << " = {\n";
|
||||||
out << " {\n";
|
out << " {\n";
|
||||||
@ -2819,7 +2851,13 @@ write_module_class(ostream &out, Object *obj) {
|
|||||||
write_function_slot(out, 4, slots, "tp_setattr");
|
write_function_slot(out, 4, slots, "tp_setattr");
|
||||||
|
|
||||||
// cmpfunc tp_compare; (reserved in Python 3)
|
// cmpfunc tp_compare; (reserved in Python 3)
|
||||||
out << "#if PY_MAJOR_VERSION >= 3\n";
|
out << "#if PY_VERSION_HEX >= 0x03050000\n";
|
||||||
|
if (have_async) {
|
||||||
|
out << " &Dtool_AsyncMethods_" << ClassName << ",\n";
|
||||||
|
} else {
|
||||||
|
out << " 0, // tp_as_async\n";
|
||||||
|
}
|
||||||
|
out << "#elif PY_MAJOR_VERSION >= 3\n";
|
||||||
out << " 0, // tp_reserved\n";
|
out << " 0, // tp_reserved\n";
|
||||||
out << "#else\n";
|
out << "#else\n";
|
||||||
if (has_hash_compare) {
|
if (has_hash_compare) {
|
||||||
|
@ -622,6 +622,10 @@ PyObject *Dtool_PyModuleInitHelper(LibraryDef *defs[], const char *modulename) {
|
|||||||
return Dtool_Raise_TypeError("PyType_Ready(Dtool_SeqMapWrapper)");
|
return Dtool_Raise_TypeError("PyType_Ready(Dtool_SeqMapWrapper)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (PyType_Ready(&Dtool_GeneratorWrapper_Type) < 0) {
|
||||||
|
return Dtool_Raise_TypeError("PyType_Ready(Dtool_GeneratorWrapper)");
|
||||||
|
}
|
||||||
|
|
||||||
if (PyType_Ready(&Dtool_StaticProperty_Type) < 0) {
|
if (PyType_Ready(&Dtool_StaticProperty_Type) < 0) {
|
||||||
return Dtool_Raise_TypeError("PyType_Ready(Dtool_StaticProperty_Type)");
|
return Dtool_Raise_TypeError("PyType_Ready(Dtool_StaticProperty_Type)");
|
||||||
}
|
}
|
||||||
@ -1111,6 +1115,13 @@ static int Dtool_SeqMapWrapper_setitem(PyObject *self, PyObject *key, PyObject *
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PyObject *Dtool_GeneratorWrapper_iternext(PyObject *self) {
|
||||||
|
Dtool_GeneratorWrapper *wrap = (Dtool_GeneratorWrapper *)self;
|
||||||
|
nassertr(wrap, nullptr);
|
||||||
|
nassertr(wrap->_iternext_func, nullptr);
|
||||||
|
return wrap->_iternext_func(wrap->_base._self);
|
||||||
|
}
|
||||||
|
|
||||||
static PySequenceMethods Dtool_SequenceWrapper_SequenceMethods = {
|
static PySequenceMethods Dtool_SequenceWrapper_SequenceMethods = {
|
||||||
Dtool_SequenceWrapper_length,
|
Dtool_SequenceWrapper_length,
|
||||||
0, // sq_concat
|
0, // sq_concat
|
||||||
@ -1454,4 +1465,66 @@ PyTypeObject Dtool_SeqMapWrapper_Type = {
|
|||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This variant defines only a generator interface.
|
||||||
|
*/
|
||||||
|
PyTypeObject Dtool_GeneratorWrapper_Type = {
|
||||||
|
PyVarObject_HEAD_INIT(NULL, 0)
|
||||||
|
"generator wrapper",
|
||||||
|
sizeof(Dtool_GeneratorWrapper),
|
||||||
|
0, // tp_itemsize
|
||||||
|
Dtool_WrapperBase_dealloc,
|
||||||
|
0, // tp_print
|
||||||
|
0, // tp_getattr
|
||||||
|
0, // tp_setattr
|
||||||
|
#if PY_MAJOR_VERSION >= 3
|
||||||
|
0, // tp_reserved
|
||||||
|
#else
|
||||||
|
0, // tp_compare
|
||||||
|
#endif
|
||||||
|
0, // tp_repr
|
||||||
|
0, // tp_as_number
|
||||||
|
0, // tp_as_sequence
|
||||||
|
0, // tp_as_mapping
|
||||||
|
0, // tp_hash
|
||||||
|
0, // tp_call
|
||||||
|
0, // tp_str
|
||||||
|
PyObject_GenericGetAttr,
|
||||||
|
PyObject_GenericSetAttr,
|
||||||
|
0, // tp_as_buffer
|
||||||
|
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES,
|
||||||
|
0, // tp_doc
|
||||||
|
0, // tp_traverse
|
||||||
|
0, // tp_clear
|
||||||
|
0, // tp_richcompare
|
||||||
|
0, // tp_weaklistoffset
|
||||||
|
PyObject_SelfIter,
|
||||||
|
Dtool_GeneratorWrapper_iternext,
|
||||||
|
0, // tp_methods
|
||||||
|
0, // tp_members
|
||||||
|
0, // tp_getset
|
||||||
|
0, // tp_base
|
||||||
|
0, // tp_dict
|
||||||
|
0, // tp_descr_get
|
||||||
|
0, // tp_descr_set
|
||||||
|
0, // tp_dictoffset
|
||||||
|
0, // tp_init
|
||||||
|
PyType_GenericAlloc,
|
||||||
|
0, // tp_new
|
||||||
|
PyObject_Del,
|
||||||
|
0, // tp_is_gc
|
||||||
|
0, // tp_bases
|
||||||
|
0, // tp_mro
|
||||||
|
0, // tp_cache
|
||||||
|
0, // tp_subclasses
|
||||||
|
0, // tp_weaklist
|
||||||
|
0, // tp_del
|
||||||
|
#if PY_VERSION_HEX >= 0x02060000
|
||||||
|
0, // tp_version_tag
|
||||||
|
#endif
|
||||||
|
#if PY_VERSION_HEX >= 0x03040000
|
||||||
|
0, // tp_finalize
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
#endif // HAVE_PYTHON
|
#endif // HAVE_PYTHON
|
||||||
|
@ -495,9 +495,15 @@ struct Dtool_SeqMapWrapper {
|
|||||||
objobjargproc _map_setitem_func;
|
objobjargproc _map_setitem_func;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct Dtool_GeneratorWrapper {
|
||||||
|
Dtool_WrapperBase _base;
|
||||||
|
iternextfunc _iternext_func;
|
||||||
|
};
|
||||||
|
|
||||||
EXPCL_INTERROGATEDB extern PyTypeObject Dtool_SequenceWrapper_Type;
|
EXPCL_INTERROGATEDB extern PyTypeObject Dtool_SequenceWrapper_Type;
|
||||||
EXPCL_INTERROGATEDB extern PyTypeObject Dtool_MappingWrapper_Type;
|
EXPCL_INTERROGATEDB extern PyTypeObject Dtool_MappingWrapper_Type;
|
||||||
EXPCL_INTERROGATEDB extern PyTypeObject Dtool_SeqMapWrapper_Type;
|
EXPCL_INTERROGATEDB extern PyTypeObject Dtool_SeqMapWrapper_Type;
|
||||||
|
EXPCL_INTERROGATEDB extern PyTypeObject Dtool_GeneratorWrapper_Type;
|
||||||
EXPCL_INTERROGATEDB extern PyTypeObject Dtool_StaticProperty_Type;
|
EXPCL_INTERROGATEDB extern PyTypeObject Dtool_StaticProperty_Type;
|
||||||
|
|
||||||
EXPCL_INTERROGATEDB PyObject *Dtool_NewStaticProperty(PyTypeObject *obj, const PyGetSetDef *getset);
|
EXPCL_INTERROGATEDB PyObject *Dtool_NewStaticProperty(PyTypeObject *obj, const PyGetSetDef *getset);
|
||||||
|
@ -115,6 +115,7 @@ extern "C" {
|
|||||||
EXPCL_PYSTUB int PyObject_IsTrue(...);
|
EXPCL_PYSTUB int PyObject_IsTrue(...);
|
||||||
EXPCL_PYSTUB int PyObject_Repr(...);
|
EXPCL_PYSTUB int PyObject_Repr(...);
|
||||||
EXPCL_PYSTUB int PyObject_RichCompareBool(...);
|
EXPCL_PYSTUB int PyObject_RichCompareBool(...);
|
||||||
|
EXPCL_PYSTUB int PyObject_SelfIter(...);
|
||||||
EXPCL_PYSTUB int PyObject_SetAttrString(...);
|
EXPCL_PYSTUB int PyObject_SetAttrString(...);
|
||||||
EXPCL_PYSTUB int PyObject_Str(...);
|
EXPCL_PYSTUB int PyObject_Str(...);
|
||||||
EXPCL_PYSTUB int PyObject_Type(...);
|
EXPCL_PYSTUB int PyObject_Type(...);
|
||||||
@ -336,6 +337,7 @@ int PyObject_IsInstance(...) { return 0; }
|
|||||||
int PyObject_IsTrue(...) { return 0; }
|
int PyObject_IsTrue(...) { return 0; }
|
||||||
int PyObject_Repr(...) { return 0; }
|
int PyObject_Repr(...) { return 0; }
|
||||||
int PyObject_RichCompareBool(...) { return 0; }
|
int PyObject_RichCompareBool(...) { return 0; }
|
||||||
|
int PyObject_SelfIter(...) { return 0; }
|
||||||
int PyObject_SetAttrString(...) { return 0; }
|
int PyObject_SetAttrString(...) { return 0; }
|
||||||
int PyObject_Str(...) { return 0; }
|
int PyObject_Str(...) { return 0; }
|
||||||
int PyObject_Type(...) { return 0; }
|
int PyObject_Type(...) { return 0; }
|
||||||
|
@ -3616,6 +3616,7 @@ if (not RUNTIME):
|
|||||||
TargetAdd('p3event_composite2.obj', opts=OPTS, input='p3event_composite2.cxx')
|
TargetAdd('p3event_composite2.obj', opts=OPTS, input='p3event_composite2.cxx')
|
||||||
|
|
||||||
OPTS=['DIR:panda/src/event', 'PYTHON']
|
OPTS=['DIR:panda/src/event', 'PYTHON']
|
||||||
|
TargetAdd('p3event_asyncTask_ext.obj', opts=OPTS, input='asyncTask_ext.cxx')
|
||||||
TargetAdd('p3event_pythonTask.obj', opts=OPTS, input='pythonTask.cxx')
|
TargetAdd('p3event_pythonTask.obj', opts=OPTS, input='pythonTask.cxx')
|
||||||
IGATEFILES=GetDirectoryContents('panda/src/event', ["*.h", "*_composite*.cxx"])
|
IGATEFILES=GetDirectoryContents('panda/src/event', ["*.h", "*_composite*.cxx"])
|
||||||
TargetAdd('libp3event.in', opts=OPTS, input=IGATEFILES)
|
TargetAdd('libp3event.in', opts=OPTS, input=IGATEFILES)
|
||||||
@ -4209,6 +4210,7 @@ if (not RUNTIME):
|
|||||||
TargetAdd('core.pyd', input='p3pipeline_pythonThread.obj')
|
TargetAdd('core.pyd', input='p3pipeline_pythonThread.obj')
|
||||||
TargetAdd('core.pyd', input='p3putil_ext_composite.obj')
|
TargetAdd('core.pyd', input='p3putil_ext_composite.obj')
|
||||||
TargetAdd('core.pyd', input='p3pnmimage_pfmFile_ext.obj')
|
TargetAdd('core.pyd', input='p3pnmimage_pfmFile_ext.obj')
|
||||||
|
TargetAdd('core.pyd', input='p3event_asyncTask_ext.obj')
|
||||||
TargetAdd('core.pyd', input='p3event_pythonTask.obj')
|
TargetAdd('core.pyd', input='p3event_pythonTask.obj')
|
||||||
TargetAdd('core.pyd', input='p3gobj_ext_composite.obj')
|
TargetAdd('core.pyd', input='p3gobj_ext_composite.obj')
|
||||||
TargetAdd('core.pyd', input='p3pgraph_ext_composite.obj')
|
TargetAdd('core.pyd', input='p3pgraph_ext_composite.obj')
|
||||||
|
@ -62,11 +62,24 @@ is_ready() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the sound that was loaded asynchronously, if any, or NULL if there
|
* Returns the sound that was loaded asynchronously, if any, or nullptr if
|
||||||
* was an error. It is an error to call this unless is_ready() returns true.
|
* there was an error. It is an error to call this unless is_ready() returns
|
||||||
|
* true.
|
||||||
|
* @deprecated Use result() instead.
|
||||||
*/
|
*/
|
||||||
INLINE AudioSound *AudioLoadRequest::
|
INLINE AudioSound *AudioLoadRequest::
|
||||||
get_sound() const {
|
get_sound() const {
|
||||||
nassertr(_is_ready, NULL);
|
nassertr(_is_ready, NULL);
|
||||||
return _sound;
|
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;
|
||||||
|
}
|
||||||
|
@ -42,6 +42,8 @@ PUBLISHED:
|
|||||||
INLINE bool is_ready() const;
|
INLINE bool is_ready() const;
|
||||||
INLINE AudioSound *get_sound() const;
|
INLINE AudioSound *get_sound() const;
|
||||||
|
|
||||||
|
INLINE AudioSound *result() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual DoneStatus do_task();
|
virtual DoneStatus do_task();
|
||||||
|
|
||||||
|
@ -33,6 +33,7 @@ is_alive() const {
|
|||||||
case S_servicing:
|
case S_servicing:
|
||||||
case S_sleeping:
|
case S_sleeping:
|
||||||
case S_active_nested:
|
case S_active_nested:
|
||||||
|
case S_awaiting:
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case S_inactive:
|
case S_inactive:
|
||||||
|
@ -45,6 +45,7 @@ PUBLISHED:
|
|||||||
DS_exit, // stop the enclosing sequence
|
DS_exit, // stop the enclosing sequence
|
||||||
DS_pause, // pause, then exit (useful within a sequence)
|
DS_pause, // pause, then exit (useful within a sequence)
|
||||||
DS_interrupt, // interrupt the task manager, but run task again
|
DS_interrupt, // interrupt the task manager, but run task again
|
||||||
|
DS_await, // await a different task's completion
|
||||||
};
|
};
|
||||||
|
|
||||||
enum State {
|
enum State {
|
||||||
@ -54,6 +55,7 @@ PUBLISHED:
|
|||||||
S_servicing_removed, // Still servicing, but wants removal from manager.
|
S_servicing_removed, // Still servicing, but wants removal from manager.
|
||||||
S_sleeping,
|
S_sleeping,
|
||||||
S_active_nested, // active within a sequence.
|
S_active_nested, // active within a sequence.
|
||||||
|
S_awaiting, // Waiting for a dependent task to complete
|
||||||
};
|
};
|
||||||
|
|
||||||
INLINE State get_state() const;
|
INLINE State get_state() const;
|
||||||
@ -98,6 +100,9 @@ PUBLISHED:
|
|||||||
|
|
||||||
virtual void output(ostream &out) const;
|
virtual void output(ostream &out) const;
|
||||||
|
|
||||||
|
EXTENSION(static PyObject *__await__(PyObject *self));
|
||||||
|
EXTENSION(static PyObject *__iter__(PyObject *self));
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void jump_to_task_chain(AsyncTaskManager *manager);
|
void jump_to_task_chain(AsyncTaskManager *manager);
|
||||||
DoneStatus unlock_and_do_task();
|
DoneStatus unlock_and_do_task();
|
||||||
@ -130,11 +135,16 @@ protected:
|
|||||||
double _total_dt;
|
double _total_dt;
|
||||||
int _num_frames;
|
int _num_frames;
|
||||||
|
|
||||||
|
// Tasks waiting for this one to complete.
|
||||||
|
pvector<PT(AsyncTask)> _waiting_tasks;
|
||||||
|
|
||||||
static AtomicAdjust::Integer _next_task_id;
|
static AtomicAdjust::Integer _next_task_id;
|
||||||
|
|
||||||
static PStatCollector _show_code_pcollector;
|
static PStatCollector _show_code_pcollector;
|
||||||
PStatCollector _task_pcollector;
|
PStatCollector _task_pcollector;
|
||||||
|
|
||||||
|
friend class PythonTask;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static TypeHandle get_class_type() {
|
static TypeHandle get_class_type() {
|
||||||
return _type_handle;
|
return _type_handle;
|
||||||
|
@ -44,6 +44,7 @@ AsyncTaskChain(AsyncTaskManager *manager, const string &name) :
|
|||||||
_frame_sync(false),
|
_frame_sync(false),
|
||||||
_num_busy_threads(0),
|
_num_busy_threads(0),
|
||||||
_num_tasks(0),
|
_num_tasks(0),
|
||||||
|
_num_awaiting_tasks(0),
|
||||||
_state(S_initial),
|
_state(S_initial),
|
||||||
_current_sort(-INT_MAX),
|
_current_sort(-INT_MAX),
|
||||||
_pickup_mode(false),
|
_pickup_mode(false),
|
||||||
@ -726,6 +727,13 @@ service_one_task(AsyncTaskChain::AsyncTaskChainThread *thread) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case AsyncTask::DS_await:
|
||||||
|
// The task wants to wait for another one to finish.
|
||||||
|
task->_state = AsyncTask::S_awaiting;
|
||||||
|
_cvar.notify_all();
|
||||||
|
++_num_awaiting_tasks;
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// The task has finished.
|
// The task has finished.
|
||||||
cleanup_task(task, true, true);
|
cleanup_task(task, true, true);
|
||||||
@ -775,6 +783,20 @@ cleanup_task(AsyncTask *task, bool upon_death, bool clean_exit) {
|
|||||||
|
|
||||||
_manager->remove_task_by_name(task);
|
_manager->remove_task_by_name(task);
|
||||||
|
|
||||||
|
// Activate the tasks that were waiting for this one to finish.
|
||||||
|
if (upon_death) {
|
||||||
|
pvector<PT(AsyncTask)>::iterator it;
|
||||||
|
for (it = task->_waiting_tasks.begin(); it != task->_waiting_tasks.end(); ++it) {
|
||||||
|
AsyncTask *task = *it;
|
||||||
|
// Note that this task may not be on the same task chain.
|
||||||
|
nassertd(task->_manager == _manager) continue;
|
||||||
|
task->_state = AsyncTask::S_active;
|
||||||
|
task->_chain->_active.push_back(task);
|
||||||
|
--task->_chain->_num_awaiting_tasks;
|
||||||
|
}
|
||||||
|
task->_waiting_tasks.clear();
|
||||||
|
}
|
||||||
|
|
||||||
if (upon_death) {
|
if (upon_death) {
|
||||||
_manager->_lock.release();
|
_manager->_lock.release();
|
||||||
task->upon_death(_manager, clean_exit);
|
task->upon_death(_manager, clean_exit);
|
||||||
@ -899,7 +921,7 @@ finish_sort_group() {
|
|||||||
filter_timeslice_priority();
|
filter_timeslice_priority();
|
||||||
}
|
}
|
||||||
|
|
||||||
nassertr((size_t)_num_tasks == _active.size() + _this_active.size() + _next_active.size() + _sleeping.size(), true);
|
nassertr((size_t)_num_tasks == _active.size() + _this_active.size() + _next_active.size() + _sleeping.size() + (size_t)_num_awaiting_tasks, true);
|
||||||
make_heap(_active.begin(), _active.end(), AsyncTaskSortPriority());
|
make_heap(_active.begin(), _active.end(), AsyncTaskSortPriority());
|
||||||
|
|
||||||
_current_sort = -INT_MAX;
|
_current_sort = -INT_MAX;
|
||||||
|
@ -172,6 +172,7 @@ protected:
|
|||||||
bool _frame_sync;
|
bool _frame_sync;
|
||||||
int _num_busy_threads;
|
int _num_busy_threads;
|
||||||
int _num_tasks;
|
int _num_tasks;
|
||||||
|
int _num_awaiting_tasks;
|
||||||
TaskHeap _active;
|
TaskHeap _active;
|
||||||
TaskHeap _this_active;
|
TaskHeap _this_active;
|
||||||
TaskHeap _next_active;
|
TaskHeap _next_active;
|
||||||
|
@ -155,6 +155,7 @@ private:
|
|||||||
friend class AsyncTaskChain::AsyncTaskChainThread;
|
friend class AsyncTaskChain::AsyncTaskChainThread;
|
||||||
friend class AsyncTask;
|
friend class AsyncTask;
|
||||||
friend class AsyncTaskSequence;
|
friend class AsyncTaskSequence;
|
||||||
|
friend class PythonTask;
|
||||||
};
|
};
|
||||||
|
|
||||||
INLINE ostream &operator << (ostream &out, const AsyncTaskManager &manager) {
|
INLINE ostream &operator << (ostream &out, const AsyncTaskManager &manager) {
|
||||||
|
75
panda/src/event/asyncTask_ext.cxx
Executable file
75
panda/src/event/asyncTask_ext.cxx
Executable file
@ -0,0 +1,75 @@
|
|||||||
|
/**
|
||||||
|
* PANDA 3D SOFTWARE
|
||||||
|
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||||
|
*
|
||||||
|
* All use of this software is subject to the terms of the revised BSD
|
||||||
|
* license. You should have received a copy of this license along
|
||||||
|
* with this source code in a file named "LICENSE."
|
||||||
|
*
|
||||||
|
* @file asyncTask_ext.h
|
||||||
|
* @author rdb
|
||||||
|
* @date 2017-10-29
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "asyncTask_ext.h"
|
||||||
|
#include "nodePath.h"
|
||||||
|
|
||||||
|
#ifdef HAVE_PYTHON
|
||||||
|
|
||||||
|
#ifndef CPPPARSER
|
||||||
|
extern struct Dtool_PyTypedObject Dtool_AsyncTask;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Yields continuously until the task has finished.
|
||||||
|
*/
|
||||||
|
static PyObject *gen_next(PyObject *self) {
|
||||||
|
const AsyncTask *request = nullptr;
|
||||||
|
if (!Dtool_Call_ExtractThisPointer(self, Dtool_AsyncTask, (void **)&request)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request->is_alive()) {
|
||||||
|
// Continue awaiting the result.
|
||||||
|
Py_INCREF(self);
|
||||||
|
return self;
|
||||||
|
} else {
|
||||||
|
// It's done. Do we have a method like result(), eg. in the case of a
|
||||||
|
// ModelLoadRequest? In that case we pass that value into the exception.
|
||||||
|
PyObject *method = PyObject_GetAttrString(self, "result");
|
||||||
|
PyObject *result = nullptr;
|
||||||
|
if (method != nullptr) {
|
||||||
|
if (PyCallable_Check(method)) {
|
||||||
|
result = _PyObject_CallNoArg(method);
|
||||||
|
Py_DECREF(method);
|
||||||
|
if (result == nullptr) {
|
||||||
|
// An exception happened. Pass it on.
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Py_DECREF(method);
|
||||||
|
}
|
||||||
|
Py_INCREF(PyExc_StopIteration);
|
||||||
|
PyErr_Restore(PyExc_StopIteration, result, nullptr);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a generator that continuously yields an awaitable until the task
|
||||||
|
* has finished. This allows syntax like `model = await loader.load...` to be
|
||||||
|
* used in a Python coroutine.
|
||||||
|
*/
|
||||||
|
PyObject *Extension<AsyncTask>::
|
||||||
|
__await__(PyObject *self) {
|
||||||
|
Dtool_GeneratorWrapper *gen;
|
||||||
|
gen = (Dtool_GeneratorWrapper *)PyType_GenericAlloc(&Dtool_GeneratorWrapper_Type, 0);
|
||||||
|
if (gen != nullptr) {
|
||||||
|
Py_INCREF(self);
|
||||||
|
gen->_base._self = self;
|
||||||
|
gen->_iternext_func = &gen_next;
|
||||||
|
}
|
||||||
|
return (PyObject *)gen;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
35
panda/src/event/asyncTask_ext.h
Executable file
35
panda/src/event/asyncTask_ext.h
Executable file
@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ASYNCTASK_EXT_H
|
||||||
|
#define ASYNCTASK_EXT_H
|
||||||
|
|
||||||
|
#include "extension.h"
|
||||||
|
#include "py_panda.h"
|
||||||
|
#include "modelLoadRequest.h"
|
||||||
|
|
||||||
|
#ifdef HAVE_PYTHON
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension class for AsyncTask
|
||||||
|
*/
|
||||||
|
template<>
|
||||||
|
class Extension<AsyncTask> : public ExtensionBase<AsyncTask> {
|
||||||
|
public:
|
||||||
|
static PyObject *__await__(PyObject *self);
|
||||||
|
static PyObject *__iter__(PyObject *self) { return __await__(self); }
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // HAVE_PYTHON
|
||||||
|
|
||||||
|
#endif // ASYNCTASK_EXT_H
|
@ -10,3 +10,52 @@
|
|||||||
* @author drose
|
* @author drose
|
||||||
* @date 2008-09-16
|
* @date 2008-09-16
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the function that is called when the task runs.
|
||||||
|
*/
|
||||||
|
INLINE PyObject *PythonTask::
|
||||||
|
get_function() {
|
||||||
|
Py_INCREF(_function);
|
||||||
|
return _function;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the function that is called when the task finishes.
|
||||||
|
*/
|
||||||
|
INLINE PyObject *PythonTask::
|
||||||
|
get_upon_death() {
|
||||||
|
Py_INCREF(_upon_death);
|
||||||
|
return _upon_death;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the "owner" object. See set_owner().
|
||||||
|
*/
|
||||||
|
INLINE PyObject *PythonTask::
|
||||||
|
get_owner() const {
|
||||||
|
Py_INCREF(_owner);
|
||||||
|
return _owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the "result" of this task. This is the value returned from an "await"
|
||||||
|
* expression on this task.
|
||||||
|
* This can only be called while the task is still alive.
|
||||||
|
*/
|
||||||
|
INLINE void PythonTask::
|
||||||
|
set_result(PyObject *result) {
|
||||||
|
nassertv(is_alive());
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
@ -19,28 +19,52 @@
|
|||||||
#include "py_panda.h"
|
#include "py_panda.h"
|
||||||
|
|
||||||
#include "pythonThread.h"
|
#include "pythonThread.h"
|
||||||
|
#include "asyncTaskManager.h"
|
||||||
|
|
||||||
TypeHandle PythonTask::_type_handle;
|
TypeHandle PythonTask::_type_handle;
|
||||||
|
|
||||||
#ifndef CPPPARSER
|
#ifndef CPPPARSER
|
||||||
extern struct Dtool_PyTypedObject Dtool_TypedReferenceCount;
|
extern struct Dtool_PyTypedObject Dtool_TypedReferenceCount;
|
||||||
|
extern struct Dtool_PyTypedObject Dtool_AsyncTask;
|
||||||
|
extern struct Dtool_PyTypedObject Dtool_PythonTask;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
PythonTask::
|
PythonTask::
|
||||||
PythonTask(PyObject *function, const string &name) :
|
PythonTask(PyObject *func_or_coro, const string &name) :
|
||||||
AsyncTask(name)
|
AsyncTask(name),
|
||||||
{
|
_function(nullptr),
|
||||||
_function = NULL;
|
_args(nullptr),
|
||||||
_args = NULL;
|
_upon_death(nullptr),
|
||||||
_upon_death = NULL;
|
_owner(nullptr),
|
||||||
_owner = NULL;
|
_registered_to_owner(false),
|
||||||
_registered_to_owner = false;
|
_exception(nullptr),
|
||||||
_generator = NULL;
|
_exc_value(nullptr),
|
||||||
|
_exc_traceback(nullptr),
|
||||||
|
_generator(nullptr),
|
||||||
|
_future_done(nullptr),
|
||||||
|
_retrieved_exception(false) {
|
||||||
|
|
||||||
|
nassertv(func_or_coro != nullptr);
|
||||||
|
if (func_or_coro == Py_None || PyCallable_Check(func_or_coro)) {
|
||||||
|
_function = func_or_coro;
|
||||||
|
Py_INCREF(_function);
|
||||||
|
#if PY_VERSION_HEX >= 0x03050000
|
||||||
|
} else if (PyCoro_CheckExact(func_or_coro)) {
|
||||||
|
// We also allow passing in a coroutine, because why not.
|
||||||
|
_generator = func_or_coro;
|
||||||
|
Py_INCREF(_generator);
|
||||||
|
#endif
|
||||||
|
} else if (PyGen_CheckExact(func_or_coro)) {
|
||||||
|
// Something emulating a coroutine.
|
||||||
|
_generator = func_or_coro;
|
||||||
|
Py_INCREF(_generator);
|
||||||
|
} else {
|
||||||
|
nassert_raise("Invalid function passed to PythonTask");
|
||||||
|
}
|
||||||
|
|
||||||
set_function(function);
|
|
||||||
set_args(Py_None, true);
|
set_args(Py_None, true);
|
||||||
set_upon_death(Py_None);
|
set_upon_death(Py_None);
|
||||||
set_owner(Py_None);
|
set_owner(Py_None);
|
||||||
@ -60,9 +84,24 @@ PythonTask(PyObject *function, const string &name) :
|
|||||||
*/
|
*/
|
||||||
PythonTask::
|
PythonTask::
|
||||||
~PythonTask() {
|
~PythonTask() {
|
||||||
Py_DECREF(_function);
|
#ifndef NDEBUG
|
||||||
|
// If the coroutine threw an exception, and there was no opportunity to
|
||||||
|
// handle it, let the user know.
|
||||||
|
if (_exception != nullptr && !_retrieved_exception) {
|
||||||
|
task_cat.error()
|
||||||
|
<< *this << " exception was never retrieved:\n";
|
||||||
|
PyErr_Restore(_exception, _exc_value, _exc_traceback);
|
||||||
|
PyErr_Print();
|
||||||
|
PyErr_Restore(nullptr, nullptr, nullptr);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Py_XDECREF(_function);
|
||||||
Py_DECREF(_args);
|
Py_DECREF(_args);
|
||||||
Py_DECREF(__dict__);
|
Py_DECREF(__dict__);
|
||||||
|
Py_XDECREF(_exception);
|
||||||
|
Py_XDECREF(_exc_value);
|
||||||
|
Py_XDECREF(_exc_traceback);
|
||||||
Py_XDECREF(_generator);
|
Py_XDECREF(_generator);
|
||||||
Py_XDECREF(_owner);
|
Py_XDECREF(_owner);
|
||||||
Py_XDECREF(_upon_death);
|
Py_XDECREF(_upon_death);
|
||||||
@ -83,15 +122,6 @@ set_function(PyObject *function) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the function that is called when the task runs.
|
|
||||||
*/
|
|
||||||
PyObject *PythonTask::
|
|
||||||
get_function() {
|
|
||||||
Py_INCREF(_function);
|
|
||||||
return _function;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replaces the argument list that is passed to the task function. The
|
* Replaces the argument list that is passed to the task function. The
|
||||||
* parameter should be a tuple or list of arguments, or None to indicate the
|
* parameter should be a tuple or list of arguments, or None to indicate the
|
||||||
@ -166,15 +196,6 @@ set_upon_death(PyObject *upon_death) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the function that is called when the task finishes.
|
|
||||||
*/
|
|
||||||
PyObject *PythonTask::
|
|
||||||
get_upon_death() {
|
|
||||||
Py_INCREF(_upon_death);
|
|
||||||
return _upon_death;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specifies a Python object that serves as the "owner" for the task. This
|
* Specifies a Python object that serves as the "owner" for the task. This
|
||||||
* owner object must have two methods: _addTask() and _clearTask(), which will
|
* owner object must have two methods: _addTask() and _clearTask(), which will
|
||||||
@ -212,12 +233,87 @@ set_owner(PyObject *owner) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the "owner" object. See set_owner().
|
* Returns the result of this task's execution, as set by set_result() within
|
||||||
|
* the task or returned from a coroutine added to the task manager. If an
|
||||||
|
* exception occurred within this task, it is raised instead.
|
||||||
*/
|
*/
|
||||||
PyObject *PythonTask::
|
PyObject *PythonTask::
|
||||||
get_owner() {
|
result() const {
|
||||||
Py_INCREF(_owner);
|
nassertr(!is_alive(), nullptr);
|
||||||
return _owner;
|
|
||||||
|
if (_exception == nullptr) {
|
||||||
|
// The result of the call is stored in _exc_value.
|
||||||
|
Py_XINCREF(_exc_value);
|
||||||
|
return _exc_value;
|
||||||
|
} else {
|
||||||
|
_retrieved_exception = true;
|
||||||
|
Py_INCREF(_exception);
|
||||||
|
Py_XINCREF(_exc_value);
|
||||||
|
Py_XINCREF(_exc_traceback);
|
||||||
|
PyErr_Restore(_exception, _exc_value, _exc_traceback);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If an exception occurred during execution of this task, returns it. This
|
||||||
|
* is only set if this task returned a coroutine or generator.
|
||||||
|
*/
|
||||||
|
/*PyObject *PythonTask::
|
||||||
|
exception() const {
|
||||||
|
if (_exception == nullptr) {
|
||||||
|
Py_INCREF(Py_None);
|
||||||
|
return Py_None;
|
||||||
|
} else if (_exc_value == nullptr || _exc_value == Py_None) {
|
||||||
|
return _PyObject_CallNoArg(_exception);
|
||||||
|
} else if (PyTuple_Check(_exc_value)) {
|
||||||
|
return PyObject_Call(_exception, _exc_value, nullptr);
|
||||||
|
} else {
|
||||||
|
return PyObject_CallFunctionObjArgs(_exception, _exc_value, nullptr);
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -396,16 +492,30 @@ do_task() {
|
|||||||
*/
|
*/
|
||||||
AsyncTask::DoneStatus PythonTask::
|
AsyncTask::DoneStatus PythonTask::
|
||||||
do_python_task() {
|
do_python_task() {
|
||||||
PyObject *result = NULL;
|
PyObject *result = nullptr;
|
||||||
|
|
||||||
if (_generator == (PyObject *)NULL) {
|
// Are we waiting for a future to finish?
|
||||||
|
if (_future_done != nullptr) {
|
||||||
|
PyObject *is_done = PyObject_CallObject(_future_done, nullptr);
|
||||||
|
if (!PyObject_IsTrue(is_done)) {
|
||||||
|
// Nope, ask again next frame.
|
||||||
|
Py_DECREF(is_done);
|
||||||
|
return DS_cont;
|
||||||
|
}
|
||||||
|
Py_DECREF(is_done);
|
||||||
|
Py_DECREF(_future_done);
|
||||||
|
_future_done = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_generator == nullptr) {
|
||||||
// We are calling the function directly.
|
// We are calling the function directly.
|
||||||
|
nassertr(_function != nullptr, DS_interrupt);
|
||||||
|
|
||||||
PyObject *args = get_args();
|
PyObject *args = get_args();
|
||||||
result = PythonThread::call_python_func(_function, args);
|
result = PythonThread::call_python_func(_function, args);
|
||||||
Py_DECREF(args);
|
Py_DECREF(args);
|
||||||
|
|
||||||
#ifdef PyGen_Check
|
if (result != nullptr && PyGen_Check(result)) {
|
||||||
if (result != (PyObject *)NULL && PyGen_Check(result)) {
|
|
||||||
// The function has yielded a generator. We will call into that
|
// The function has yielded a generator. We will call into that
|
||||||
// henceforth, instead of calling the function from the top again.
|
// henceforth, instead of calling the function from the top again.
|
||||||
if (task_cat.is_debug()) {
|
if (task_cat.is_debug()) {
|
||||||
@ -423,30 +533,166 @@ do_python_task() {
|
|||||||
Py_DECREF(str);
|
Py_DECREF(str);
|
||||||
}
|
}
|
||||||
_generator = result;
|
_generator = result;
|
||||||
result = NULL;
|
result = nullptr;
|
||||||
}
|
|
||||||
|
#if PY_VERSION_HEX >= 0x03050000
|
||||||
|
} else if (result != nullptr && Py_TYPE(result)->tp_as_async != nullptr) {
|
||||||
|
// The function yielded a coroutine, or something of the sort.
|
||||||
|
if (task_cat.is_debug()) {
|
||||||
|
PyObject *str = PyObject_ASCII(_function);
|
||||||
|
PyObject *str2 = PyObject_ASCII(result);
|
||||||
|
task_cat.debug()
|
||||||
|
<< PyUnicode_AsUTF8(str) << " in " << *this
|
||||||
|
<< " yielded an awaitable: " << PyUnicode_AsUTF8(str2) << "\n";
|
||||||
|
Py_DECREF(str);
|
||||||
|
Py_DECREF(str2);
|
||||||
|
}
|
||||||
|
if (PyCoro_CheckExact(result)) {
|
||||||
|
// If a coroutine, am_await is possible but senseless, since we can
|
||||||
|
// just call send(None) on the coroutine itself.
|
||||||
|
_generator = result;
|
||||||
|
} else {
|
||||||
|
unaryfunc await = Py_TYPE(result)->tp_as_async->am_await;
|
||||||
|
_generator = await(result);
|
||||||
|
Py_DECREF(result);
|
||||||
|
}
|
||||||
|
result = nullptr;
|
||||||
#endif
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_generator != (PyObject *)NULL) {
|
if (_generator != nullptr) {
|
||||||
// We are calling a generator.
|
// We are calling a generator. Use "send" rather than PyIter_Next since
|
||||||
PyObject *func = PyObject_GetAttrString(_generator, "next");
|
// we need to be able to read the value from a StopIteration exception.
|
||||||
nassertr(func != (PyObject *)NULL, DS_interrupt);
|
PyObject *func = PyObject_GetAttrString(_generator, "send");
|
||||||
|
nassertr(func != nullptr, DS_interrupt);
|
||||||
result = PyObject_CallObject(func, NULL);
|
result = PyObject_CallFunctionObjArgs(func, Py_None, nullptr);
|
||||||
Py_DECREF(func);
|
Py_DECREF(func);
|
||||||
|
|
||||||
if (result == (PyObject *)NULL && PyErr_Occurred() &&
|
if (result == nullptr) {
|
||||||
PyErr_ExceptionMatches(PyExc_StopIteration)) {
|
// An error happened. If StopIteration, that indicates the task has
|
||||||
// "Catch" StopIteration and treat it like DS_done.
|
// returned. Otherwise, we need to save it so that it can be re-raised
|
||||||
PyErr_Clear();
|
// in the function that awaited this task.
|
||||||
Py_DECREF(_generator);
|
Py_DECREF(_generator);
|
||||||
_generator = NULL;
|
_generator = nullptr;
|
||||||
return DS_done;
|
|
||||||
|
#if PY_VERSION_HEX >= 0x03030000
|
||||||
|
if (_PyGen_FetchStopIterationValue(&result) == 0) {
|
||||||
|
#else
|
||||||
|
if (PyErr_ExceptionMatches(PyExc_StopIteration)) {
|
||||||
|
result = Py_None;
|
||||||
|
Py_INCREF(result);
|
||||||
|
#endif
|
||||||
|
PyErr_Restore(nullptr, nullptr, nullptr);
|
||||||
|
|
||||||
|
// If we passed a coroutine into the task, eg. something like:
|
||||||
|
// taskMgr.add(my_async_function())
|
||||||
|
// then we cannot rerun the task, so the return value is always
|
||||||
|
// assumed to be DS_done. Instead, we pass the return value to the
|
||||||
|
// result of the `await` expression.
|
||||||
|
if (_function == nullptr) {
|
||||||
|
if (task_cat.is_debug()) {
|
||||||
|
task_cat.debug()
|
||||||
|
<< *this << " received StopIteration from coroutine.\n";
|
||||||
|
}
|
||||||
|
// Store the result in _exc_value because that's not used anyway.
|
||||||
|
Py_XDECREF(_exc_value);
|
||||||
|
_exc_value = result;
|
||||||
|
return DS_done;
|
||||||
|
}
|
||||||
|
} else if (_function == nullptr) {
|
||||||
|
// We got an exception. If this is a scheduled coroutine, we will
|
||||||
|
// keep it and instead throw it into whatever 'awaits' this task.
|
||||||
|
// Otherwise, fall through and handle it the regular way.
|
||||||
|
Py_XDECREF(_exception);
|
||||||
|
Py_XDECREF(_exc_value);
|
||||||
|
Py_XDECREF(_exc_traceback);
|
||||||
|
PyErr_Fetch(&_exception, &_exc_value, &_exc_traceback);
|
||||||
|
_retrieved_exception = false;
|
||||||
|
|
||||||
|
if (task_cat.is_debug()) {
|
||||||
|
if (_exception != nullptr && Py_TYPE(_exception) == &PyType_Type) {
|
||||||
|
task_cat.debug()
|
||||||
|
<< *this << " received " << ((PyTypeObject *)_exception)->tp_name << " from coroutine.\n";
|
||||||
|
} else {
|
||||||
|
task_cat.debug()
|
||||||
|
<< *this << " received exception from coroutine.\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
} 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) {
|
||||||
|
// 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 (task_cat.is_debug()) {
|
||||||
|
task_cat.debug()
|
||||||
|
<< *this << " is now awaiting <" << *task << ">.\n";
|
||||||
|
}
|
||||||
|
task->_waiting_tasks.push_back(this);
|
||||||
|
} else {
|
||||||
|
// The task is already done. Continue at next opportunity.
|
||||||
|
Py_DECREF(result);
|
||||||
|
manager->_lock.release();
|
||||||
|
return DS_cont;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// This is an error. If we wanted to be fancier we could also
|
||||||
|
// detect deeper circular dependencies.
|
||||||
|
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.
|
||||||
|
PyObject *check = PyObject_GetAttrString(result, "_asyncio_future_blocking");
|
||||||
|
if (check != nullptr && check != Py_None) {
|
||||||
|
Py_DECREF(check);
|
||||||
|
// Next frame, check whether this future is done.
|
||||||
|
_future_done = PyObject_GetAttrString(result, "done");
|
||||||
|
if (_future_done == nullptr || !PyCallable_Check(_future_done)) {
|
||||||
|
task_cat.error()
|
||||||
|
<< "future.done is not callable\n";
|
||||||
|
return DS_interrupt;
|
||||||
|
}
|
||||||
|
#if PY_MAJOR_VERSION >= 3
|
||||||
|
if (task_cat.is_debug()) {
|
||||||
|
PyObject *str = PyObject_ASCII(result);
|
||||||
|
task_cat.debug()
|
||||||
|
<< *this << " is now awaiting " << PyUnicode_AsUTF8(str) << ".\n";
|
||||||
|
Py_DECREF(str);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
Py_DECREF(result);
|
||||||
|
return DS_cont;
|
||||||
|
}
|
||||||
|
PyErr_Clear();
|
||||||
|
Py_XDECREF(check);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result == (PyObject *)NULL) {
|
if (result == nullptr) {
|
||||||
if (PyErr_Occurred() && PyErr_ExceptionMatches(PyExc_SystemExit)) {
|
if (PyErr_Occurred() && PyErr_ExceptionMatches(PyExc_SystemExit)) {
|
||||||
// Don't print an error message for SystemExit. Or rather, make it a
|
// Don't print an error message for SystemExit. Or rather, make it a
|
||||||
// debug message.
|
// debug message.
|
||||||
|
@ -22,8 +22,8 @@
|
|||||||
#include "py_panda.h"
|
#include "py_panda.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class exists to allow association of a Python function with the
|
* This class exists to allow association of a Python function or coroutine
|
||||||
* AsyncTaskManager.
|
* with the AsyncTaskManager.
|
||||||
*/
|
*/
|
||||||
class PythonTask : public AsyncTask {
|
class PythonTask : public AsyncTask {
|
||||||
PUBLISHED:
|
PUBLISHED:
|
||||||
@ -32,16 +32,23 @@ PUBLISHED:
|
|||||||
ALLOC_DELETED_CHAIN(PythonTask);
|
ALLOC_DELETED_CHAIN(PythonTask);
|
||||||
|
|
||||||
void set_function(PyObject *function);
|
void set_function(PyObject *function);
|
||||||
PyObject *get_function();
|
INLINE PyObject *get_function();
|
||||||
|
|
||||||
void set_args(PyObject *args, bool append_task);
|
void set_args(PyObject *args, bool append_task);
|
||||||
PyObject *get_args();
|
PyObject *get_args();
|
||||||
|
|
||||||
void set_upon_death(PyObject *upon_death);
|
void set_upon_death(PyObject *upon_death);
|
||||||
PyObject *get_upon_death();
|
INLINE PyObject *get_upon_death();
|
||||||
|
|
||||||
void set_owner(PyObject *owner);
|
void set_owner(PyObject *owner);
|
||||||
PyObject *get_owner();
|
INLINE PyObject *get_owner() const;
|
||||||
|
|
||||||
|
INLINE void set_result(PyObject *result);
|
||||||
|
PyObject *result() const;
|
||||||
|
//PyObject *exception() const;
|
||||||
|
|
||||||
|
static PyObject *__await__(PyObject *self);
|
||||||
|
INLINE static PyObject *__iter__(PyObject *self);
|
||||||
|
|
||||||
int __setattr__(PyObject *self, PyObject *attr, PyObject *v);
|
int __setattr__(PyObject *self, PyObject *attr, PyObject *v);
|
||||||
int __delattr__(PyObject *self, PyObject *attr);
|
int __delattr__(PyObject *self, PyObject *attr);
|
||||||
@ -94,6 +101,8 @@ protected:
|
|||||||
virtual void upon_death(AsyncTaskManager *manager, bool clean_exit);
|
virtual void upon_death(AsyncTaskManager *manager, bool clean_exit);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
static PyObject *gen_next(PyObject *self);
|
||||||
|
|
||||||
void register_to_owner();
|
void register_to_owner();
|
||||||
void unregister_from_owner();
|
void unregister_from_owner();
|
||||||
void call_owner_method(const char *method_name);
|
void call_owner_method(const char *method_name);
|
||||||
@ -102,12 +111,19 @@ private:
|
|||||||
private:
|
private:
|
||||||
PyObject *_function;
|
PyObject *_function;
|
||||||
PyObject *_args;
|
PyObject *_args;
|
||||||
bool _append_task;
|
|
||||||
PyObject *_upon_death;
|
PyObject *_upon_death;
|
||||||
PyObject *_owner;
|
PyObject *_owner;
|
||||||
bool _registered_to_owner;
|
|
||||||
|
PyObject *_exception;
|
||||||
|
PyObject *_exc_value;
|
||||||
|
PyObject *_exc_traceback;
|
||||||
|
|
||||||
PyObject *_generator;
|
PyObject *_generator;
|
||||||
|
PyObject *_future_done;
|
||||||
|
|
||||||
|
bool _append_task;
|
||||||
|
bool _registered_to_owner;
|
||||||
|
mutable bool _retrieved_exception;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static TypeHandle get_class_type() {
|
static TypeHandle get_class_type() {
|
||||||
|
@ -34,7 +34,7 @@ get_orig() const {
|
|||||||
/**
|
/**
|
||||||
* Returns true if this request has completed, false if it is still pending.
|
* 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
|
* When this returns true, you may retrieve the model loaded by calling
|
||||||
* get_result().
|
* result().
|
||||||
*/
|
*/
|
||||||
INLINE bool ModelFlattenRequest::
|
INLINE bool ModelFlattenRequest::
|
||||||
is_ready() const {
|
is_ready() const {
|
||||||
@ -47,6 +47,20 @@ is_ready() const {
|
|||||||
*/
|
*/
|
||||||
INLINE PandaNode *ModelFlattenRequest::
|
INLINE PandaNode *ModelFlattenRequest::
|
||||||
get_model() const {
|
get_model() const {
|
||||||
nassertr(_is_ready, NULL);
|
nassertr(_is_ready, nullptr);
|
||||||
return _model;
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
#include "asyncTask.h"
|
#include "asyncTask.h"
|
||||||
#include "pandaNode.h"
|
#include "pandaNode.h"
|
||||||
#include "pointerTo.h"
|
#include "pointerTo.h"
|
||||||
|
#include "nodePath.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class object manages a single asynchronous request to flatten a model.
|
* This class object manages a single asynchronous request to flatten a model.
|
||||||
@ -38,9 +39,10 @@ PUBLISHED:
|
|||||||
INLINE bool is_ready() const;
|
INLINE bool is_ready() const;
|
||||||
INLINE PandaNode *get_model() const;
|
INLINE PandaNode *get_model() const;
|
||||||
|
|
||||||
|
INLINE NodePath result() const;
|
||||||
|
|
||||||
MAKE_PROPERTY(orig, get_orig);
|
MAKE_PROPERTY(orig, get_orig);
|
||||||
MAKE_PROPERTY(ready, is_ready);
|
MAKE_PROPERTY(ready, is_ready);
|
||||||
MAKE_PROPERTY(model, get_model);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual DoneStatus do_task();
|
virtual DoneStatus do_task();
|
||||||
|
@ -37,6 +37,16 @@ get_loader() const {
|
|||||||
return _loader;
|
return _loader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
* 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
|
* When this returns true, you may retrieve the model loaded by calling
|
||||||
|
@ -11,8 +11,8 @@
|
|||||||
* @date 2006-08-29
|
* @date 2006-08-29
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef MODELLOADREQUEST
|
#ifndef MODELLOADREQUEST_H
|
||||||
#define MODELLOADREQUEST
|
#define MODELLOADREQUEST_H
|
||||||
|
|
||||||
#include "pandabase.h"
|
#include "pandabase.h"
|
||||||
|
|
||||||
@ -22,6 +22,7 @@
|
|||||||
#include "pandaNode.h"
|
#include "pandaNode.h"
|
||||||
#include "pointerTo.h"
|
#include "pointerTo.h"
|
||||||
#include "loader.h"
|
#include "loader.h"
|
||||||
|
#include "nodePath.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class object that manages a single asynchronous model load request.
|
* A class object that manages a single asynchronous model load request.
|
||||||
@ -42,6 +43,8 @@ PUBLISHED:
|
|||||||
INLINE const LoaderOptions &get_options() const;
|
INLINE const LoaderOptions &get_options() const;
|
||||||
INLINE Loader *get_loader() const;
|
INLINE Loader *get_loader() const;
|
||||||
|
|
||||||
|
INLINE NodePath result() const;
|
||||||
|
|
||||||
INLINE bool is_ready() const;
|
INLINE bool is_ready() const;
|
||||||
INLINE PandaNode *get_model() const;
|
INLINE PandaNode *get_model() const;
|
||||||
|
|
||||||
@ -49,7 +52,6 @@ PUBLISHED:
|
|||||||
MAKE_PROPERTY(options, get_options);
|
MAKE_PROPERTY(options, get_options);
|
||||||
MAKE_PROPERTY(loader, get_loader);
|
MAKE_PROPERTY(loader, get_loader);
|
||||||
MAKE_PROPERTY(ready, is_ready);
|
MAKE_PROPERTY(ready, is_ready);
|
||||||
MAKE_PROPERTY(model, get_model);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual DoneStatus do_task();
|
virtual DoneStatus do_task();
|
||||||
|
@ -64,3 +64,13 @@ get_success() const {
|
|||||||
nassertr(_is_ready, false);
|
nassertr(_is_ready, false);
|
||||||
return _success;
|
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);
|
||||||
|
return _success;
|
||||||
|
}
|
||||||
|
@ -11,8 +11,8 @@
|
|||||||
* @date 2012-12-19
|
* @date 2012-12-19
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef MODELSAVEREQUEST
|
#ifndef MODELSAVEREQUEST_H
|
||||||
#define MODELSAVEREQUEST
|
#define MODELSAVEREQUEST_H
|
||||||
|
|
||||||
#include "pandabase.h"
|
#include "pandabase.h"
|
||||||
|
|
||||||
@ -46,12 +46,13 @@ PUBLISHED:
|
|||||||
INLINE bool is_ready() const;
|
INLINE bool is_ready() const;
|
||||||
INLINE bool get_success() const;
|
INLINE bool get_success() const;
|
||||||
|
|
||||||
|
INLINE bool result() const;
|
||||||
|
|
||||||
MAKE_PROPERTY(filename, get_filename);
|
MAKE_PROPERTY(filename, get_filename);
|
||||||
MAKE_PROPERTY(options, get_options);
|
MAKE_PROPERTY(options, get_options);
|
||||||
MAKE_PROPERTY(node, get_node);
|
MAKE_PROPERTY(node, get_node);
|
||||||
MAKE_PROPERTY(loader, get_loader);
|
MAKE_PROPERTY(loader, get_loader);
|
||||||
MAKE_PROPERTY(ready, is_ready);
|
MAKE_PROPERTY(ready, is_ready);
|
||||||
MAKE_PROPERTY(success, get_success);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual DoneStatus do_task();
|
virtual DoneStatus do_task();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user