more work on outbound scripting

This commit is contained in:
David Rose 2009-07-07 01:05:41 +00:00
parent b2906cda8b
commit 59210bf718
12 changed files with 214 additions and 111 deletions

View File

@ -173,7 +173,7 @@ set_wparams(const P3DWindowParams &wparams) {
// of the instance, to be used by JavaScript code in the
// browser to control this program.
////////////////////////////////////////////////////////////////////
P3DObject *P3DInstance::
P3D_object *P3DInstance::
get_panda_script_object() const {
assert(_session != NULL);
nout << "Called P3DInstance::get_panda_script_object()\n";
@ -188,13 +188,13 @@ get_panda_script_object() const {
TiXmlDocument *response = _session->command_and_response(doc);
nout << "response pointer: " << response << "\n" << flush;
P3DObject *result = NULL;
P3D_object *result = NULL;
if (response != NULL) {
TiXmlElement *xresponse = response->FirstChildElement("response");
if (xresponse != NULL) {
TiXmlElement *xvalue = xresponse->FirstChildElement("value");
if (xvalue != NULL) {
result = _session->xml_to_object(xvalue);
result = _session->xml_to_p3dobj(xvalue);
}
}
delete response;
@ -464,7 +464,7 @@ send_browser_script_object() {
xcommand->SetAttribute("cmd", "pyobj");
xcommand->SetAttribute("op", "set_browser_script_object");
if (_browser_script_object != NULL) {
xcommand->LinkEndChild(_session->object_to_xml(_browser_script_object));
xcommand->LinkEndChild(_session->p3dobj_to_xml(_browser_script_object));
}
doc->LinkEndChild(decl);
@ -534,7 +534,7 @@ handle_script_request(P3D_request *request) {
doc->LinkEndChild(decl);
doc->LinkEndChild(xcommand);
if (value != NULL) {
xcommand->LinkEndChild(_session->object_to_xml(value));
xcommand->LinkEndChild(_session->p3dobj_to_xml(value));
P3D_OBJECT_FINISH(value);
}

View File

@ -46,7 +46,7 @@ public:
void set_wparams(const P3DWindowParams &wparams);
inline const P3DWindowParams &get_wparams() const;
P3DObject *get_panda_script_object() const;
P3D_object *get_panda_script_object() const;
void set_browser_script_object(P3D_object *object);
bool has_request();

View File

@ -194,7 +194,7 @@ call(const string &method_name, P3D_object *params[], int num_params) const {
}
for (int i = 0; i < num_params; ++i) {
TiXmlElement *xparams = _session->object_to_xml(params[i]);
TiXmlElement *xparams = _session->p3dobj_to_xml(params[i]);
xcommand->LinkEndChild(xparams);
// Now we're done with the params object passed in, we can delete
@ -207,13 +207,13 @@ call(const string &method_name, P3D_object *params[], int num_params) const {
TiXmlDocument *response = _session->command_and_response(doc);
nout << "call response pointer: " << response << "\n" << flush;
P3DObject *result = NULL;
P3D_object *result = NULL;
if (response != NULL) {
TiXmlElement *xresponse = response->FirstChildElement("response");
if (xresponse != NULL) {
TiXmlElement *xvalue = xresponse->FirstChildElement("value");
if (xvalue != NULL) {
result = _session->xml_to_object(xvalue);
result = _session->xml_to_p3dobj(xvalue);
}
}
delete response;

View File

@ -172,7 +172,7 @@ run_python() {
// Now add check_comm() as a task.
_check_comm_task = new GenericAsyncTask("check_comm", st_check_comm, this);
_check_comm_task = new GenericAsyncTask("check_comm", task_check_comm, this);
AsyncTaskManager *task_mgr = AsyncTaskManager::get_global_ptr();
task_mgr->add(_check_comm_task);
@ -244,7 +244,7 @@ handle_command(TiXmlDocument *doc) {
} else if (strcmp(cmd, "script_response") == 0) {
// Response from a script request.
assert(!needs_response);
handle_script_response_command(xcommand);
nout << "Ignoring unexpected script_response\n";
} else {
nout << "Unhandled command " << cmd << "\n";
@ -408,41 +408,16 @@ handle_pyobj_command(TiXmlElement *xcommand, bool needs_response,
}
}
////////////////////////////////////////////////////////////////////
// Function: P3DPythonRun::handle_script_response_command
// Access: Private
// Description: Handles the script_response command, a response from
// the browser to a previous script request from this
// process.
////////////////////////////////////////////////////////////////////
void P3DPythonRun::
handle_script_response_command(TiXmlElement *xcommand) {
int unique_id;
if (xcommand->QueryIntAttribute("unique_id", &unique_id) == TIXML_SUCCESS) {
PyObject *value = NULL;
TiXmlElement *xvalue = xcommand->FirstChildElement("value");
if (xvalue != NULL) {
value = xml_to_pyobj(xvalue);
} else {
value = Py_None;
Py_INCREF(value);
}
PyObject *result = PyObject_CallMethod
(_runner, (char *)"scriptResponse", (char *)"iO", unique_id, value);
Py_DECREF(value);
}
}
////////////////////////////////////////////////////////////////////
// Function: P3DPythonRun::check_comm
// Access: Private
// Description: This method is added to the Python task manager (via
// py_check_comm, below) so that it gets a call every
// Description: This method is added to the task manager (via
// task_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.
////////////////////////////////////////////////////////////////////
AsyncTask::DoneStatus P3DPythonRun::
check_comm(GenericAsyncTask *task) {
void P3DPythonRun::
check_comm() {
ACQUIRE_LOCK(_commands_lock);
while (!_commands.empty()) {
TiXmlDocument *doc = _commands.front();
@ -461,21 +436,82 @@ check_comm(GenericAsyncTask *task) {
}
RELEASE_LOCK(_commands_lock);
return AsyncTask::DS_cont;
}
////////////////////////////////////////////////////////////////////
// Function: P3DPythonRun::st_check_comm
// Function: P3DPythonRun::task_check_comm
// Access: Private, Static
// Description: This static function wrapper around check_comm is
// necessary to add the method function to the
// GenericAsyncTask object.
////////////////////////////////////////////////////////////////////
AsyncTask::DoneStatus P3DPythonRun::
st_check_comm(GenericAsyncTask *task, void *user_data) {
task_check_comm(GenericAsyncTask *task, void *user_data) {
P3DPythonRun *self = (P3DPythonRun *)user_data;
return self->check_comm(task);
self->check_comm();
return AsyncTask::DS_cont;
}
////////////////////////////////////////////////////////////////////
// Function: P3DPythonRun::wait_script_response
// Access: Private
// Description: This method is similar to check_comm(), above, but
// instead of handling all events, it waits for a
// specific script_response ID to come back from the
// browser, and leaves all other events in the queue.
////////////////////////////////////////////////////////////////////
TiXmlDocument *P3DPythonRun::
wait_script_response(int response_id) {
while (true) {
ACQUIRE_LOCK(_commands_lock);
Commands::iterator ci;
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);
return doc;
}
}
}
// 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.
_commands.erase(ci);
RELEASE_LOCK(_commands_lock);
handle_command(doc);
delete doc;
ACQUIRE_LOCK(_commands_lock);
break;
}
}
}
if (!_program_continue) {
terminate_session();
}
RELEASE_LOCK(_commands_lock);
// It hasn't shown up yet. Give the sub-thread a chance to
// process the input and append it to the queue.
Thread::force_yield();
}
}
////////////////////////////////////////////////////////////////////
@ -494,6 +530,38 @@ py_request_func(PyObject *args) {
return NULL;
}
if (strcmp(request_type, "wait_script_response") == 0) {
// This is a special case. Instead of generating a new request,
// this means to wait for a particular script_response to come in
// on the wire.
int response_id;
if (!PyArg_ParseTuple(extra_args, "i", &response_id)) {
Py_DECREF(extra_args);
return NULL;
}
nout << "Waiting for script_response " << response_id << "\n";
TiXmlDocument *doc = wait_script_response(response_id);
nout << "got: " << *doc << "\n";
TiXmlElement *xcommand = doc->FirstChildElement("command");
assert(xcommand != NULL);
TiXmlElement *xvalue = xcommand->FirstChildElement("value");
PyObject *value = NULL;
if (xvalue != NULL) {
nout << "Converting xvalue: " << *xvalue << "\n";
value = xml_to_pyobj(xvalue);
} else {
value = Py_None;
Py_INCREF(value);
}
nout << "Got script_response " << response_id << ", xvalue = " << xvalue << "\n";
delete doc;
Py_DECREF(extra_args);
return value;
}
TiXmlDocument doc;
TiXmlDeclaration *decl = new TiXmlDeclaration("1.0", "utf-8", "");
TiXmlElement *xrequest = new TiXmlElement("request");
@ -506,6 +574,7 @@ py_request_func(PyObject *args) {
// A general notification to be sent directly to the instance.
const char *message;
if (!PyArg_ParseTuple(extra_args, "s", &message)) {
Py_DECREF(extra_args);
return NULL;
}
@ -522,8 +591,10 @@ py_request_func(PyObject *args) {
int unique_id;
if (!PyArg_ParseTuple(extra_args, "sOsOi",
&operation, &object, &property_name, &value, &unique_id)) {
Py_DECREF(extra_args);
return NULL;
}
xrequest->SetAttribute("operation", operation);
xrequest->SetAttribute("property_name", property_name);
xrequest->SetAttribute("unique_id", unique_id);
@ -542,6 +613,7 @@ py_request_func(PyObject *args) {
const char *expression;
int unique_id;
if (!PyArg_ParseTuple(extra_args, "si", &expression, &unique_id)) {
Py_DECREF(extra_args);
return NULL;
}
@ -554,9 +626,11 @@ py_request_func(PyObject *args) {
} else {
string message = string("Unsupported request type: ") + string(request_type);
PyErr_SetString(PyExc_ValueError, message.c_str());
Py_DECREF(extra_args);
return NULL;
}
Py_DECREF(extra_args);
return Py_BuildValue("");
}
@ -780,6 +854,11 @@ terminate_session() {
}
Py_DECREF(result);
nout << "done calling stop()\n";
// The task manager is cleaned up. Let's exit immediately here,
// rather than returning all the way up. This just makes it easier
// when we call terminate_session() from a deeply-nested loop.
exit(0);
}
////////////////////////////////////////////////////////////////////
@ -921,7 +1000,8 @@ xml_to_pyobj(TiXmlElement *xvalue) {
int object_id;
if (xvalue->QueryIntAttribute("object_id", &object_id) == TIXML_SUCCESS) {
// Construct a new BrowserObject wrapper around this object.
return PyObject_CallFunction(_browser_object_class, (char *)"i", object_id);
return PyObject_CallFunction(_browser_object_class, (char *)"Oi",
_runner, object_id);
}
} else if (strcmp(type, "python") == 0) {

View File

@ -74,8 +74,9 @@ private:
int want_response_id);
void handle_script_response_command(TiXmlElement *xcommand);
AsyncTask::DoneStatus check_comm(GenericAsyncTask *task);
static AsyncTask::DoneStatus st_check_comm(GenericAsyncTask *task, void *user_data);
void check_comm();
static AsyncTask::DoneStatus task_check_comm(GenericAsyncTask *task, void *user_data);
TiXmlDocument *wait_script_response(int response_id);
PyObject *py_request_func(PyObject *args);
static PyObject *st_request_func(PyObject *, PyObject *args);

View File

@ -312,14 +312,14 @@ command_and_response(TiXmlDocument *command) {
}
////////////////////////////////////////////////////////////////////
// Function: P3DSession::xml_to_object
// Function: P3DSession::xml_to_p3dobj
// Access: Public
// Description: Converts the XML representation of the particular
// object value into a corresponding P3DObject object.
// object value into a corresponding P3D_object.
// Returns the newly-allocated object.
////////////////////////////////////////////////////////////////////
P3DObject *P3DSession::
xml_to_object(const TiXmlElement *xvalue) {
P3D_object *P3DSession::
xml_to_p3dobj(const TiXmlElement *xvalue) {
const char *type = xvalue->Attribute("type");
if (strcmp(type, "none") == 0) {
return new P3DNoneObject;
@ -350,6 +350,15 @@ xml_to_object(const TiXmlElement *xvalue) {
return new P3DStringObject(*value);
}
} else if (strcmp(type, "browser") == 0) {
int object_id;
if (xvalue->QueryIntAttribute("object_id", &object_id) == TIXML_SUCCESS) {
P3D_object *obj = (P3D_object *)object_id;
nout << "Found object " << obj << "\n" << flush;
nout << " formatted is " << *obj << "\n" << flush;
return P3D_OBJECT_COPY(obj);
}
} else if (strcmp(type, "python") == 0) {
int object_id;
if (xvalue->QueryIntAttribute("object_id", &object_id) == TIXML_SUCCESS) {
@ -362,14 +371,14 @@ xml_to_object(const TiXmlElement *xvalue) {
}
////////////////////////////////////////////////////////////////////
// Function: P3DSession::object_to_xml
// Function: P3DSession::p3dobj_to_xml
// Access: Public
// Description: Allocates and returns a new XML structure
// corresponding to the indicated value. The supplied
// P3DObject passed in is *not* deleted.
////////////////////////////////////////////////////////////////////
TiXmlElement *P3DSession::
object_to_xml(const P3D_object *obj) {
p3dobj_to_xml(const P3D_object *obj) {
TiXmlElement *xvalue = new TiXmlElement("value");
switch (P3D_OBJECT_GET_TYPE(obj)) {
@ -681,7 +690,11 @@ rt_make_p3d_request(TiXmlElement *xrequest) {
xrequest->Attribute("unique_id", &unique_id);
if (operation != NULL && xobject != NULL) {
P3D_object *object = xml_to_object(xobject);
nout << "xobject = " << *xobject << "\n" << flush;
P3D_object *object = xml_to_p3dobj(xobject);
nout << "converted to " << object << "\n" << flush;
nout << "object = " << *object << "\n" << flush;
nout << "operation = " << *operation << "\n" << flush;
if (strcmp(operation, "get_property") == 0 && property_name != NULL) {
request = new P3D_request;
request->_request_type = P3D_RT_script;

View File

@ -49,8 +49,8 @@ public:
void send_command(TiXmlDocument *command);
TiXmlDocument *command_and_response(TiXmlDocument *command);
P3DObject *xml_to_object(const TiXmlElement *xvalue);
TiXmlElement *object_to_xml(const P3D_object *obj);
P3D_object *xml_to_p3dobj(const TiXmlElement *xvalue);
TiXmlElement *p3dobj_to_xml(const P3D_object *obj);
private:
void install_progress(P3DPackage *package, double progress);

View File

@ -128,7 +128,7 @@ get_property(const string &property) const {
return NULL;
}
P3D_object *object = _instance->variant_to_object(&result);
P3D_object *object = _instance->variant_to_p3dobj(&result);
browser->releasevariantvalue(&result);
return object;
}

View File

@ -499,13 +499,13 @@ get_panda_script_object() {
}
////////////////////////////////////////////////////////////////////
// Function: PPInstance::object_to_variant
// Function: PPInstance::p3dobj_to_variant
// Access: Private
// Description: Converts the indicated P3D_object to the equivalent
// NPVariant, and stores it in result.
////////////////////////////////////////////////////////////////////
void PPInstance::
object_to_variant(NPVariant *result, const P3D_object *object) {
p3dobj_to_variant(NPVariant *result, const P3D_object *object) {
switch (P3D_OBJECT_GET_TYPE(object)) {
case P3D_OT_none:
VOID_TO_NPVARIANT(*result);
@ -542,7 +542,7 @@ object_to_variant(NPVariant *result, const P3D_object *object) {
}
////////////////////////////////////////////////////////////////////
// Function: PPInstance::variant_to_object
// Function: PPInstance::variant_to_p3dobj
// Access: Private
// Description: Converts the indicated NPVariant to the equivalent
// P3D_object, and returns it (newly-allocated). The
@ -550,7 +550,7 @@ object_to_variant(NPVariant *result, const P3D_object *object) {
// later.
////////////////////////////////////////////////////////////////////
P3D_object *PPInstance::
variant_to_object(const NPVariant *variant) {
variant_to_p3dobj(const NPVariant *variant) {
if (NPVARIANT_IS_VOID(*variant) ||
NPVARIANT_IS_NULL(*variant)) {
return P3D_new_none_object();
@ -564,8 +564,7 @@ variant_to_object(const NPVariant *variant) {
NPString str = NPVARIANT_TO_STRING(*variant);
return P3D_new_string_object(str.utf8characters, str.utf8length);
} else if (NPVARIANT_IS_OBJECT(*variant)) {
// TODO.
return P3D_new_none_object();
return new PPBrowserObject(this, NPVARIANT_TO_OBJECT(*variant));
}
// Hmm, none of the above?
@ -826,37 +825,6 @@ show_np_variant(const NPVariant &result) {
}
}
////////////////////////////////////////////////////////////////////
// Function: PPInstance::np_variant_to_object
// Access: Private
// Description: Returns a freshly-allocated P3D_object corresponding
// to the indicated NPVariant.
////////////////////////////////////////////////////////////////////
P3D_object *PPInstance::
np_variant_to_object(const NPVariant &result) {
if (NPVARIANT_IS_NULL(result)) {
return NULL;
} else if (NPVARIANT_IS_VOID(result)) {
return P3D_new_none_object();
} else if (NPVARIANT_IS_BOOLEAN(result)) {
return P3D_new_bool_object(NPVARIANT_TO_BOOLEAN(result));
} else if (NPVARIANT_IS_INT32(result)) {
return P3D_new_int_object(NPVARIANT_TO_INT32(result));
} else if (NPVARIANT_IS_DOUBLE(result)) {
return P3D_new_float_object(NPVARIANT_TO_DOUBLE(result));
} else if (NPVARIANT_IS_STRING(result)) {
NPString str = NPVARIANT_TO_STRING(result);
return P3D_new_string_object(str.utf8characters, str.utf8length);
} else if (NPVARIANT_IS_OBJECT(result)) {
// TODO?
return P3D_new_none_object();
// NPVARIANT_TO_OBJECT(result);
}
// Huh, what is this?
return NULL;
}
#ifdef _WIN32
////////////////////////////////////////////////////////////////////

View File

@ -52,8 +52,8 @@ public:
NPObject *get_panda_script_object();
void object_to_variant(NPVariant *result, const P3D_object *object);
P3D_object *variant_to_object(const NPVariant *variant);
void p3dobj_to_variant(NPVariant *result, const P3D_object *object);
P3D_object *variant_to_p3dobj(const NPVariant *variant);
private:
bool read_contents_file(const string &filename);
@ -65,7 +65,6 @@ private:
void send_window();
void show_np_variant(const NPVariant &result);
P3D_object *np_variant_to_object(const NPVariant &result);
#ifdef _WIN32
static LONG

View File

@ -175,7 +175,7 @@ get_property(NPIdentifier name, NPVariant *result) {
}
// We have the property, and its value is stored in value.
_instance->object_to_variant(result, value);
_instance->p3dobj_to_variant(result, value);
P3D_OBJECT_FINISH(value);
return true;
}
@ -195,7 +195,7 @@ set_property(NPIdentifier name, const NPVariant *value) {
return false;
}
P3D_object *object = _instance->variant_to_object(value);
P3D_object *object = _instance->variant_to_p3dobj(value);
bool result = P3D_OBJECT_SET_PROPERTY(_p3d_object, property_name.c_str(), object);
return result;
}

View File

@ -22,7 +22,7 @@ See pack3d.py for a script that generates these p3d files.
import sys
from direct.showbase import VFSImporter
from direct.showbase.DirectObject import DirectObject
from pandac.PandaModules import VirtualFileSystem, Filename, Multifile, loadPrcFileData, unloadPrcFile, getModelPath, HTTPClient
from pandac.PandaModules import VirtualFileSystem, Filename, Multifile, loadPrcFileData, unloadPrcFile, getModelPath, HTTPClient, Thread
from direct.stdpy import file
from direct.task.TaskManagerGlobal import taskMgr
from direct.showbase import AppRunnerGlobal
@ -155,8 +155,9 @@ class AppRunner(DirectObject):
""" Replaces self.window with the browser's toplevel DOM
object, for controlling the JavaScript and the document in the
same page with the Panda3D plugin. """
print "setBrowserScriptObject(%s)" % (window)
self.window = window
print "setBrowserScriptObject(%s)" % (window)
def setP3DFilename(self, p3dFilename, tokens = [],
instanceId = None):
@ -261,7 +262,8 @@ class AppRunner(DirectObject):
self.requestFunc = func
def sendRequest(self, request, *args):
self.requestFunc(self.instanceId, request, args)
assert self.requestFunc
return self.requestFunc(self.instanceId, request, args)
def windowEvent(self, win):
print "Got window event in runp3d"
@ -271,7 +273,9 @@ class AppRunner(DirectObject):
def scriptRequest(self, operation, object, propertyName = None,
value = None):
""" Issues a new script request to the browser. This queries
or modifies one of the browser's DOM properties.
or modifies one of the browser's DOM properties. This method
blocks until the return value is received from the browser,
and then it returns that value.
operation may be one of [ 'get_property', 'set_property',
'call', 'evaluate' ].
@ -291,10 +295,10 @@ class AppRunner(DirectObject):
self.sendRequest('script', operation, object,
propertyName, value, uniqueId);
def scriptResponse(self, uniqueId, value):
""" Called by the browser in response to a scriptRequest,
above. """
print "Got scriptResponse: %s, %s" % (uniqueId, value)
# Now wait for the response to come in.
result = self.sendRequest('wait_script_response', uniqueId)
print "result for %s.%s = %s" % (object, propertyName, result,)
return result
def parseSysArgs(self):
""" Converts sys.argv into (p3dFilename, tokens). """
@ -333,9 +337,47 @@ class BrowserObject:
actually exists in the plugin host's namespace, e.g. a JavaScript
or DOM object. """
def __init__(self, objectId):
self.__objectId = objectId
def __init__(self, runner, objectId):
self.__dict__['_BrowserObject__runner'] = runner
self.__dict__['_BrowserObject__objectId'] = objectId
def __str__(self):
return "BrowserObject(%s)" % (self.__objectId)
def __nonzero__(self):
return True
def __getattr__(self, name):
""" Remaps attempts to query an attribute into the appropriate
calls to query the actual browser object under the hood. """
print "__getattr_(self, %s)" % (name)
print "runner = %s" % (self.__runner)
value = self.__runner.scriptRequest('get_property', self,
propertyName = name)
return value
# raise AttributeError(name)
def __setattr__(self, name, value):
if name in self.__dict__:
self.__dict__[name] = value
return
value = self.__runner.scriptRequest('set_property', self,
propertyName = name,
value = value)
if not value:
raise AttributeError(name)
def __delattr__(self, name):
if name in self.__dict__:
del self.__dict__[name]
return
value = self.__runner.scriptRequest('del_property', self,
propertyName = name)
if not value:
raise AttributeError(name)
if __name__ == '__main__':
runner = AppRunner()