瀏覽代碼

async JavaScript

David Rose 16 年之前
父節點
當前提交
aa8285f5a4

+ 92 - 55
direct/src/plugin/p3dPythonRun.cxx

@@ -168,11 +168,13 @@ run_python() {
   Py_DECREF(runp3d);
   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[] = {
   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);
   PyObject *p3dpython = Py_InitModule("p3dpython", p3dpython_methods);
   if (p3dpython == NULL) {
   if (p3dpython == NULL) {
@@ -196,11 +198,31 @@ run_python() {
   Py_DECREF(request_func);
   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();
   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);
   task_mgr->add(_check_comm_task);
 
 
+  Py_DECREF(check_comm);
+
   // Finally, get lost in taskMgr.run().
   // Finally, get lost in taskMgr.run().
   PyObject *done = PyObject_CallMethod(_taskMgr, (char *)"run", (char *)"");
   PyObject *done = PyObject_CallMethod(_taskMgr, (char *)"run", (char *)"");
   if (done == NULL) {
   if (done == NULL) {
@@ -216,11 +238,14 @@ run_python() {
 //     Function: P3DPythonRun::handle_command
 //     Function: P3DPythonRun::handle_command
 //       Access: Private
 //       Access: Private
 //  Description: Handles a command received from the plugin host, via
 //  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::
 void P3DPythonRun::
 handle_command(TiXmlDocument *doc) {
 handle_command(TiXmlDocument *doc) {
-  nout << "received: " << *doc << "\n" << flush;
   TiXmlElement *xcommand = doc->FirstChildElement("command");
   TiXmlElement *xcommand = doc->FirstChildElement("command");
   if (xcommand != NULL) {
   if (xcommand != NULL) {
     bool needs_response = false;
     bool needs_response = false;
@@ -281,9 +306,15 @@ handle_command(TiXmlDocument *doc) {
         handle_pyobj_command(xcommand, needs_response, want_response_id);
         handle_pyobj_command(xcommand, needs_response, want_response_id);
 
 
       } else if (strcmp(cmd, "script_response") == 0) {
       } 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) {
       } else if (strcmp(cmd, "drop_pyobj") == 0) {
         int object_id;
         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
 //     Function: P3DPythonRun::check_comm
 //       Access: Private
 //       Access: Private
 //  Description: This method is added to the task manager (via
 //  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
 //               frame.  Its job is to check for commands received
 //               from the plugin host in the parent process.
 //               from the plugin host in the parent process.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void P3DPythonRun::
 void P3DPythonRun::
 check_comm() {
 check_comm() {
-  nout << ":" << flush;
+  //  nout << ":" << flush;
   ACQUIRE_LOCK(_commands_lock);
   ACQUIRE_LOCK(_commands_lock);
   while (!_commands.empty()) {
   while (!_commands.empty()) {
     TiXmlDocument *doc = _commands.front();
     TiXmlDocument *doc = _commands.front();
     _commands.pop_front();
     _commands.pop_front();
-    assert(_commands.size() < 10);
     RELEASE_LOCK(_commands_lock);
     RELEASE_LOCK(_commands_lock);
     handle_command(doc);
     handle_command(doc);
-    delete doc;
     ACQUIRE_LOCK(_commands_lock);
     ACQUIRE_LOCK(_commands_lock);
   }
   }
+  RELEASE_LOCK(_commands_lock);
 
 
   if (!_program_continue) {
   if (!_program_continue) {
     // The low-level thread detected an error, for instance pipe
     // The low-level thread detected an error, for instance pipe
     // closed.  We should exit gracefully.
     // closed.  We should exit gracefully.
     terminate_session();
     terminate_session();
   }
   }
-
-  RELEASE_LOCK(_commands_lock);
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: P3DPythonRun::task_check_comm
+//     Function: P3DPythonRun::st_check_comm
 //       Access: Private, Static
 //       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::
 TiXmlDocument *P3DPythonRun::
 wait_script_response(int response_id) {
 wait_script_response(int response_id) {
-  nout << "waiting script_response " << response_id << "\n" << flush;
+  //  nout << "waiting script_response " << response_id << "\n" << flush;
   while (true) {
   while (true) {
-    ACQUIRE_LOCK(_commands_lock);
-    
     Commands::iterator ci;
     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) {
     for (ci = _commands.begin(); ci != _commands.end(); ++ci) {
       TiXmlDocument *doc = (*ci);
       TiXmlDocument *doc = (*ci);
 
 
       TiXmlElement *xcommand = doc->FirstChildElement("command");
       TiXmlElement *xcommand = doc->FirstChildElement("command");
       if (xcommand != NULL) {
       if (xcommand != NULL) {
         const char *cmd = xcommand->Attribute("cmd");
         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);
           _commands.erase(ci);
           RELEASE_LOCK(_commands_lock);
           RELEASE_LOCK(_commands_lock);
           handle_command(doc);
           handle_command(doc);
-          delete doc;
           ACQUIRE_LOCK(_commands_lock);
           ACQUIRE_LOCK(_commands_lock);
           break;
           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) {
     if (!_program_continue) {
       terminate_session();
       terminate_session();
     }
     }
-    
-    RELEASE_LOCK(_commands_lock);
 
 
 #ifdef _WIN32
 #ifdef _WIN32
     // Make sure we process the Windows event loop while we're
     // 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);
     PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE | PM_NOYIELD);
 #endif  // _WIN32
 #endif  // _WIN32
 
 
-    nout << "." << flush;
+    //    nout << "." << flush;
 
 
     // It hasn't shown up yet.  Give the sub-thread a chance to
     // It hasn't shown up yet.  Give the sub-thread a chance to
     // process the input and append it to the queue.
     // process the input and append it to the queue.

+ 11 - 4
direct/src/plugin/p3dPythonRun.h

@@ -25,9 +25,10 @@
 #include "p3d_lock.h"
 #include "p3d_lock.h"
 #include "handleStream.h"
 #include "handleStream.h"
 #include "p3dCInstance.h"
 #include "p3dCInstance.h"
-#include "genericAsyncTask.h"
+#include "pythonTask.h"
 #include "pmap.h"
 #include "pmap.h"
 #include "pdeque.h"
 #include "pdeque.h"
+#include "pmutex.h"
 #include "get_tinyxml.h"
 #include "get_tinyxml.h"
 
 
 #include <Python.h>
 #include <Python.h>
@@ -75,7 +76,7 @@ private:
   void handle_script_response_command(TiXmlElement *xcommand);
   void handle_script_response_command(TiXmlElement *xcommand);
 
 
   void check_comm();
   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);
   TiXmlDocument *wait_script_response(int response_id);
 
 
   PyObject *py_request_func(PyObject *args);
   PyObject *py_request_func(PyObject *args);
@@ -118,7 +119,7 @@ private:
   PyObject *_browser_object_class;
   PyObject *_browser_object_class;
   PyObject *_taskMgr;
   PyObject *_taskMgr;
 
 
-  PT(GenericAsyncTask) _check_comm_task;
+  PT(PythonTask) _check_comm_task;
 
 
   // This map keeps track of the PyObject pointers we have delivered
   // This map keeps track of the PyObject pointers we have delivered
   // to the parent process.  We have to hold the reference count on
   // to the parent process.  We have to hold the reference count on
@@ -128,8 +129,14 @@ private:
   SentObjects _sent_objects;
   SentObjects _sent_objects;
   int _next_sent_id;
   int _next_sent_id;
 
 
-  // The remaining members are manipulated by the read thread.
   typedef pdeque<TiXmlDocument *> Commands;
   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;
   Commands _commands;
   
   
   // This has to be an actual OS LOCK instead of Panda's Mutex,
   // This has to be an actual OS LOCK instead of Panda's Mutex,

+ 1 - 0
direct/src/showbase/Messenger.py

@@ -311,6 +311,7 @@ class Messenger:
 
 
             if taskChain:
             if taskChain:
                 # Queue the event onto the indicated task chain.
                 # 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)
                 taskMgr.add(self.__lockAndDispatch, name = 'Messenger-%s-%s' % (event, taskChain), extraArgs = [acceptorDict, event, sentArgs, foundWatch], taskChain = taskChain)
             else:
             else:
                 # Handle the event immediately.
                 # Handle the event immediately.

+ 17 - 5
direct/src/showutil/JavaScript.py

@@ -68,12 +68,18 @@ class BrowserObject:
     def __nonzero__(self):
     def __nonzero__(self):
         return True
         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:
         try:
             parentObj, attribName = self.__boundMethod
             parentObj, attribName = self.__boundMethod
             if parentObj:
             if parentObj:
                 # Call it as a method.
                 # Call it as a method.
-                needsResponse = True
                 if parentObj is self.__runner.dom and attribName == 'alert':
                 if parentObj is self.__runner.dom and attribName == 'alert':
                     # As a special hack, we don't wait for the return
                     # As a special hack, we don't wait for the return
                     # value from the alert() call, since this is a
                     # value from the alert() call, since this is a
@@ -98,7 +104,7 @@ class BrowserObject:
                         raise AttributeError
                         raise AttributeError
             else:
             else:
                 # Call it as a plain function.
                 # 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:
         except EnvironmentError:
             # Some odd problem on the call.
             # Some odd problem on the call.
             raise TypeError
             raise TypeError
@@ -208,11 +214,17 @@ class MethodWrapper:
     def __nonzero__(self):
     def __nonzero__(self):
         return True
         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:
         try:
             parentObj, attribName = self.__boundMethod
             parentObj, attribName = self.__boundMethod
             # Call it as a method.
             # Call it as a method.
-            needsResponse = True
             if parentObj is self.__runner.dom and attribName == 'alert':
             if parentObj is self.__runner.dom and attribName == 'alert':
                 # As a special hack, we don't wait for the return
                 # As a special hack, we don't wait for the return
                 # value from the alert() call, since this is a
                 # value from the alert() call, since this is a

+ 15 - 3
direct/src/showutil/runp3d.py

@@ -25,6 +25,7 @@ from direct.showbase.DirectObject import DirectObject
 from pandac.PandaModules import VirtualFileSystem, Filename, Multifile, loadPrcFileData, unloadPrcFile, getModelPath, HTTPClient, Thread, WindowProperties
 from pandac.PandaModules import VirtualFileSystem, Filename, Multifile, loadPrcFileData, unloadPrcFile, getModelPath, HTTPClient, Thread, WindowProperties
 from direct.stdpy import file
 from direct.stdpy import file
 from direct.task.TaskManagerGlobal import taskMgr
 from direct.task.TaskManagerGlobal import taskMgr
+from direct.showbase.MessengerGlobal import messenger
 from direct.showbase import AppRunnerGlobal
 from direct.showbase import AppRunnerGlobal
 
 
 # These imports are read by the C++ wrapper in p3dPythonRun.cxx.
 # These imports are read by the C++ wrapper in p3dPythonRun.cxx.
@@ -98,6 +99,11 @@ class AppRunner(DirectObject):
         if AppRunnerGlobal.appRunner is None:
         if AppRunnerGlobal.appRunner is None:
             AppRunnerGlobal.appRunner = self
             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):
     def setSessionId(self, sessionId):
         """ This message should come in at startup. """
         """ This message should come in at startup. """
         self.sessionId = sessionId
         self.sessionId = sessionId
@@ -167,6 +173,9 @@ class AppRunner(DirectObject):
         if self.gotWindow and self.gotP3DFilename:
         if self.gotWindow and self.gotP3DFilename:
             self.started = True
             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.
             # Hang a hook so we know when the window is actually opened.
             self.acceptOnce('window-event', self.windowEvent)
             self.acceptOnce('window-event', self.windowEvent)
 
 
@@ -265,7 +274,8 @@ class AppRunner(DirectObject):
 
 
         self.gotP3DFilename = True
         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):
     def clearWindowPrc(self):
         """ Clears the windowPrc file that was created in a previous
         """ Clears the windowPrc file that was created in a previous
@@ -325,7 +335,9 @@ class AppRunner(DirectObject):
         self.windowPrc = loadPrcFileData("setupWindow", data)
         self.windowPrc = loadPrcFileData("setupWindow", data)
 
 
         self.gotWindow = True
         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):
     def setRequestFunc(self, func):
         """ This method is called by the plugin at startup to supply a
         """ This method is called by the plugin at startup to supply a
@@ -468,4 +480,4 @@ if __name__ == '__main__':
     except ArgumentError, e:
     except ArgumentError, e:
         print e.args[0]
         print e.args[0]
         sys.exit(1)
         sys.exit(1)
-    run()
+    taskMgr.run()