From 4d2ffd8e77e5a1440ce47f3fe5995a1c50b67646 Mon Sep 17 00:00:00 2001 From: David Rose Date: Thu, 9 Jul 2009 23:59:17 +0000 Subject: [PATCH] much better object model --- direct/src/plugin/Sources.pp | 2 + direct/src/plugin/p3dInstance.cxx | 463 +++++++++++--------- direct/src/plugin/p3dInstance.h | 29 +- direct/src/plugin/p3dInstanceManager.cxx | 7 +- direct/src/plugin/p3dPythonObject.cxx | 9 +- direct/src/plugin/p3dPythonRun.cxx | 25 ++ direct/src/plugin/p3dReferenceCount.I | 72 +++ direct/src/plugin/p3dReferenceCount.cxx | 15 + direct/src/plugin/p3dReferenceCount.h | 44 ++ direct/src/plugin/p3dSession.cxx | 191 ++++---- direct/src/plugin/p3dSession.h | 11 +- direct/src/plugin/p3d_plugin.h | 23 - direct/src/plugin/p3d_plugin_composite1.cxx | 1 + direct/src/showutil/runp3d.py | 17 +- 14 files changed, 567 insertions(+), 342 deletions(-) create mode 100644 direct/src/plugin/p3dReferenceCount.I create mode 100644 direct/src/plugin/p3dReferenceCount.cxx create mode 100644 direct/src/plugin/p3dReferenceCount.h diff --git a/direct/src/plugin/Sources.pp b/direct/src/plugin/Sources.pp index 9d49ea4343..62c4ac107a 100644 --- a/direct/src/plugin/Sources.pp +++ b/direct/src/plugin/Sources.pp @@ -40,6 +40,7 @@ p3dObject.h p3dObject.I \ p3dPackage.h p3dPackage.I \ p3dPythonObject.h \ + p3dReferenceCount.h p3dReferenceCount.I \ p3dSession.h p3dSession.I \ p3dSplashWindow.h p3dSplashWindow.I \ p3dStringObject.h \ @@ -64,6 +65,7 @@ p3dObject.cxx \ p3dPackage.cxx \ p3dPythonObject.cxx \ + p3dReferenceCount.cxx \ p3dSession.cxx \ p3dSplashWindow.cxx \ p3dStringObject.cxx \ diff --git a/direct/src/plugin/p3dInstance.cxx b/direct/src/plugin/p3dInstance.cxx index d6111add3f..a3c2f38541 100644 --- a/direct/src/plugin/p3dInstance.cxx +++ b/direct/src/plugin/p3dInstance.cxx @@ -71,6 +71,9 @@ P3DInstance(P3D_request_ready_func *func, void *user_data) : //////////////////////////////////////////////////////////////////// P3DInstance:: ~P3DInstance() { + // TODO: maybe this should be a reference-counted object, so we + // don't delete it too soon. + assert(_session == NULL); P3D_OBJECT_XDECREF(_browser_script_object); @@ -90,7 +93,8 @@ P3DInstance:: _splash_window = NULL; } - // TODO: empty _pending_requests queue and _downloads map. + // TODO: empty _raw_requests and _baked_requests queues, and + // _downloads map. // TODO: Is it possible for someone to delete an instance while a // download is still running? Who will crash when this happens? @@ -239,10 +243,7 @@ set_browser_script_object(P3D_object *browser_script_object) { //////////////////////////////////////////////////////////////////// bool P3DInstance:: has_request() { - ACQUIRE_LOCK(_request_lock); - bool any_requests = !_pending_requests.empty(); - RELEASE_LOCK(_request_lock); - return any_requests; + return _request_pending; } //////////////////////////////////////////////////////////////////// @@ -256,52 +257,105 @@ has_request() { //////////////////////////////////////////////////////////////////// P3D_request *P3DInstance:: get_request() { - P3D_request *result = NULL; - ACQUIRE_LOCK(_request_lock); - if (!_pending_requests.empty()) { - result = _pending_requests.front(); - _pending_requests.pop_front(); - _request_pending = !_pending_requests.empty(); - } - RELEASE_LOCK(_request_lock); - - if (result != NULL) { - switch (result->_request_type) { - case P3D_RT_notify: - handle_notify_request(result); - break; - - case P3D_RT_script: - handle_script_request(result); - // Completely eat this request; don't return it to the caller. - finish_request(result, true); - return get_request(); - - default: - // Other kinds of requests don't require special handling at - // this level; pass it up unmolested. - break; - } + bake_requests(); + if (_baked_requests.empty()) { + // No requests ready. + _request_pending = false; + return NULL; } - return result; + P3D_request *request = _baked_requests.front(); + _baked_requests.pop_front(); + _request_pending = !_baked_requests.empty(); + + return request; } //////////////////////////////////////////////////////////////////// -// Function: P3DInstance::add_request +// Function: P3DInstance::bake_requests // Access: Public -// Description: May be called in any thread to add a new P3D_request -// to the pending_request queue for this instance. +// Description: Copies requests from the _raw_requests queue, which +// is built up in one or more sub-threads, into the +// _baked_requests queue, which is publicly presented +// to the browser. Along the way, some requests (like +// script requests) are handled immediately. +// +// At the end of this call, _baked_requests will contain +// the current set of requests pending for the browser. +// +// This method should only be called in the main thread. //////////////////////////////////////////////////////////////////// void P3DInstance:: -add_request(P3D_request *request) { - request->_instance = this; +bake_requests() { + while (true) { + // Get the latest request from the read thread. + TiXmlDocument *doc = NULL; + ACQUIRE_LOCK(_request_lock); + if (!_raw_requests.empty()) { + doc = _raw_requests.front(); + _raw_requests.pop_front(); + } + RELEASE_LOCK(_request_lock); + + if (doc == NULL) { + // No more requests to process right now. + return; + } + nout << "received: " << *doc << "\n" << flush; + + // Now we've got a request in XML form; convert it to P3D_request + // form. + TiXmlElement *xrequest = doc->FirstChildElement("request"); + assert(xrequest != (TiXmlElement *)NULL); + P3D_request *request = make_p3d_request(xrequest); + delete doc; + + if (request != NULL) { + _baked_requests.push_back(request); + } + } +} +//////////////////////////////////////////////////////////////////// +// Function: P3DInstance::add_raw_request +// Access: Public +// Description: May be called in any thread to add a new XML request +// to the pending_request queue for this instance. The +// XML document will be deleted when the request is +// eventually handled. +//////////////////////////////////////////////////////////////////// +void P3DInstance:: +add_raw_request(TiXmlDocument *doc) { ACQUIRE_LOCK(_request_lock); - _pending_requests.push_back(request); + _raw_requests.push_back(doc); _request_pending = true; RELEASE_LOCK(_request_lock); + // We don't decode the XML yet, since we might be running in any + // thread here. We'll decode it in the main thread, where it's + // safe. + + // Tell the world we've got a new request. + P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr(); + inst_mgr->signal_request_ready(this); + _session->signal_request_ready(this); +} + +//////////////////////////////////////////////////////////////////// +// Function: P3DInstance::add_baked_request +// Access: Public +// Description: May be called in the main thread only to add a new +// request to the baked_request queue. This request +// queue is directly passed on the browser without +// further processing at this level. +//////////////////////////////////////////////////////////////////// +void P3DInstance:: +add_baked_request(P3D_request *request) { + request->_instance = this; + + _baked_requests.push_back(request); + _request_pending = true; + // Tell the world we've got a new request. P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr(); inst_mgr->signal_request_ready(this); @@ -335,18 +389,6 @@ finish_request(P3D_request *request, bool handled) { case P3D_RT_notify: free((char *)request->_request._notify._message); break; - - case P3D_RT_script: - { - P3D_OBJECT_DECREF(request->_request._script._object); - if (request->_request._script._property_name != NULL) { - free((char *)request->_request._script._property_name); - } - for (int i = 0; i < request->_request._script._num_values; ++i) { - P3D_OBJECT_DECREF(request->_request._script._values[i]); - } - } - break; } delete request; @@ -430,7 +472,7 @@ start_download(P3DDownload *download) { request->_request._get_url._url = strdup(download->get_url().c_str()); request->_request._get_url._unique_id = download_id; - add_request(request); + add_baked_request(request); } //////////////////////////////////////////////////////////////////// @@ -446,7 +488,7 @@ request_stop() { _requested_stop = true; P3D_request *request = new P3D_request; request->_request_type = P3D_RT_stop; - add_request(request); + add_baked_request(request); } } @@ -497,6 +539,93 @@ send_browser_script_object() { _session->send_command(doc); } +//////////////////////////////////////////////////////////////////// +// Function: P3DInstance::make_p3d_request +// Access: Private +// Description: Creates a new P3D_request structure from the XML. +// Returns NULL if no request is needed. +//////////////////////////////////////////////////////////////////// +P3D_request *P3DInstance:: +make_p3d_request(TiXmlElement *xrequest) { + P3D_request *request = NULL; + + const char *rtype = xrequest->Attribute("rtype"); + if (rtype != NULL) { + if (strcmp(rtype, "notify") == 0) { + const char *message = xrequest->Attribute("message"); + if (message != NULL) { + request = new P3D_request; + request->_request_type = P3D_RT_notify; + request->_request._notify._message = strdup(message); + } + + } else if (strcmp(rtype, "script") == 0) { + // We don't actually build a P3D_request for a script request; + // we always just handle it immediately. + const char *operation = xrequest->Attribute("operation"); + TiXmlElement *xobject = xrequest->FirstChildElement("object"); + const char *property_name = xrequest->Attribute("property_name"); + int needs_response = 0; + xrequest->Attribute("needs_response", &needs_response); + int unique_id = 0; + xrequest->Attribute("unique_id", &unique_id); + + vector values; + TiXmlElement *xvalue = xrequest->FirstChildElement("value"); + while (xvalue != NULL) { + P3D_object *value = _session->xml_to_p3dobj(xvalue); + values.push_back(value); + xvalue = xvalue->NextSiblingElement("value"); + } + + P3D_object **values_ptr = NULL; + if (!values.empty()) { + values_ptr = &values[0]; + } + + if (operation != NULL && xobject != NULL) { + P3D_object *object = _session->xml_to_p3dobj(xobject); + + if (property_name == NULL) { + property_name = ""; + } + + handle_script_request(operation, object, property_name, + values_ptr, values.size(), + (needs_response != 0), unique_id); + } + + for (size_t i = 0; i < values.size(); ++i) { + P3D_OBJECT_DECREF(values[i]); + } + + } else if (strcmp(rtype, "notify") == 0) { + const char *message = xrequest->Attribute("message"); + if (message != NULL) { + request = new P3D_request; + request->_request_type = P3D_RT_notify; + request->_request._notify._message = strdup(message); + handle_notify_request(message); + } + + } else if (strcmp(rtype, "drop_p3dobj") == 0) { + int object_id; + if (xrequest->QueryIntAttribute("object_id", &object_id) == TIXML_SUCCESS) { + // We no longer need to keep this reference. + _session->drop_p3dobj(object_id); + } + + } else { + nout << "Ignoring request of type " << rtype << "\n"; + } + } + + if (request != NULL) { + request->_instance = this; + } + return request; +} + //////////////////////////////////////////////////////////////////// // Function: P3DInstance::handle_notify_request // Access: Private @@ -504,13 +633,10 @@ send_browser_script_object() { // received from the subprocess. //////////////////////////////////////////////////////////////////// void P3DInstance:: -handle_notify_request(P3D_request *request) { - assert(request->_request_type == P3D_RT_notify); - +handle_notify_request(const string &message) { // We look for certain notify events that have particular meaning // to this instance. - const char *message = request->_request._notify._message; - if (strcmp(message, "onwindowopen") == 0) { + if (message == "onwindowopen") { // The process told us that it just succesfully opened its // window. Tear down the splash window. _instance_window_opened = true; @@ -528,172 +654,87 @@ handle_notify_request(P3D_request *request) { // received from the subprocess. //////////////////////////////////////////////////////////////////// void P3DInstance:: -handle_script_request(P3D_request *request) { - assert(request->_request_type == P3D_RT_script); +handle_script_request(const string &operation, P3D_object *object, + const string &property_name, + P3D_object *values[], int num_values, + bool needs_response, int unique_id) { - P3D_object *object = request->_request._script._object; - bool needs_response = request->_request._script._needs_response; - int unique_id = request->_request._script._unique_id; - switch (request->_request._script._op) { - case P3D_SO_get_property: - { - P3D_object *result = P3D_OBJECT_GET_PROPERTY(object, request->_request._script._property_name); + TiXmlDocument *doc = new TiXmlDocument; + TiXmlDeclaration *decl = new TiXmlDeclaration("1.0", "utf-8", ""); + TiXmlElement *xcommand = new TiXmlElement("command"); + xcommand->SetAttribute("cmd", "script_response"); + xcommand->SetAttribute("unique_id", unique_id); + + doc->LinkEndChild(decl); + doc->LinkEndChild(xcommand); - // We've got the property value; feed it back down to the - // subprocess. - TiXmlDocument *doc = new TiXmlDocument; - TiXmlDeclaration *decl = new TiXmlDeclaration("1.0", "utf-8", ""); - TiXmlElement *xcommand = new TiXmlElement("command"); - xcommand->SetAttribute("cmd", "script_response"); - xcommand->SetAttribute("unique_id", unique_id); - - doc->LinkEndChild(decl); - doc->LinkEndChild(xcommand); - if (result != NULL) { - xcommand->LinkEndChild(_session->p3dobj_to_xml(result)); - P3D_OBJECT_DECREF(result); - } + if (operation == "get_property") { + P3D_object *result = P3D_OBJECT_GET_PROPERTY(object, property_name.c_str()); - if (needs_response) { - _session->send_command(doc); - } else { - delete doc; - } + // We've got the property value; feed it back down to the + // subprocess. + + if (result != NULL) { + xcommand->LinkEndChild(_session->p3dobj_to_xml(result)); + P3D_OBJECT_DECREF(result); } - break; - case P3D_SO_set_property: - { - bool result; - if (request->_request._script._num_values == 1) { - result = P3D_OBJECT_SET_PROPERTY(object, request->_request._script._property_name, - request->_request._script._values[0]); - } else { - // Wrong number of values. Error. - result = false; - } - - // Feed the result back down to the subprocess. - TiXmlDocument *doc = new TiXmlDocument; - TiXmlDeclaration *decl = new TiXmlDeclaration("1.0", "utf-8", ""); - TiXmlElement *xcommand = new TiXmlElement("command"); - xcommand->SetAttribute("cmd", "script_response"); - xcommand->SetAttribute("unique_id", unique_id); - - doc->LinkEndChild(decl); - doc->LinkEndChild(xcommand); - - TiXmlElement *xvalue = new TiXmlElement("value"); - xvalue->SetAttribute("type", "bool"); - xvalue->SetAttribute("value", (int)result); - xcommand->LinkEndChild(xvalue); - - if (needs_response) { - _session->send_command(doc); - } else { - delete doc; - } + } else if (operation == "set_property") { + bool result; + if (num_values == 1) { + result = P3D_OBJECT_SET_PROPERTY(object, property_name.c_str(), values[0]); + } else { + // Wrong number of values. Error. + result = false; } - break; + + TiXmlElement *xvalue = new TiXmlElement("value"); + xvalue->SetAttribute("type", "bool"); + xvalue->SetAttribute("value", (int)result); + xcommand->LinkEndChild(xvalue); - case P3D_SO_del_property: - { - bool result = P3D_OBJECT_SET_PROPERTY(object, request->_request._script._property_name, - NULL); + } else if (operation == "del_property") { + bool result = P3D_OBJECT_SET_PROPERTY(object, property_name.c_str(), NULL); + + TiXmlElement *xvalue = new TiXmlElement("value"); + xvalue->SetAttribute("type", "bool"); + xvalue->SetAttribute("value", (int)result); + xcommand->LinkEndChild(xvalue); - // Feed the result back down to the subprocess. - TiXmlDocument *doc = new TiXmlDocument; - TiXmlDeclaration *decl = new TiXmlDeclaration("1.0", "utf-8", ""); - TiXmlElement *xcommand = new TiXmlElement("command"); - xcommand->SetAttribute("cmd", "script_response"); - xcommand->SetAttribute("unique_id", unique_id); - - doc->LinkEndChild(decl); - doc->LinkEndChild(xcommand); - - TiXmlElement *xvalue = new TiXmlElement("value"); - xvalue->SetAttribute("type", "bool"); - xvalue->SetAttribute("value", (int)result); - xcommand->LinkEndChild(xvalue); - - if (needs_response) { - _session->send_command(doc); - } else { - delete doc; - } + } else if (operation == "call") { + P3D_object *result = + P3D_OBJECT_CALL(object, property_name.c_str(), values, num_values); + + if (result != NULL) { + xcommand->LinkEndChild(_session->p3dobj_to_xml(result)); + P3D_OBJECT_DECREF(result); } - break; - case P3D_SO_call: - { - P3D_object *result = - P3D_OBJECT_CALL(object, request->_request._script._property_name, - request->_request._script._values, - request->_request._script._num_values); - - // Feed the result back down to the subprocess. - TiXmlDocument *doc = new TiXmlDocument; - TiXmlDeclaration *decl = new TiXmlDeclaration("1.0", "utf-8", ""); - TiXmlElement *xcommand = new TiXmlElement("command"); - xcommand->SetAttribute("cmd", "script_response"); - xcommand->SetAttribute("unique_id", unique_id); - - doc->LinkEndChild(decl); - doc->LinkEndChild(xcommand); - - if (result != NULL) { - xcommand->LinkEndChild(_session->p3dobj_to_xml(result)); - P3D_OBJECT_DECREF(result); - } - - if (needs_response) { - _session->send_command(doc); - } else { - delete doc; - } + } else if (operation == "eval") { + P3D_object *result; + if (num_values == 1) { + P3D_object *expression = values[0]; + int size = P3D_OBJECT_GET_STRING(expression, NULL, 0); + char *buffer = new char[size + 1]; + P3D_OBJECT_GET_STRING(expression, buffer, size + 1); + result = P3D_OBJECT_EVAL(object, buffer); + logfile << " eval " << *object << ": " << buffer << ", result = " << result << "\n"; + delete[] buffer; + } else { + // Wrong number of values. Error. + result = NULL; } - break; - - case P3D_SO_eval: - { - P3D_object *result; - if (request->_request._script._num_values == 1) { - P3D_object *expression = request->_request._script._values[0]; - int size = P3D_OBJECT_GET_STRING(expression, NULL, 0); - char *buffer = new char[size + 1]; - P3D_OBJECT_GET_STRING(expression, buffer, size + 1); - result = P3D_OBJECT_EVAL(object, buffer); - logfile << " eval " << *object << ": " << buffer << ", result = " << result << "\n"; - delete[] buffer; - } else { - // Wrong number of values. Error. - result = NULL; - } - - // Feed the result back down to the subprocess. - TiXmlDocument *doc = new TiXmlDocument; - TiXmlDeclaration *decl = new TiXmlDeclaration("1.0", "utf-8", ""); - TiXmlElement *xcommand = new TiXmlElement("command"); - xcommand->SetAttribute("cmd", "script_response"); - xcommand->SetAttribute("unique_id", unique_id); - - doc->LinkEndChild(decl); - doc->LinkEndChild(xcommand); - - if (result != NULL) { - xcommand->LinkEndChild(_session->p3dobj_to_xml(result)); - P3D_OBJECT_DECREF(result); - } - - logfile << "eval response: " << *doc << "\n"; - - if (needs_response) { - _session->send_command(doc); - } else { - delete doc; - } + + if (result != NULL) { + xcommand->LinkEndChild(_session->p3dobj_to_xml(result)); + P3D_OBJECT_DECREF(result); } - break; + } + + if (needs_response) { + _session->send_command(doc); + } else { + delete doc; } } diff --git a/direct/src/plugin/p3dInstance.h b/direct/src/plugin/p3dInstance.h index 9c41b66ec2..e2dc0d8bee 100644 --- a/direct/src/plugin/p3dInstance.h +++ b/direct/src/plugin/p3dInstance.h @@ -19,6 +19,7 @@ #include "p3dFileDownload.h" #include "p3dFileParams.h" #include "p3dWindowParams.h" +#include "p3dReferenceCount.h" #include "get_tinyxml.h" #include @@ -35,7 +36,7 @@ class P3DObject; // Description : This is an instance of a Panda3D window, as seen in // the parent-level process. //////////////////////////////////////////////////////////////////// -class P3DInstance : public P3D_instance { +class P3DInstance : public P3D_instance, public P3DReferenceCount { public: P3DInstance(P3D_request_ready_func *func, void *user_data); ~P3DInstance(); @@ -51,7 +52,9 @@ public: bool has_request(); P3D_request *get_request(); - void add_request(P3D_request *request); + void bake_requests(); + void add_raw_request(TiXmlDocument *doc); + void add_baked_request(P3D_request *request); void finish_request(P3D_request *request, bool handled); bool feed_url_stream(int unique_id, @@ -88,8 +91,12 @@ private: }; void send_browser_script_object(); - void handle_notify_request(P3D_request *request); - void handle_script_request(P3D_request *request); + P3D_request *make_p3d_request(TiXmlElement *xrequest); + void handle_notify_request(const string &message); + void handle_script_request(const string &operation, P3D_object *object, + const string &property_name, + P3D_object *values[], int num_values, + bool needs_response, int unique_id); void make_splash_window(); void install_progress(P3DPackage *package, double progress); @@ -105,7 +112,10 @@ private: int _instance_id; string _session_key; string _python_version; + + // Not ref-counted: session is the parent. P3DSession *_session; + P3DSplashWindow *_splash_window; bool _instance_window_opened; @@ -115,11 +125,18 @@ private: typedef map Downloads; Downloads _downloads; + // The _raw_requests queue might be filled up by the read thread, so + // we protect it in a lock. LOCK _request_lock; - typedef deque Requests; - Requests _pending_requests; + typedef deque RawRequests; + RawRequests _raw_requests; bool _requested_stop; + // The _baked_requests queue is only touched in the main thread; no + // lock needed. + typedef deque BakedRequests; + BakedRequests _baked_requests; + static int _next_instance_id; friend class P3DSession; diff --git a/direct/src/plugin/p3dInstanceManager.cxx b/direct/src/plugin/p3dInstanceManager.cxx index aba9b679c4..e998427d29 100644 --- a/direct/src/plugin/p3dInstanceManager.cxx +++ b/direct/src/plugin/p3dInstanceManager.cxx @@ -138,6 +138,7 @@ initialize() { P3DInstance *P3DInstanceManager:: create_instance(P3D_request_ready_func *func, void *user_data) { P3DInstance *inst = new P3DInstance(func, user_data); + inst->ref(); _instances.insert(inst); return inst; @@ -164,6 +165,7 @@ start_instance(P3DInstance *inst, const string &p3d_filename, Sessions::iterator si = _sessions.find(inst->get_session_key()); if (si == _sessions.end()) { session = new P3DSession(inst); + session->ref(); bool inserted = _sessions.insert(Sessions::value_type(session->get_session_key(), session)).second; assert(inserted); } else { @@ -197,10 +199,11 @@ finish_instance(P3DInstance *inst) { // session. if (session->get_num_instances() == 0) { _sessions.erase(session->get_session_key()); - delete session; + session->shutdown(); + unref_delete(session); } - delete inst; + unref_delete(inst); } //////////////////////////////////////////////////////////////////// diff --git a/direct/src/plugin/p3dPythonObject.cxx b/direct/src/plugin/p3dPythonObject.cxx index e2fa28e17e..e3a041dfc6 100644 --- a/direct/src/plugin/p3dPythonObject.cxx +++ b/direct/src/plugin/p3dPythonObject.cxx @@ -24,6 +24,8 @@ P3DPythonObject(P3DSession *session, int object_id) : _session(session), _object_id(object_id) { + _session->ref(); + nout << "new P3DPythonObject " << this << " : " << object_id << "\n" << flush; } //////////////////////////////////////////////////////////////////// @@ -33,7 +35,12 @@ P3DPythonObject(P3DSession *session, int object_id) : //////////////////////////////////////////////////////////////////// P3DPythonObject:: ~P3DPythonObject() { - // TODO. + nout << "del P3DPythonObject " << this << " : " << _object_id << "\n"; + // When the P3DPythonObject wrapper goes away, we have to inform the + // child process that we no longer need the corresponding PyObject + // to be kept around. + _session->drop_pyobj(_object_id); + unref_delete(_session); } //////////////////////////////////////////////////////////////////// diff --git a/direct/src/plugin/p3dPythonRun.cxx b/direct/src/plugin/p3dPythonRun.cxx index e5ac75ba61..ffa894fd3f 100755 --- a/direct/src/plugin/p3dPythonRun.cxx +++ b/direct/src/plugin/p3dPythonRun.cxx @@ -259,6 +259,18 @@ handle_command(TiXmlDocument *doc) { // Response from a script request. assert(!needs_response); nout << "Ignoring unexpected script_response\n"; + + } else if (strcmp(cmd, "drop_pyobj") == 0) { + int object_id; + if (xcommand->QueryIntAttribute("object_id", &object_id) == TIXML_SUCCESS) { + nout << "got drop_pyobj(" << object_id << ")\n" << flush; + SentObjects::iterator si = _sent_objects.find(object_id); + if (si != _sent_objects.end()) { + PyObject *obj = (*si).second; + Py_DECREF(obj); + _sent_objects.erase(si); + } + } } else { nout << "Unhandled command " << cmd << "\n"; @@ -666,6 +678,19 @@ py_request_func(PyObject *args) { nout << "sent: " << doc << "\n" << flush; _pipe_write << doc << flush; + } else if (strcmp(request_type, "drop_p3dobj") == 0) { + // Release a particular P3D_object that we were holding a + // reference to. + int object_id; + if (!PyArg_ParseTuple(extra_args, "i", &object_id)) { + return NULL; + } + nout << "got drop_p3dobj(" << object_id << ")\n" << flush; + + xrequest->SetAttribute("object_id", object_id); + nout << "sent: " << doc << "\n" << flush; + _pipe_write << doc << flush; + } else { string message = string("Unsupported request type: ") + string(request_type); PyErr_SetString(PyExc_ValueError, message.c_str()); diff --git a/direct/src/plugin/p3dReferenceCount.I b/direct/src/plugin/p3dReferenceCount.I new file mode 100644 index 0000000000..51d6fdbe5d --- /dev/null +++ b/direct/src/plugin/p3dReferenceCount.I @@ -0,0 +1,72 @@ +// Filename: p3dReferenceCount.I +// Created by: drose (09Jul09) +// +//////////////////////////////////////////////////////////////////// +// +// 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." +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// Function: P3DReferenceCount::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +inline P3DReferenceCount:: +P3DReferenceCount() { + _ref_count = 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: P3DReferenceCount::Destructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +inline P3DReferenceCount:: +~P3DReferenceCount() { + assert(_ref_count == 0); +} + +//////////////////////////////////////////////////////////////////// +// Function: P3DReferenceCount::ref +// Access: Public +// Description: Explicitly increments the reference count. +//////////////////////////////////////////////////////////////////// +inline void P3DReferenceCount:: +ref() const { + ++((P3DReferenceCount *)this)->_ref_count; +} + +//////////////////////////////////////////////////////////////////// +// Function: P3DReferenceCount::unref +// Access: Published, Virtual +// Description: Explicitly decrements the reference count. Usually, +// you should call unref_delete() instead. +// +// The return value is true if the new reference count +// is nonzero, false if it is zero. +//////////////////////////////////////////////////////////////////// +inline bool P3DReferenceCount:: +unref() const { + return --(((P3DReferenceCount *)this)->_ref_count) != 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: unref_delete +// Description: This global helper function will unref the given +// P3DReferenceCount object, and if the reference count +// reaches zero, automatically delete it. +//////////////////////////////////////////////////////////////////// +template +inline void +unref_delete(RefCountType *ptr) { + if (!ptr->unref()) { + delete ptr; + } +} diff --git a/direct/src/plugin/p3dReferenceCount.cxx b/direct/src/plugin/p3dReferenceCount.cxx new file mode 100644 index 0000000000..5975dcbe29 --- /dev/null +++ b/direct/src/plugin/p3dReferenceCount.cxx @@ -0,0 +1,15 @@ +// Filename: p3dReferenceCount.cxx +// Created by: drose (09Jul09) +// +//////////////////////////////////////////////////////////////////// +// +// 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." +// +//////////////////////////////////////////////////////////////////// + +#include "p3dReferenceCount.h" diff --git a/direct/src/plugin/p3dReferenceCount.h b/direct/src/plugin/p3dReferenceCount.h new file mode 100644 index 0000000000..20e84491fc --- /dev/null +++ b/direct/src/plugin/p3dReferenceCount.h @@ -0,0 +1,44 @@ +// Filename: p3dReferenceCount.h +// Created by: drose (09Jul09) +// +//////////////////////////////////////////////////////////////////// +// +// 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." +// +//////////////////////////////////////////////////////////////////// + +#ifndef P3DREFERENCECOUNT_H +#define P3DREFERENCECOUNT_H + +#include "p3d_plugin_common.h" + +//////////////////////////////////////////////////////////////////// +// Class : P3DReferenceCount +// Description : A base class for reference-counted objects in this +// module. We follow the Panda convention, rather than +// the Pythong convention: the reference count of a new +// object is initially 0. +//////////////////////////////////////////////////////////////////// +class P3DReferenceCount { +public: + inline P3DReferenceCount(); + inline ~P3DReferenceCount(); + + inline void ref() const; + inline bool unref() const; + +private: + int _ref_count; +}; + +template +inline void unref_delete(RefCountType *ptr); + +#include "p3dReferenceCount.I" + +#endif diff --git a/direct/src/plugin/p3dSession.cxx b/direct/src/plugin/p3dSession.cxx index b6897dc3db..f8568fc309 100644 --- a/direct/src/plugin/p3dSession.cxx +++ b/direct/src/plugin/p3dSession.cxx @@ -69,14 +69,26 @@ P3DSession(P3DInstance *inst) { //////////////////////////////////////////////////////////////////// // Function: P3DSession::Destructor // Access: Public -// Description: Terminates the session by shutting down Python and -// stopping the subprocess. +// Description: //////////////////////////////////////////////////////////////////// P3DSession:: ~P3DSession() { + assert(!_p3dpython_running); + DESTROY_LOCK(_instances_lock); +} + +//////////////////////////////////////////////////////////////////// +// Function: P3DSession::shutdown +// Access: Public +// Description: Terminates the session by shutting down Python and +// stopping the subprocess. +//////////////////////////////////////////////////////////////////// +void P3DSession:: +shutdown() { if (_panda3d_callback != NULL) { _panda3d->cancel_callback(_panda3d_callback); delete _panda3d_callback; + _panda3d_callback = NULL; } if (_p3dpython_running) { @@ -106,12 +118,13 @@ P3DSession:: } CloseHandle(_p3dpython_handle); - #else // _WIN32 // TODO: posix kill(). #endif // _WIN32 + + _p3dpython_running = false; } // If there are any leftover commands in the queue (presumably @@ -124,7 +137,6 @@ P3DSession:: _commands.clear(); join_read_thread(); - DESTROY_LOCK(_instances_lock); } //////////////////////////////////////////////////////////////////// @@ -143,6 +155,7 @@ start_instance(P3DInstance *inst) { assert(inst->get_session_key() == _session_key); assert(inst->get_python_version() == _python_version); + inst->ref(); ACQUIRE_LOCK(_instances_lock); inst->_session = this; bool inserted = _instances.insert(Instances::value_type(inst->get_instance_id(), inst)).second; @@ -200,6 +213,7 @@ terminate_instance(P3DInstance *inst) { _instances.erase(inst->get_instance_id()); } RELEASE_LOCK(_instances_lock); + unref_delete(inst); } //////////////////////////////////////////////////////////////////// @@ -284,6 +298,16 @@ command_and_response(TiXmlDocument *command) { return NULL; } + // Make sure we bake requests while we are waiting, to process + // recursive script requests. (The child process might have to + // wait for us to process some of these before it can fulfill the + // command we're actually waiting for.) + Instances::iterator ii; + for (ii = _instances.begin(); ii != _instances.end(); ++ii) { + P3DInstance *inst = (*ii).second; + inst->bake_requests(); + } + #ifdef _WIN32 // Make sure we process the Windows event loop while we're // waiting, or everything that depends on Windows messages--in @@ -370,6 +394,7 @@ xml_to_p3dobj(const TiXmlElement *xvalue) { } P3D_object *obj = (*si).second; + P3D_OBJECT_INCREF(obj); return obj; } @@ -472,6 +497,69 @@ p3dobj_to_xml(P3D_object *obj) { return xvalue; } +//////////////////////////////////////////////////////////////////// +// Function: P3DSession::signal_request_ready +// Access: Public +// Description: May be called in any thread to indicate that a new +// P3D_request is available in the indicated instance. +//////////////////////////////////////////////////////////////////// +void P3DSession:: +signal_request_ready(P3DInstance *inst) { + // Since a new request might require baking, we should wake up a + // blocked command_and_request() process, so the main thread can go + // back and bake the new request. + + // Technically, a response isn't really ready now, but we still need + // the main thread to wake up and look around for a bit. + _response_ready.acquire(); + _response_ready.notify(); + _response_ready.release(); +} + +//////////////////////////////////////////////////////////////////// +// Function: P3DSession::drop_pyobj +// Access: Public +// Description: If the session is still active, issues the command to +// the child process to release the indicated PyObject +// from its table. This is intended to be called +// strictly by the P3DPythonObject destructor. +//////////////////////////////////////////////////////////////////// +void P3DSession:: +drop_pyobj(int object_id) { + nout << "got drop_pyobj(" << object_id << ")\n" << flush; + if (_p3dpython_running) { + TiXmlDocument doc; + TiXmlDeclaration *decl = new TiXmlDeclaration("1.0", "utf-8", ""); + TiXmlElement *xcommand = new TiXmlElement("command"); + xcommand->SetAttribute("cmd", "drop_pyobj"); + xcommand->SetAttribute("object_id", object_id); + doc.LinkEndChild(decl); + doc.LinkEndChild(xcommand); + nout << "sent: " << doc << "\n" << flush; + _pipe_write << doc << flush; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: P3DSession::drop_p3dobj +// Access: Public +// Description: Responds to a drop_p3dobj message from the child +// process indicating that a particular P3D_object is no +// longer being used by the child. This removes the +// corresponding P3D_object from our tables and +// decrements its reference count. +//////////////////////////////////////////////////////////////////// +void P3DSession:: +drop_p3dobj(int object_id) { + nout << "got drop_p3dobj(" << object_id << ")\n" << flush; + SentObjects::iterator si = _sent_objects.find(object_id); + if (si != _sent_objects.end()) { + P3D_object *obj = (*si).second; + P3D_OBJECT_DECREF(obj); + _sent_objects.erase(si); + } +} + //////////////////////////////////////////////////////////////////// // Function: P3DSession::install_progress // Access: Private @@ -676,103 +764,16 @@ rt_handle_request(TiXmlDocument *doc) { ii = _instances.find(instance_id); if (ii != _instances.end()) { P3DInstance *inst = (*ii).second; - P3D_request *request = rt_make_p3d_request(xrequest); - if (request != NULL) { - inst->add_request(request); - } + inst->add_raw_request(doc); + doc = NULL; } RELEASE_LOCK(_instances_lock); } } - delete doc; -} - -//////////////////////////////////////////////////////////////////// -// Function: P3DSession::rt_make_p3d_request -// Access: Private -// Description: Creates a new P3D_request structure from the XML. -//////////////////////////////////////////////////////////////////// -P3D_request *P3DSession:: -rt_make_p3d_request(TiXmlElement *xrequest) { - P3D_request *request = NULL; - - const char *rtype = xrequest->Attribute("rtype"); - if (rtype != NULL) { - if (strcmp(rtype, "notify") == 0) { - const char *message = xrequest->Attribute("message"); - if (message != NULL) { - request = new P3D_request; - request->_request_type = P3D_RT_notify; - request->_request._notify._message = strdup(message); - } - - } else if (strcmp(rtype, "script") == 0) { - const char *operation = xrequest->Attribute("operation"); - TiXmlElement *xobject = xrequest->FirstChildElement("object"); - const char *property_name = xrequest->Attribute("property_name"); - int needs_response = 0; - xrequest->Attribute("needs_response", &needs_response); - int unique_id = 0; - xrequest->Attribute("unique_id", &unique_id); - - if (operation != NULL && xobject != NULL) { - P3D_script_operation op; - if (strcmp(operation, "get_property") == 0) { - op = P3D_SO_get_property; - } else if (strcmp(operation, "set_property") == 0) { - op = P3D_SO_set_property; - } else if (strcmp(operation, "del_property") == 0) { - op = P3D_SO_del_property; - } else if (strcmp(operation, "call") == 0) { - op = P3D_SO_call; - } else if (strcmp(operation, "eval") == 0) { - op = P3D_SO_eval; - } else { - // An unexpected operation. - return NULL; - } - - request = new P3D_request; - request->_request_type = P3D_RT_script; - request->_request._script._op = op; - request->_request._script._object = xml_to_p3dobj(xobject); - request->_request._script._property_name = NULL; - if (property_name != NULL) { - request->_request._script._property_name = strdup(property_name); - } - request->_request._script._needs_response = (needs_response != 0); - request->_request._script._unique_id = unique_id; - - // Fill in the value(s). - vector values; - TiXmlElement *xvalue = xrequest->FirstChildElement("value"); - while (xvalue != NULL) { - P3D_object *value = xml_to_p3dobj(xvalue); - values.push_back(value); - xvalue = xvalue->NextSiblingElement("value"); - } - - if (values.empty()) { - // No values. - request->_request._script._values = NULL; - request->_request._script._num_values = 0; - } else { - // Some values. - int num_values = (int)values.size(); - P3D_object **valuesp = new P3D_object *[num_values]; - memcpy(valuesp, &values[0], num_values * sizeof(P3D_object *)); - request->_request._script._values = valuesp; - request->_request._script._num_values = num_values; - } - } - - } else { - nout << "Ignoring request of type " << rtype << "\n"; - } + if (doc != NULL) { + delete doc; } - - return request; } //////////////////////////////////////////////////////////////////// diff --git a/direct/src/plugin/p3dSession.h b/direct/src/plugin/p3dSession.h index d6af610262..45d096af62 100644 --- a/direct/src/plugin/p3dSession.h +++ b/direct/src/plugin/p3dSession.h @@ -19,6 +19,7 @@ #include "handleStream.h" #include "p3dPackage.h" #include "p3dConditionVar.h" +#include "p3dReferenceCount.h" #include "get_tinyxml.h" #include @@ -34,11 +35,13 @@ class P3DProgressWindow; // might include one or more P3DInstance objects running // in the same memory space with each other. //////////////////////////////////////////////////////////////////// -class P3DSession { +class P3DSession : public P3DReferenceCount { public: P3DSession(P3DInstance *inst); ~P3DSession(); + void shutdown(); + inline const string &get_session_key() const; inline const string &get_python_version() const; @@ -52,6 +55,11 @@ public: P3D_object *xml_to_p3dobj(const TiXmlElement *xvalue); TiXmlElement *p3dobj_to_xml(P3D_object *obj); + void signal_request_ready(P3DInstance *inst); + + void drop_pyobj(int object_id); + void drop_p3dobj(int object_id); + private: void install_progress(P3DPackage *package, double progress); void start_p3dpython(); @@ -65,7 +73,6 @@ private: void rt_thread_run(); void rt_terminate(); void rt_handle_request(TiXmlDocument *doc); - P3D_request *rt_make_p3d_request(TiXmlElement *xrequest); #ifdef _WIN32 static HANDLE diff --git a/direct/src/plugin/p3d_plugin.h b/direct/src/plugin/p3d_plugin.h index 04bc2fbd36..102ca31bd0 100644 --- a/direct/src/plugin/p3d_plugin.h +++ b/direct/src/plugin/p3d_plugin.h @@ -580,7 +580,6 @@ typedef enum { P3D_RT_get_url, P3D_RT_post_url, P3D_RT_notify, - P3D_RT_script, } P3D_request_type; /* Structures corresponding to the request types in the above enum. */ @@ -624,27 +623,6 @@ typedef struct { const char *_message; } P3D_request_notify; -/* A script object request. This is used to call out into the - browser_script_object (above). This request is handled internally - by the core API, and may safely be ignored by the host. -*/ -typedef enum { - P3D_SO_get_property, - P3D_SO_set_property, - P3D_SO_del_property, - P3D_SO_call, - P3D_SO_eval, -} P3D_script_operation; -typedef struct { - P3D_object *_object; - P3D_script_operation _op; - const char *_property_name; - P3D_object **_values; - int _num_values; - bool _needs_response; - int _unique_id; -} P3D_request_script; - /* This is the overall structure that represents a single request. It is returned by P3D_instance_get_request(). */ typedef struct { @@ -655,7 +633,6 @@ typedef struct { P3D_request_get_url _get_url; P3D_request_post_url _post_url; P3D_request_notify _notify; - P3D_request_script _script; } _request; } P3D_request; diff --git a/direct/src/plugin/p3d_plugin_composite1.cxx b/direct/src/plugin/p3d_plugin_composite1.cxx index e52eb158f1..2b30539ca2 100644 --- a/direct/src/plugin/p3d_plugin_composite1.cxx +++ b/direct/src/plugin/p3d_plugin_composite1.cxx @@ -13,6 +13,7 @@ #include "p3dObject.cxx" #include "p3dPackage.cxx" #include "p3dPythonObject.cxx" +#include "p3dReferenceCount.cxx" #include "p3dSession.cxx" #include "p3dSplashWindow.cxx" #include "p3dStringObject.cxx" diff --git a/direct/src/showutil/runp3d.py b/direct/src/showutil/runp3d.py index c068ce359a..8e5a932eec 100755 --- a/direct/src/showutil/runp3d.py +++ b/direct/src/showutil/runp3d.py @@ -377,13 +377,20 @@ class AppRunner(DirectObject): uniqueId = self.nextScriptId self.nextScriptId += 1 self.sendRequest('script', operation, object, - propertyName, value, needsResponse, uniqueId); + propertyName, value, needsResponse, uniqueId) if needsResponse: # Now wait for the response to come in. result = self.sendRequest('wait_script_response', uniqueId) return result + def dropObject(self, objectId): + """ Inform the parent process that we no longer have an + interest in the P3D_object corresponding to the indicated + objectId. """ + + self.sendRequest('drop_p3dobj', objectId) + def parseSysArgs(self): """ Converts sys.argv into (p3dFilename, tokens). """ import getopt @@ -444,6 +451,12 @@ class BrowserObject: # the object to its parent. self.__dict__['_BrowserObject__boundMethod'] = (None, None) + def __del__(self): + # When the BrowserObject destructs, tell the parent process it + # doesn't need to keep around its corresponding P3D_object any + # more. + self.__runner.dropObject(self.__objectId) + def __str__(self): parentObj, attribName = self.__boundMethod if parentObj: @@ -546,7 +559,7 @@ class BrowserObject: else: raise IndexError(key) - return value; + return value def __setitem__(self, key, value): result = self.__runner.scriptRequest('set_property', self,