Browse Source

support python in a child process, multiple pythons now possible

David Rose 16 years ago
parent
commit
10d51dafb6

+ 2 - 0
direct/src/plugin/Sources.pp

@@ -37,6 +37,8 @@
     handleStream.cxx handleStream.h handleStream.I \
     handleStreamBuf.cxx handleStreamBuf.h \
     p3d_lock.h \
+    p3dCInstance.cxx \
+    p3dCInstance.h p3dCInstance.I \
     p3dPythonRun.cxx p3dPythonRun.h p3dPythonRun.I
 
 #end bin_target

+ 7 - 5
direct/src/plugin/handleStreamBuf.cxx

@@ -258,7 +258,7 @@ read_chars(char *start, size_t length) {
   BOOL success = ReadFile(_handle, start, length, &bytes_read, NULL);
   if (!success) {
     DWORD error = GetLastError();
-    if (error != ERROR_HANDLE_EOF) {
+    if (error != ERROR_HANDLE_EOF && error != ERROR_BROKEN_PIPE) {
       cerr
         << "Error reading " << length
         << " bytes, windows error code 0x" << hex
@@ -311,10 +311,12 @@ write_chars(const char *start, size_t length) {
   BOOL success = WriteFile(_handle, start, length, &bytes_written, NULL);
   if (!success) {
     DWORD error = GetLastError();
-    cerr
-      << "Error writing " << length
-      << " bytes, windows error code 0x" << hex
-      << error << dec << ".\n";
+    if (error != ERROR_BROKEN_PIPE) {
+      cerr
+        << "Error writing " << length
+        << " bytes, windows error code 0x" << hex
+        << error << dec << ".\n";
+    }
     return bytes_written;
   }
   assert(bytes_written == length);

+ 3 - 3
direct/src/plugin/p3dInstance.I

@@ -19,7 +19,7 @@
 //  Description: Returns the p3d filename that was passed to the
 //               constructor.
 ////////////////////////////////////////////////////////////////////
-INLINE const string &P3DInstance::
+inline const string &P3DInstance::
 get_p3d_filename() const {
   return _p3d_filename;
 }
@@ -35,7 +35,7 @@ get_p3d_filename() const {
 //               each unique session required for different
 //               P3DInstances.
 ////////////////////////////////////////////////////////////////////
-INLINE const string &P3DInstance::
+inline const string &P3DInstance::
 get_session_key() const {
   return _session_key;
 }
@@ -46,7 +46,7 @@ get_session_key() const {
 //  Description: Returns a string that uniquely identifies this
 //               instance's required Python version.
 ////////////////////////////////////////////////////////////////////
-INLINE const string &P3DInstance::
+inline const string &P3DInstance::
 get_python_version() const {
   return _python_version;
 }

+ 46 - 1
direct/src/plugin/p3dInstance.cxx

@@ -38,7 +38,6 @@ P3DInstance(P3D_request_ready_func *func,
   _parent_window(parent_window)
 {
   P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
-  cerr << "creating instance\n";
 
   INIT_LOCK(_request_lock);
 
@@ -192,6 +191,51 @@ feed_url_stream(int unique_id,
                 size_t this_data_size) {
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: P3DInstance::make_xml
+//       Access: Public
+//  Description: Returns a newly-allocated XML structure that
+//               corresponds to the data within this instance.
+////////////////////////////////////////////////////////////////////
+TiXmlElement *P3DInstance::
+make_xml() {
+  TiXmlElement *xinstance = new TiXmlElement("instance");
+  xinstance->SetAttribute("p3d_filename", _p3d_filename);
+
+  switch (_window_type) {
+  case P3D_WT_embedded:
+    xinstance->SetAttribute("window_type", "embedded");
+    xinstance->SetAttribute("win_x", _win_x);
+    xinstance->SetAttribute("win_y", _win_y);
+    xinstance->SetAttribute("win_width", _win_width);
+    xinstance->SetAttribute("win_height", _win_height);
+#ifdef _WIN32
+    xinstance->SetAttribute("parent_hwnd", (int)_parent_window._hwnd);
+#endif
+    break;
+
+  case P3D_WT_toplevel:
+    xinstance->SetAttribute("window_type", "toplevel");
+    xinstance->SetAttribute("win_x", _win_x);
+    xinstance->SetAttribute("win_y", _win_y);
+    xinstance->SetAttribute("win_width", _win_width);
+    xinstance->SetAttribute("win_height", _win_height);
+    break;
+
+  case P3D_WT_fullscreen:
+    xinstance->SetAttribute("window_type", "fullscreen");
+    xinstance->SetAttribute("win_width", _win_width);
+    xinstance->SetAttribute("win_height", _win_height);
+    break;
+
+  case P3D_WT_hidden:
+    xinstance->SetAttribute("window_type", "hidden");
+    break;
+  }
+
+  return xinstance;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: P3DInstance::fill_tokens
 //       Access: Private
@@ -211,3 +255,4 @@ fill_tokens(const P3D_token *tokens[], size_t tokens_size) {
     _tokens.push_back(token);
   }
 }
+

+ 8 - 4
direct/src/plugin/p3dInstance.h

@@ -19,12 +19,14 @@
 
 #include <vector>
 #include <deque>
+#include <tinyxml.h>
 
 class P3DSession;
 
 ////////////////////////////////////////////////////////////////////
 //       Class : P3DInstance
-// Description : This is an instance of a Panda3D window.
+// Description : This is an instance of a Panda3D window, as seen in
+//               the parent-level process.
 ////////////////////////////////////////////////////////////////////
 class P3DInstance : public P3D_instance {
 public:
@@ -53,9 +55,11 @@ public:
                        const unsigned char *this_data, 
                        size_t this_data_size);
 
-  INLINE const string &get_p3d_filename() const;
-  INLINE const string &get_session_key() const;
-  INLINE const string &get_python_version() const;
+  inline const string &get_p3d_filename() const;
+  inline const string &get_session_key() const;
+  inline const string &get_python_version() const;
+
+  TiXmlElement *make_xml();
 
 private:
   void fill_tokens(const P3D_token *tokens[], size_t tokens_size);

+ 68 - 11
direct/src/plugin/p3dPythonRun.cxx

@@ -24,6 +24,18 @@ P3DPythonRun(int argc, char *argv[]) {
   _read_thread_alive = false;
   INIT_LOCK(_commands_lock);
 
+  _program_name = argv[0];
+  _py_argc = 1;
+  _py_argv = (char **)malloc(2 * sizeof(char *));
+  _py_argv[0] = argv[0];
+  _py_argv[1] = NULL;
+
+  // Initialize Python.  It appears to be important to do this before
+  // we open the pipe streams and spawn the thread, below.
+  Py_SetProgramName((char *)_program_name.c_str());
+  Py_Initialize();
+  PySys_SetArgv(_py_argc, _py_argv);
+
   // Open the pipe streams with the input and output handles from the
   // parent.
 #ifdef _WIN32
@@ -35,6 +47,7 @@ P3DPythonRun(int argc, char *argv[]) {
   if (!SetStdHandle(STD_OUTPUT_HANDLE, INVALID_HANDLE_VALUE)) {
     cerr << "unable to reset input handle\n";
   }
+
   _pipe_read.open_read(read);
   _pipe_write.open_write(write);
 #endif  // _WIN32
@@ -46,17 +59,6 @@ P3DPythonRun(int argc, char *argv[]) {
   }
 
   spawn_read_thread();
-
-  _program_name = argv[0];
-  _py_argc = 1;
-  _py_argv = (char **)malloc(2 * sizeof(char *));
-  _py_argv[0] = argv[0];
-  _py_argv[1] = NULL;
-
-  // Initialize Python.
-  Py_SetProgramName((char *)_program_name.c_str());
-  Py_Initialize();
-  PySys_SetArgv(_py_argc, _py_argv);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -172,6 +174,12 @@ run_python() {
 void P3DPythonRun::
 handle_command(TiXmlDocument *doc) {
   cerr << "got command: " << *doc << "\n";
+  TiXmlHandle h(doc);
+  TiXmlElement *xinstance = h.FirstChild("instance").ToElement();
+  if (xinstance != (TiXmlElement *)NULL) {
+    P3DCInstance *inst = new P3DCInstance(xinstance);
+    start_instance(inst);
+  }    
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -255,6 +263,53 @@ join_read_thread() {
   cerr << "done waiting for thread\n";
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPythonRun::start_instance
+//       Access: Private
+//  Description: Starts the indicated instance running within the
+//               Python process.
+////////////////////////////////////////////////////////////////////
+void P3DPythonRun::
+start_instance(P3DCInstance *inst) {
+  cerr << "starting instance " << inst->get_p3d_filename() << "\n";
+  _instances.push_back(inst);
+
+  string window_type;
+  switch (inst->_window_type) {
+  case P3D_WT_embedded:
+    window_type = "embedded";
+    break;
+  case P3D_WT_toplevel:
+    window_type = "toplevel";
+    break;
+  case P3D_WT_fullscreen:
+    window_type = "fullscreen";
+    break;
+  case P3D_WT_hidden:
+    window_type = "hidden";
+    break;
+  }
+
+  PyObject *result = PyObject_CallFunction
+    (_setupWindow, "siiiii", window_type.c_str(),
+     inst->_win_x, inst->_win_y,
+     inst->_win_width, inst->_win_height,
+#ifdef _WIN32
+     (int)(inst->_parent_window._hwnd)
+#endif
+     );
+  if (result == NULL) {
+    PyErr_Print();
+  }
+  Py_XDECREF(result);
+  
+  result = PyObject_CallFunction(_runPackedApp, "[s]", inst->get_p3d_filename().c_str());
+  if (result == NULL) {
+    PyErr_Print();
+  }
+  Py_XDECREF(result);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: P3DPythonRun::rt_thread_run
 //       Access: Private
@@ -262,8 +317,10 @@ join_read_thread() {
 ////////////////////////////////////////////////////////////////////
 void P3DPythonRun::
 rt_thread_run() {
+  cerr << "thread reading.\n";
   while (_read_thread_alive) {
     TiXmlDocument *doc = new TiXmlDocument;
+
     _pipe_read >> *doc;
     if (!_pipe_read || _pipe_read.eof()) {
       // Some error on reading.  Abort.

+ 15 - 7
direct/src/plugin/p3dPythonRun.h

@@ -18,11 +18,13 @@
 #include <iostream>
 #include <string>
 #include <deque>
+#include <vector>
 #include <assert.h>
 #include <Python.h>
 #include <tinyxml.h>
 #include "p3d_lock.h"
 #include "handleStream.h"
+#include "p3dCInstance.h"
 
 #ifdef _WIN32
 #include <windows.h>
@@ -60,6 +62,8 @@ private:
   void spawn_read_thread();
   void join_read_thread();
 
+  void start_instance(P3DCInstance *inst);
+
 private:
   // This method runs only within the read thread.
   void rt_thread_run();
@@ -68,6 +72,17 @@ private:
 #endif
 
 private:
+  typedef vector<P3DCInstance *> Instances;
+  Instances _instances;
+
+  string _program_name;
+  int _py_argc;
+  char **_py_argv;
+
+  PyObject *_runPackedApp;
+  PyObject *_setupWindow;
+
+  // The remaining members are manipulated by the read thread.
   typedef deque<TiXmlDocument *> Commands;
   Commands _commands;
   LOCK _commands_lock;
@@ -79,13 +94,6 @@ private:
 #ifdef _WIN32
   HANDLE _read_thread;
 #endif
-
-  string _program_name;
-  int _py_argc;
-  char **_py_argv;
-
-  PyObject *_runPackedApp;
-  PyObject *_setupWindow;
 };
 
 #include "p3dPythonRun.I"

+ 34 - 23
direct/src/plugin/p3dSession.cxx

@@ -43,6 +43,9 @@ P3DSession(P3DInstance *inst) {
   // environment, if they are set.
   const char *keep[] = {
     "TEMP", "HOME", "USER", 
+#ifdef _WIN32
+    "SYSTEMROOT",
+#endif
     NULL
   };
   for (int ki = 0; keep[ki] != NULL; ++ki) {
@@ -66,36 +69,43 @@ P3DSession(P3DInstance *inst) {
 
   // Create a bi-directional pipe to communicate with the sub-process.
 #ifdef _WIN32
-  cerr << "creating pipe\n";
-  HANDLE orig_stdin = GetStdHandle(STD_INPUT_HANDLE);
-  HANDLE orig_stdout = GetStdHandle(STD_OUTPUT_HANDLE);
-
-  SECURITY_ATTRIBUTES inheritable;
-  inheritable.nLength = sizeof(inheritable);
-  inheritable.lpSecurityDescriptor = NULL;
-  inheritable.bInheritHandle = true;
-
   HANDLE r_to, w_to, r_from, w_from;
 
   // Create the pipe to the process.
-  if (!CreatePipe(&r_to, &w_to, &inheritable, 0)) {
+  if (!CreatePipe(&r_to, &w_to, NULL, 0)) {
     cerr << "failed to create pipe\n";
   } else {
-    SetStdHandle(STD_INPUT_HANDLE, r_to);
+    // Make sure the right end of the pipe is inheritable.
+    SetHandleInformation(r_to, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
+    SetHandleInformation(w_to, HANDLE_FLAG_INHERIT, 0);
   }
 
   // Create the pipe from the process.
-  if (!CreatePipe(&r_from, &w_from, &inheritable, 0)) {
+  if (!CreatePipe(&r_from, &w_from, NULL, 0)) {
     cerr << "failed to create pipe\n";
-  } else {
-    SetStdHandle(STD_OUTPUT_HANDLE, w_from);
+  } else { 
+    // Make sure the right end of the pipe is inheritable.
+    SetHandleInformation(w_from, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
+    SetHandleInformation(r_from, HANDLE_FLAG_INHERIT, 0);
   }
 
-  cerr << "creating process " << p3dpython << "\n";
+  // Make sure we see an error dialog if there is a missing DLL.
+  SetErrorMode(0);
+
+  // Pass the appropriate ends of the bi-directional pipe as the
+  // standard input and standard output of the child process.
+  STARTUPINFO startup_info;
+  ZeroMemory(&startup_info, sizeof(STARTUPINFO));
+  startup_info.cb = sizeof(startup_info); 
+  startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE);
+  startup_info.hStdOutput = w_from;
+  startup_info.hStdInput = r_to;
+  startup_info.dwFlags |= STARTF_USESTDHANDLES;
+ 
   BOOL result = CreateProcess
     (p3dpython.c_str(), NULL, NULL, NULL, TRUE, 0,
-     /* (void *)env.c_str() */NULL, _python_root_dir.c_str(), NULL, &_p3dpython);
-  cerr << "result = " << result << "\n";
+     (void *)env.c_str(), _python_root_dir.c_str(),
+     &startup_info, &_p3dpython);
   _started_p3dpython = (result != 0);
 
   if (!_started_p3dpython) {
@@ -104,10 +114,9 @@ P3DSession(P3DInstance *inst) {
     cerr << "Created process: " << _p3dpython.dwProcessId << "\n";
   }
 
-  CloseHandle(r_to);
+  // Close the pipe handles that are now owned by the child.
   CloseHandle(w_from);
-  SetStdHandle(STD_INPUT_HANDLE, orig_stdin);
-  SetStdHandle(STD_OUTPUT_HANDLE, orig_stdout);
+  CloseHandle(r_to);
 
   _pipe_read.open_read(r_from);
   _pipe_write.open_write(w_to);
@@ -162,11 +171,13 @@ start_instance(P3DInstance *inst) {
 
   TiXmlDocument doc;
   TiXmlDeclaration *decl = new TiXmlDeclaration("1.0", "", "");
-  TiXmlElement *element = new TiXmlElement("instance");
+  TiXmlElement *xinstance = inst->make_xml();
+  
   doc.LinkEndChild(decl);
-  doc.LinkEndChild(element);
+  doc.LinkEndChild(xinstance);
 
-  _pipe_write << doc;
+  cerr << "sending: " << doc << "\n";
+  _pipe_write << doc << flush;
 
   /*
   P3DPython *python = inst_mgr->start_python(_python_version);

+ 50 - 12
direct/src/plugin/panda3d.cxx

@@ -16,6 +16,7 @@
 
 #include <iostream>
 #include <string>
+#include <math.h>
 
 #ifdef _WIN32
 #include "wingetopt.h"
@@ -301,26 +302,63 @@ main(int argc, char *argv[]) {
     return 1;
   }
 
+  int num_instances = argc - 1;
+
   P3D_window_handle parent_window;
   if (window_type == P3D_WT_embedded) {
     // The user asked for an embedded window.  Create a toplevel
     // window to be its parent, of the requested size.
+    if (win_width == 0 && win_height == 0) {
+      win_width = 640;
+      win_height = 480;
+    }
+
     make_parent_window(parent_window, win_width, win_height);
+    
+    // Center the child window(s) within the parent window.
+    RECT rect;
+    GetClientRect(parent_window._hwnd, &rect);
+
+    win_x = (int)(rect.right * 0.1);
+    win_y = (int)(rect.bottom * 0.1);
+    win_width = (int)(rect.right * 0.8);
+    win_height = (int)(rect.bottom * 0.8);
+
+    // Subdivide the window into num_x_spans * num_y_spans sub-windows.
+    int num_y_spans = int(sqrt((double)num_instances));
+    int num_x_spans = (num_instances + num_y_spans - 1) / num_y_spans;
+    
+    int inst_width = win_width / num_x_spans;
+    int inst_height = win_height / num_y_spans;
+
+    for (int yi = 0; yi < num_y_spans; ++yi) {
+      for (int xi = 0; xi < num_x_spans; ++xi) {
+        int i = yi * num_x_spans + xi;
+        if (i >= num_instances) {
+          continue;
+        }
+
+        // Create instance i at window slot (xi, yi).
+        int inst_x = win_x + xi * inst_width;
+        int inst_y = win_y + yi * inst_height;
+
+        P3D_create_instance
+          (NULL, argv[i + 1], 
+           P3D_WT_embedded, inst_x, inst_y, inst_width, inst_height, parent_window,
+           NULL, 0);
+      }
+    }
 
-    // And center the graphics window within that parent window.
-    win_x = (int)(win_width * 0.1);
-    win_y = (int)(win_height * 0.1);
-    win_width = (int)(win_width * 0.8);
-    win_height = (int)(win_height * 0.8);
+  } else {
+    // Not an embedded window.  Create each window with the same parameters.
+    for (int i = 0; i < num_instances; ++i) {
+      P3D_create_instance
+        (NULL, argv[i + 1], 
+         window_type, win_x, win_y, win_width, win_height, parent_window,
+         NULL, 0);
+    }
   }
 
-  // For now, only one instance at a time is supported.  Ignore the
-  // remaining command-line parameters.
-  P3D_create_instance
-    (NULL, argv[1], 
-     window_type, win_x, win_y, win_width, win_height, parent_window,
-     NULL, 0);
-
 #ifdef _WIN32
   // Wait for new messages from Windows, and new requests from the
   // plugin.