mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-01 09:23:03 -04:00
async JavaScript
This commit is contained in:
parent
8aa18e2e9c
commit
aa8285f5a4
@ -168,11 +168,13 @@ run_python() {
|
||||
Py_DECREF(runp3d);
|
||||
|
||||
|
||||
// Construct a Python wrapper around our request_func() method.
|
||||
// Construct a Python wrapper around our methods we need to expose to Python.
|
||||
static PyMethodDef p3dpython_methods[] = {
|
||||
{"request_func", P3DPythonRun::st_request_func, METH_VARARGS,
|
||||
"Send an asynchronous request to the plugin host"},
|
||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||
{ "check_comm", P3DPythonRun::st_check_comm, METH_VARARGS,
|
||||
"Poll for communications from the parent process" },
|
||||
{ "request_func", P3DPythonRun::st_request_func, METH_VARARGS,
|
||||
"Send an asynchronous request to the plugin host" },
|
||||
{ NULL, NULL, 0, NULL } /* Sentinel */
|
||||
};
|
||||
PyObject *p3dpython = Py_InitModule("p3dpython", p3dpython_methods);
|
||||
if (p3dpython == NULL) {
|
||||
@ -196,11 +198,31 @@ run_python() {
|
||||
Py_DECREF(request_func);
|
||||
|
||||
|
||||
// Now add check_comm() as a task.
|
||||
_check_comm_task = new GenericAsyncTask("check_comm", task_check_comm, this);
|
||||
// Now add check_comm() as a task. It can be a threaded task, but
|
||||
// this does mean that application programmers will have to be alert
|
||||
// to asynchronous calls coming in from JavaScript. We'll put it on
|
||||
// its own task chain so the application programmer can decide how
|
||||
// it should be.
|
||||
AsyncTaskManager *task_mgr = AsyncTaskManager::get_global_ptr();
|
||||
PT(AsyncTaskChain) chain = task_mgr->make_task_chain("JavaScript");
|
||||
chain->set_num_threads(1);
|
||||
chain->set_thread_priority(TP_low);
|
||||
|
||||
PyObject *check_comm = PyObject_GetAttrString(p3dpython, "check_comm");
|
||||
if (check_comm == NULL) {
|
||||
PyErr_Print();
|
||||
return false;
|
||||
}
|
||||
|
||||
// We have to make it a PythonTask, not just a GenericAsyncTask,
|
||||
// because we need the code in PythonTask that supports calling into
|
||||
// Python from a separate thread.
|
||||
_check_comm_task = new PythonTask(check_comm, "check_comm");
|
||||
_check_comm_task->set_task_chain("JavaScript");
|
||||
task_mgr->add(_check_comm_task);
|
||||
|
||||
Py_DECREF(check_comm);
|
||||
|
||||
// Finally, get lost in taskMgr.run().
|
||||
PyObject *done = PyObject_CallMethod(_taskMgr, (char *)"run", (char *)"");
|
||||
if (done == NULL) {
|
||||
@ -216,11 +238,14 @@ run_python() {
|
||||
// Function: P3DPythonRun::handle_command
|
||||
// Access: Private
|
||||
// Description: Handles a command received from the plugin host, via
|
||||
// an XML syntax on the wire.
|
||||
// an XML syntax on the wire. Ownership of the XML
|
||||
// document object is passed into this method.
|
||||
//
|
||||
// It's important *not* to be holding _commands_lock
|
||||
// when calling this method.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void P3DPythonRun::
|
||||
handle_command(TiXmlDocument *doc) {
|
||||
nout << "received: " << *doc << "\n" << flush;
|
||||
TiXmlElement *xcommand = doc->FirstChildElement("command");
|
||||
if (xcommand != NULL) {
|
||||
bool needs_response = false;
|
||||
@ -281,9 +306,15 @@ handle_command(TiXmlDocument *doc) {
|
||||
handle_pyobj_command(xcommand, needs_response, want_response_id);
|
||||
|
||||
} else if (strcmp(cmd, "script_response") == 0) {
|
||||
// Response from a script request.
|
||||
assert(!needs_response);
|
||||
nout << "Ignoring unexpected script_response\n";
|
||||
// Response from a script request. In this case, we just
|
||||
// store it away instead of processing it immediately.
|
||||
|
||||
MutexHolder holder(_responses_lock);
|
||||
_responses.push_back(doc);
|
||||
|
||||
// And now we must return out, instead of deleting the
|
||||
// document at the bottom of this method.
|
||||
return;
|
||||
|
||||
} else if (strcmp(cmd, "drop_pyobj") == 0) {
|
||||
int object_id;
|
||||
@ -312,6 +343,8 @@ handle_command(TiXmlDocument *doc) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delete doc;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
@ -560,45 +593,40 @@ handle_pyobj_command(TiXmlElement *xcommand, bool needs_response,
|
||||
// Function: P3DPythonRun::check_comm
|
||||
// Access: Private
|
||||
// Description: This method is added to the task manager (via
|
||||
// task_check_comm, below) so that it gets a call every
|
||||
// st_check_comm, below) so that it gets a call every
|
||||
// frame. Its job is to check for commands received
|
||||
// from the plugin host in the parent process.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void P3DPythonRun::
|
||||
check_comm() {
|
||||
nout << ":" << flush;
|
||||
// nout << ":" << flush;
|
||||
ACQUIRE_LOCK(_commands_lock);
|
||||
while (!_commands.empty()) {
|
||||
TiXmlDocument *doc = _commands.front();
|
||||
_commands.pop_front();
|
||||
assert(_commands.size() < 10);
|
||||
RELEASE_LOCK(_commands_lock);
|
||||
handle_command(doc);
|
||||
delete doc;
|
||||
ACQUIRE_LOCK(_commands_lock);
|
||||
}
|
||||
RELEASE_LOCK(_commands_lock);
|
||||
|
||||
if (!_program_continue) {
|
||||
// The low-level thread detected an error, for instance pipe
|
||||
// closed. We should exit gracefully.
|
||||
terminate_session();
|
||||
}
|
||||
|
||||
RELEASE_LOCK(_commands_lock);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: P3DPythonRun::task_check_comm
|
||||
// Function: P3DPythonRun::st_check_comm
|
||||
// Access: Private, Static
|
||||
// Description: This static function wrapper around check_comm is
|
||||
// necessary to add the method function to the
|
||||
// GenericAsyncTask object.
|
||||
// Description: This is a static Python wrapper around py_check_comm,
|
||||
// needed to add the function to a PythonTask.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
AsyncTask::DoneStatus P3DPythonRun::
|
||||
task_check_comm(GenericAsyncTask *task, void *user_data) {
|
||||
P3DPythonRun *self = (P3DPythonRun *)user_data;
|
||||
self->check_comm();
|
||||
return AsyncTask::DS_cont;
|
||||
PyObject *P3DPythonRun::
|
||||
st_check_comm(PyObject *, PyObject *args) {
|
||||
P3DPythonRun::_global_ptr->check_comm();
|
||||
return Py_BuildValue("i", AsyncTask::DS_cont);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
@ -611,54 +639,63 @@ task_check_comm(GenericAsyncTask *task, void *user_data) {
|
||||
////////////////////////////////////////////////////////////////////
|
||||
TiXmlDocument *P3DPythonRun::
|
||||
wait_script_response(int response_id) {
|
||||
nout << "waiting script_response " << response_id << "\n" << flush;
|
||||
// nout << "waiting script_response " << response_id << "\n" << flush;
|
||||
while (true) {
|
||||
ACQUIRE_LOCK(_commands_lock);
|
||||
|
||||
Commands::iterator ci;
|
||||
|
||||
// First, walk through the _commands queue to see if there's
|
||||
// anything that needs immediate processing.
|
||||
ACQUIRE_LOCK(_commands_lock);
|
||||
for (ci = _commands.begin(); ci != _commands.end(); ++ci) {
|
||||
TiXmlDocument *doc = (*ci);
|
||||
|
||||
TiXmlElement *xcommand = doc->FirstChildElement("command");
|
||||
if (xcommand != NULL) {
|
||||
const char *cmd = xcommand->Attribute("cmd");
|
||||
if (cmd != NULL && strcmp(cmd, "script_response") == 0) {
|
||||
int unique_id;
|
||||
if (xcommand->QueryIntAttribute("unique_id", &unique_id) == TIXML_SUCCESS) {
|
||||
if (unique_id == response_id) {
|
||||
// This is the response we were waiting for.
|
||||
_commands.erase(ci);
|
||||
RELEASE_LOCK(_commands_lock);
|
||||
nout << "got script_response " << unique_id << "\n" << flush;
|
||||
return doc;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((cmd != NULL && strcmp(cmd, "script_response") == 0) ||
|
||||
xcommand->Attribute("want_response_id") != NULL) {
|
||||
|
||||
// It's not the response we're waiting for, but maybe we need
|
||||
// to handle it anyway.
|
||||
bool needs_response = false;
|
||||
int want_response_id;
|
||||
if (xcommand->QueryIntAttribute("want_response_id", &want_response_id) == TIXML_SUCCESS) {
|
||||
// This command will be wanting a response. We'd better
|
||||
// honor it right away, or we risk deadlock with the browser
|
||||
// process and the Python process waiting for each other.
|
||||
nout << "honoring response " << want_response_id << "\n" << flush;
|
||||
// This is either a response, or it's a command that will
|
||||
// want a response itself. In either case we should handle
|
||||
// it right away. ("handling" a response means moving it to
|
||||
// the _responses queue.)
|
||||
_commands.erase(ci);
|
||||
RELEASE_LOCK(_commands_lock);
|
||||
handle_command(doc);
|
||||
delete doc;
|
||||
ACQUIRE_LOCK(_commands_lock);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RELEASE_LOCK(_commands_lock);
|
||||
|
||||
// Now, walk through the _responses queue to look for the
|
||||
// particular response we're waiting for.
|
||||
_responses_lock.acquire();
|
||||
for (ci = _responses.begin(); ci != _responses.end(); ++ci) {
|
||||
TiXmlDocument *doc = (*ci);
|
||||
|
||||
TiXmlElement *xcommand = doc->FirstChildElement("command");
|
||||
assert(xcommand != NULL);
|
||||
const char *cmd = xcommand->Attribute("cmd");
|
||||
assert(cmd != NULL && strcmp(cmd, "script_response") == 0);
|
||||
|
||||
int unique_id;
|
||||
if (xcommand->QueryIntAttribute("unique_id", &unique_id) == TIXML_SUCCESS) {
|
||||
if (unique_id == response_id) {
|
||||
// This is the response we were waiting for.
|
||||
_responses.erase(ci);
|
||||
_responses_lock.release();
|
||||
// nout << "got script_response " << unique_id << "\n" << flush;
|
||||
return doc;
|
||||
}
|
||||
}
|
||||
}
|
||||
_responses_lock.release();
|
||||
|
||||
if (!_program_continue) {
|
||||
terminate_session();
|
||||
}
|
||||
|
||||
RELEASE_LOCK(_commands_lock);
|
||||
|
||||
#ifdef _WIN32
|
||||
// Make sure we process the Windows event loop while we're
|
||||
@ -671,7 +708,7 @@ wait_script_response(int response_id) {
|
||||
PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE | PM_NOYIELD);
|
||||
#endif // _WIN32
|
||||
|
||||
nout << "." << flush;
|
||||
// nout << "." << flush;
|
||||
|
||||
// It hasn't shown up yet. Give the sub-thread a chance to
|
||||
// process the input and append it to the queue.
|
||||
|
@ -25,9 +25,10 @@
|
||||
#include "p3d_lock.h"
|
||||
#include "handleStream.h"
|
||||
#include "p3dCInstance.h"
|
||||
#include "genericAsyncTask.h"
|
||||
#include "pythonTask.h"
|
||||
#include "pmap.h"
|
||||
#include "pdeque.h"
|
||||
#include "pmutex.h"
|
||||
#include "get_tinyxml.h"
|
||||
|
||||
#include <Python.h>
|
||||
@ -75,7 +76,7 @@ private:
|
||||
void handle_script_response_command(TiXmlElement *xcommand);
|
||||
|
||||
void check_comm();
|
||||
static AsyncTask::DoneStatus task_check_comm(GenericAsyncTask *task, void *user_data);
|
||||
static PyObject *st_check_comm(PyObject *, PyObject *args);
|
||||
TiXmlDocument *wait_script_response(int response_id);
|
||||
|
||||
PyObject *py_request_func(PyObject *args);
|
||||
@ -118,7 +119,7 @@ private:
|
||||
PyObject *_browser_object_class;
|
||||
PyObject *_taskMgr;
|
||||
|
||||
PT(GenericAsyncTask) _check_comm_task;
|
||||
PT(PythonTask) _check_comm_task;
|
||||
|
||||
// This map keeps track of the PyObject pointers we have delivered
|
||||
// to the parent process. We have to hold the reference count on
|
||||
@ -128,8 +129,14 @@ private:
|
||||
SentObjects _sent_objects;
|
||||
int _next_sent_id;
|
||||
|
||||
// The remaining members are manipulated by the read thread.
|
||||
typedef pdeque<TiXmlDocument *> Commands;
|
||||
|
||||
// This is a special queue of responses extracted from the _commands
|
||||
// queue, below. It's protected by the Panda mutex.
|
||||
Commands _responses;
|
||||
Mutex _responses_lock;
|
||||
|
||||
// The remaining members are manipulated by the read thread.
|
||||
Commands _commands;
|
||||
|
||||
// This has to be an actual OS LOCK instead of Panda's Mutex,
|
||||
|
@ -311,6 +311,7 @@ class Messenger:
|
||||
|
||||
if taskChain:
|
||||
# Queue the event onto the indicated task chain.
|
||||
from direct.task.TaskManagerGlobal import taskMgr
|
||||
taskMgr.add(self.__lockAndDispatch, name = 'Messenger-%s-%s' % (event, taskChain), extraArgs = [acceptorDict, event, sentArgs, foundWatch], taskChain = taskChain)
|
||||
else:
|
||||
# Handle the event immediately.
|
||||
|
@ -68,12 +68,18 @@ class BrowserObject:
|
||||
def __nonzero__(self):
|
||||
return True
|
||||
|
||||
def __call__(self, *args):
|
||||
def __call__(self, *args, **kw):
|
||||
needsResponse = True
|
||||
if 'needsResponse' in kw:
|
||||
needsResponse = kw['needsResponse']
|
||||
del kw['needsResponse']
|
||||
if kw:
|
||||
raise ArgumentError, 'Keyword arguments not supported'
|
||||
|
||||
try:
|
||||
parentObj, attribName = self.__boundMethod
|
||||
if parentObj:
|
||||
# Call it as a method.
|
||||
needsResponse = True
|
||||
if parentObj is self.__runner.dom and attribName == 'alert':
|
||||
# As a special hack, we don't wait for the return
|
||||
# value from the alert() call, since this is a
|
||||
@ -98,7 +104,7 @@ class BrowserObject:
|
||||
raise AttributeError
|
||||
else:
|
||||
# Call it as a plain function.
|
||||
result = self.__runner.scriptRequest('call', self, value = args)
|
||||
result = self.__runner.scriptRequest('call', self, value = args, needsResponse = needsResponse)
|
||||
except EnvironmentError:
|
||||
# Some odd problem on the call.
|
||||
raise TypeError
|
||||
@ -208,11 +214,17 @@ class MethodWrapper:
|
||||
def __nonzero__(self):
|
||||
return True
|
||||
|
||||
def __call__(self, *args):
|
||||
def __call__(self, *args, **kw):
|
||||
needsResponse = True
|
||||
if 'needsResponse' in kw:
|
||||
needsResponse = kw['needsResponse']
|
||||
del kw['needsResponse']
|
||||
if kw:
|
||||
raise ArgumentError, 'Keyword arguments not supported'
|
||||
|
||||
try:
|
||||
parentObj, attribName = self.__boundMethod
|
||||
# Call it as a method.
|
||||
needsResponse = True
|
||||
if parentObj is self.__runner.dom and attribName == 'alert':
|
||||
# As a special hack, we don't wait for the return
|
||||
# value from the alert() call, since this is a
|
||||
|
@ -25,6 +25,7 @@ from direct.showbase.DirectObject import DirectObject
|
||||
from pandac.PandaModules import VirtualFileSystem, Filename, Multifile, loadPrcFileData, unloadPrcFile, getModelPath, HTTPClient, Thread, WindowProperties
|
||||
from direct.stdpy import file
|
||||
from direct.task.TaskManagerGlobal import taskMgr
|
||||
from direct.showbase.MessengerGlobal import messenger
|
||||
from direct.showbase import AppRunnerGlobal
|
||||
|
||||
# These imports are read by the C++ wrapper in p3dPythonRun.cxx.
|
||||
@ -98,6 +99,11 @@ class AppRunner(DirectObject):
|
||||
if AppRunnerGlobal.appRunner is None:
|
||||
AppRunnerGlobal.appRunner = self
|
||||
|
||||
# We use this messenger hook to dispatch this startIfReady()
|
||||
# call back to the main thread.
|
||||
self.accept('startIfReady', self.startIfReady)
|
||||
|
||||
|
||||
def setSessionId(self, sessionId):
|
||||
""" This message should come in at startup. """
|
||||
self.sessionId = sessionId
|
||||
@ -167,6 +173,9 @@ class AppRunner(DirectObject):
|
||||
if self.gotWindow and self.gotP3DFilename:
|
||||
self.started = True
|
||||
|
||||
# Now we can ignore future calls to startIfReady().
|
||||
self.ignore('startIfReady')
|
||||
|
||||
# Hang a hook so we know when the window is actually opened.
|
||||
self.acceptOnce('window-event', self.windowEvent)
|
||||
|
||||
@ -265,7 +274,8 @@ class AppRunner(DirectObject):
|
||||
|
||||
self.gotP3DFilename = True
|
||||
|
||||
self.startIfReady()
|
||||
# Send this call to the main thread; don't call it directly.
|
||||
messenger.send('startIfReady', taskChain = 'default')
|
||||
|
||||
def clearWindowPrc(self):
|
||||
""" Clears the windowPrc file that was created in a previous
|
||||
@ -325,7 +335,9 @@ class AppRunner(DirectObject):
|
||||
self.windowPrc = loadPrcFileData("setupWindow", data)
|
||||
|
||||
self.gotWindow = True
|
||||
self.startIfReady()
|
||||
|
||||
# Send this call to the main thread; don't call it directly.
|
||||
messenger.send('startIfReady', taskChain = 'default')
|
||||
|
||||
def setRequestFunc(self, func):
|
||||
""" This method is called by the plugin at startup to supply a
|
||||
@ -468,4 +480,4 @@ if __name__ == '__main__':
|
||||
except ArgumentError, e:
|
||||
print e.args[0]
|
||||
sys.exit(1)
|
||||
run()
|
||||
taskMgr.run()
|
||||
|
Loading…
x
Reference in New Issue
Block a user