mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-04 02:42:49 -04:00
1056 lines
35 KiB
C++
1056 lines
35 KiB
C++
// Filename: p3dSession.cxx
|
|
// Created by: drose (03Jun09)
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
//
|
|
// 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 "p3dSession.h"
|
|
#include "p3dInstance.h"
|
|
#include "p3dInstanceManager.h"
|
|
#include "p3d_plugin_config.h"
|
|
#include "p3dUndefinedObject.h"
|
|
#include "p3dNoneObject.h"
|
|
#include "p3dBoolObject.h"
|
|
#include "p3dIntObject.h"
|
|
#include "p3dFloatObject.h"
|
|
#include "p3dPythonObject.h"
|
|
|
|
#ifndef _WIN32
|
|
#include <fcntl.h>
|
|
#endif
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DSession::Constructor
|
|
// Access: Public
|
|
// Description: Creates a new session, corresponding to a new
|
|
// subprocess with its own copy of Python. The initial
|
|
// parameters for the session are taken from the
|
|
// indicated instance object (but the instance itself is
|
|
// not automatically started within the session).
|
|
////////////////////////////////////////////////////////////////////
|
|
P3DSession::
|
|
P3DSession(P3DInstance *inst) {
|
|
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
|
_session_id = inst_mgr->get_unique_id();
|
|
_session_key = inst->get_session_key();
|
|
_python_version = inst->get_python_version();
|
|
|
|
_p3dpython_running = false;
|
|
_response = NULL;
|
|
_got_response_id = -1;
|
|
|
|
_started_read_thread = false;
|
|
_read_thread_continue = false;
|
|
|
|
_output_filename = inst->get_fparams().lookup_token("output_filename");
|
|
|
|
_panda3d_callback = NULL;
|
|
|
|
INIT_LOCK(_instances_lock);
|
|
INIT_THREAD(_read_thread);
|
|
|
|
_panda3d = inst_mgr->get_package("panda3d", "dev", "Panda3D");
|
|
_python_root_dir = _panda3d->get_package_dir();
|
|
inst->add_package(_panda3d);
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DSession::Destructor
|
|
// Access: Public
|
|
// 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) {
|
|
// Tell the process we're going away.
|
|
TiXmlDocument doc;
|
|
TiXmlDeclaration *decl = new TiXmlDeclaration("1.0", "utf-8", "");
|
|
TiXmlElement *xcommand = new TiXmlElement("command");
|
|
xcommand->SetAttribute("cmd", "exit");
|
|
doc.LinkEndChild(decl);
|
|
doc.LinkEndChild(xcommand);
|
|
nout << "sent: " << doc << "\n" << flush;
|
|
_pipe_write << doc << flush;
|
|
|
|
// Also close the pipe, to help underscore the point.
|
|
_pipe_write.close();
|
|
|
|
// Closing _pipe_read before the thread has stopped can result in
|
|
// a hang. Don't need to close it yet.
|
|
// _pipe_read.close();
|
|
|
|
#ifdef _WIN32
|
|
// Now give the process a chance to terminate itself cleanly.
|
|
if (WaitForSingleObject(_p3dpython_handle, 2000) == WAIT_TIMEOUT) {
|
|
// It didn't shut down cleanly, so kill it the hard way.
|
|
nout << "Terminating process.\n" << flush;
|
|
TerminateProcess(_p3dpython_handle, 2);
|
|
}
|
|
|
|
CloseHandle(_p3dpython_handle);
|
|
#else // _WIN32
|
|
|
|
// TODO: posix kill().
|
|
|
|
#endif // _WIN32
|
|
|
|
_p3dpython_running = false;
|
|
}
|
|
|
|
// If there are any leftover commands in the queue (presumably
|
|
// implying we have never started the python process), then delete
|
|
// them now, unsent.
|
|
Commands::iterator ci;
|
|
for (ci = _commands.begin(); ci != _commands.end(); ++ci) {
|
|
delete (*ci);
|
|
}
|
|
_commands.clear();
|
|
|
|
join_read_thread();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DSession::start_instance
|
|
// Access: Public
|
|
// Description: Adds the indicated instance to the session, and
|
|
// starts it running. It is an error if the instance
|
|
// has been started anywhere else.
|
|
//
|
|
// The instance must have the same session_key as the
|
|
// one that was passed to the P3DSession constructor.
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DSession::
|
|
start_instance(P3DInstance *inst) {
|
|
assert(inst->_session == NULL);
|
|
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;
|
|
RELEASE_LOCK(_instances_lock);
|
|
assert(inserted);
|
|
|
|
TiXmlDocument *doc = new TiXmlDocument;
|
|
TiXmlDeclaration *decl = new TiXmlDeclaration("1.0", "utf-8", "");
|
|
TiXmlElement *xcommand = new TiXmlElement("command");
|
|
xcommand->SetAttribute("cmd", "start_instance");
|
|
TiXmlElement *xinstance = inst->make_xml();
|
|
|
|
doc->LinkEndChild(decl);
|
|
doc->LinkEndChild(xcommand);
|
|
xcommand->LinkEndChild(xinstance);
|
|
|
|
send_command(doc);
|
|
inst->send_browser_script_object();
|
|
|
|
if (_panda3d->get_ready()) {
|
|
// If it's ready immediately, go ahead and start.
|
|
start_p3dpython();
|
|
} else {
|
|
// Otherwise, set a callback, so we'll know when it is ready.
|
|
if (_panda3d_callback == NULL) {
|
|
_panda3d_callback = new PackageCallback(this);
|
|
_panda3d->set_callback(_panda3d_callback);
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DSession::terminate_instance
|
|
// Access: Public
|
|
// Description: Removes the indicated instance from the session, and
|
|
// stops it. It is an error if the instance is not
|
|
// already running on this session.
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DSession::
|
|
terminate_instance(P3DInstance *inst) {
|
|
TiXmlDocument *doc = new TiXmlDocument;
|
|
TiXmlDeclaration *decl = new TiXmlDeclaration("1.0", "utf-8", "");
|
|
TiXmlElement *xcommand = new TiXmlElement("command");
|
|
xcommand->SetAttribute("cmd", "terminate_instance");
|
|
xcommand->SetAttribute("instance_id", inst->get_instance_id());
|
|
|
|
doc->LinkEndChild(decl);
|
|
doc->LinkEndChild(xcommand);
|
|
|
|
send_command(doc);
|
|
|
|
ACQUIRE_LOCK(_instances_lock);
|
|
if (inst->_session == this) {
|
|
inst->_session = NULL;
|
|
_instances.erase(inst->get_instance_id());
|
|
}
|
|
RELEASE_LOCK(_instances_lock);
|
|
unref_delete(inst);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DSession::send_command
|
|
// Access: Public
|
|
// Description: Sends the indicated command to the running Python
|
|
// process. If the process has not yet been started,
|
|
// queues it up until it is ready.
|
|
//
|
|
// The command must be a newly-allocated TiXmlDocument;
|
|
// it will be deleted after it has been delivered to the
|
|
// process.
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DSession::
|
|
send_command(TiXmlDocument *command) {
|
|
if (_p3dpython_running) {
|
|
// Python is running. Send the command.
|
|
nout << "sent: " << *command << "\n" << flush;
|
|
_pipe_write << *command << flush;
|
|
delete command;
|
|
} else {
|
|
// Python not yet running. Queue up the command instead.
|
|
_commands.push_back(command);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DSession::command_and_response
|
|
// Access: Public
|
|
// Description: Sends the indicated command to the running Python
|
|
// process, and waits for a response. Returns the
|
|
// newly-allocated response on success, or NULL on
|
|
// failure.
|
|
//
|
|
// The command must be a newly-allocated TiXmlDocument;
|
|
// it will be deleted after it has been delivered to the
|
|
// process.
|
|
//
|
|
// This will fail if the python process is not running
|
|
// or if it suddenly stops.
|
|
////////////////////////////////////////////////////////////////////
|
|
TiXmlDocument *P3DSession::
|
|
command_and_response(TiXmlDocument *command) {
|
|
if (!_p3dpython_running) {
|
|
return NULL;
|
|
}
|
|
|
|
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
|
int response_id = inst_mgr->get_unique_id();
|
|
|
|
// Add the "want_response_id" attribute to the toplevel command, so
|
|
// the sub-process knows we'll be waiting for its response.
|
|
TiXmlElement *xcommand = command->FirstChildElement("command");
|
|
assert(xcommand != NULL);
|
|
xcommand->SetAttribute("want_response_id", response_id);
|
|
|
|
nout << "sent: " << *command << "\n" << flush;
|
|
_pipe_write << *command << flush;
|
|
delete command;
|
|
|
|
// Now block, waiting for a response to be delivered. We assume
|
|
// only one thread will be waiting at a time.
|
|
nout << "waiting for response " << response_id << "\n" << flush;
|
|
_response_ready.acquire();
|
|
while (_response == NULL || _got_response_id != response_id) {
|
|
if (_response != NULL) {
|
|
// This is a bogus response. Since we're the only thread waiting,
|
|
// it follows that no one is waiting for this response, so we can
|
|
// throw it away.
|
|
nout << "Discarding bogus response: " << *_response << "\n" << flush;
|
|
delete _response;
|
|
_response = NULL;
|
|
_got_response_id = -1;
|
|
}
|
|
|
|
if (!_p3dpython_running) {
|
|
// Hmm, looks like Python has gone away.
|
|
|
|
// TODO: make sure _p3dpython_running gets set to false when the
|
|
// process dies unexpectedly.
|
|
_response_ready.release();
|
|
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
|
|
// particular, the CreateWindow() call within the subprocess--will
|
|
// starve, and we could end up with deadlock.
|
|
|
|
// A single PeekMessage() seems to be sufficient.
|
|
MSG msg;
|
|
PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE | PM_NOYIELD);
|
|
|
|
// We wait with a timeout, so we can go back and spin the event
|
|
// loop some more.
|
|
_response_ready.wait(0.1);
|
|
nout << "." << flush;
|
|
#else // _WIN32
|
|
|
|
// On non-Windows platforms, we can just wait indefinitely.
|
|
_response_ready.wait();
|
|
#endif // _WIN32
|
|
}
|
|
// When we exit the loop, we've found the desired response.
|
|
nout << "got response: " << *_response << "\n" << flush;
|
|
|
|
TiXmlDocument *response = _response;
|
|
_response = NULL;
|
|
_got_response_id = -1;
|
|
|
|
_response_ready.release();
|
|
|
|
return response;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DSession::xml_to_p3dobj
|
|
// Access: Public
|
|
// Description: Converts the XML representation of the particular
|
|
// object value into a corresponding P3D_object.
|
|
// Returns the object, a new reference.
|
|
////////////////////////////////////////////////////////////////////
|
|
P3D_object *P3DSession::
|
|
xml_to_p3dobj(const TiXmlElement *xvalue) {
|
|
const char *type = xvalue->Attribute("type");
|
|
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
|
|
|
if (strcmp(type, "undefined") == 0) {
|
|
return inst_mgr->new_undefined_object();
|
|
|
|
} else if (strcmp(type, "none") == 0) {
|
|
return inst_mgr->new_none_object();
|
|
|
|
} else if (strcmp(type, "bool") == 0) {
|
|
int value;
|
|
if (xvalue->QueryIntAttribute("value", &value) == TIXML_SUCCESS) {
|
|
return inst_mgr->new_bool_object(value != 0);
|
|
}
|
|
|
|
} else if (strcmp(type, "int") == 0) {
|
|
int value;
|
|
if (xvalue->QueryIntAttribute("value", &value) == TIXML_SUCCESS) {
|
|
return new P3DIntObject(value);
|
|
}
|
|
|
|
} else if (strcmp(type, "float") == 0) {
|
|
double value;
|
|
if (xvalue->QueryDoubleAttribute("value", &value) == TIXML_SUCCESS) {
|
|
return new P3DFloatObject(value);
|
|
}
|
|
|
|
} else if (strcmp(type, "string") == 0) {
|
|
// Using the string form here instead of the char * form, so we
|
|
// don't get tripped up on embedded null characters.
|
|
const string *value = xvalue->Attribute(string("value"));
|
|
if (value != NULL) {
|
|
return new P3DStringObject(*value);
|
|
}
|
|
|
|
} else if (strcmp(type, "browser") == 0) {
|
|
int object_id;
|
|
if (xvalue->QueryIntAttribute("object_id", &object_id) == TIXML_SUCCESS) {
|
|
SentObjects::iterator si = _sent_objects.find(object_id);
|
|
if (si == _sent_objects.end()) {
|
|
// Hmm, the child process gave us a bogus object ID.
|
|
return inst_mgr->new_undefined_object();
|
|
}
|
|
|
|
P3D_object *obj = (*si).second;
|
|
|
|
P3D_OBJECT_INCREF(obj);
|
|
return obj;
|
|
}
|
|
|
|
} else if (strcmp(type, "python") == 0) {
|
|
int object_id;
|
|
if (xvalue->QueryIntAttribute("object_id", &object_id) == TIXML_SUCCESS) {
|
|
return new P3DPythonObject(this, object_id);
|
|
}
|
|
}
|
|
|
|
// Something went wrong in decoding.
|
|
return inst_mgr->new_undefined_object();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DSession::p3dobj_to_xml
|
|
// Access: Public
|
|
// Description: Allocates and returns a new XML structure
|
|
// corresponding to the indicated value. The supplied
|
|
// P3DObject's reference count is not decremented; the
|
|
// caller remains responsible for decrementing it later.
|
|
////////////////////////////////////////////////////////////////////
|
|
TiXmlElement *P3DSession::
|
|
p3dobj_to_xml(P3D_object *obj) {
|
|
TiXmlElement *xvalue = new TiXmlElement("value");
|
|
|
|
switch (P3D_OBJECT_GET_TYPE(obj)) {
|
|
case P3D_OT_undefined:
|
|
xvalue->SetAttribute("type", "undefined");
|
|
break;
|
|
|
|
case P3D_OT_none:
|
|
xvalue->SetAttribute("type", "none");
|
|
break;
|
|
|
|
case P3D_OT_bool:
|
|
xvalue->SetAttribute("type", "bool");
|
|
xvalue->SetAttribute("value", (int)P3D_OBJECT_GET_BOOL(obj));
|
|
break;
|
|
|
|
case P3D_OT_int:
|
|
xvalue->SetAttribute("type", "int");
|
|
xvalue->SetAttribute("value", P3D_OBJECT_GET_INT(obj));
|
|
break;
|
|
|
|
case P3D_OT_float:
|
|
xvalue->SetAttribute("type", "float");
|
|
xvalue->SetDoubleAttribute("value", P3D_OBJECT_GET_FLOAT(obj));
|
|
break;
|
|
|
|
case P3D_OT_string:
|
|
{
|
|
xvalue->SetAttribute("type", "string");
|
|
int size = P3D_OBJECT_GET_STRING(obj, NULL, 0);
|
|
char *buffer = new char[size];
|
|
P3D_OBJECT_GET_STRING(obj, buffer, size);
|
|
xvalue->SetAttribute("value", string(buffer, size));
|
|
delete [] buffer;
|
|
}
|
|
break;
|
|
|
|
case P3D_OT_object:
|
|
if (obj->_class == &P3DObject::_object_class &&
|
|
((P3DObject *)obj)->is_python_object() &&
|
|
((P3DPythonObject *)obj)->get_session() == this) {
|
|
// If it's one of our kind of objects, it must be a
|
|
// P3DPythonObject. In this case, just send the object_id down,
|
|
// since the actual implementation of this object exists (as a
|
|
// Python object) in the sub-process space.
|
|
int object_id = ((P3DPythonObject *)obj)->get_object_id();
|
|
xvalue->SetAttribute("type", "python");
|
|
xvalue->SetAttribute("object_id", object_id);
|
|
|
|
} else {
|
|
// Otherwise, it must a host-provided object, which means we
|
|
// should pass a reference down to this particular object, so
|
|
// the Python process knows to call back up to here to query it.
|
|
|
|
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
|
int object_id = inst_mgr->get_unique_id();
|
|
bool inserted = _sent_objects.insert(SentObjects::value_type(object_id, obj)).second;
|
|
while (!inserted) {
|
|
// Hmm, we must have cycled around the entire int space? Either
|
|
// that, or there's a logic bug somewhere. Assume the former,
|
|
// and keep looking for an empty slot.
|
|
object_id = inst_mgr->get_unique_id();
|
|
inserted = _sent_objects.insert(SentObjects::value_type(object_id, obj)).second;
|
|
}
|
|
|
|
// Now that it's stored in the map, increment its reference count.
|
|
P3D_OBJECT_INCREF(obj);
|
|
|
|
xvalue->SetAttribute("type", "browser");
|
|
xvalue->SetAttribute("object_id", object_id);
|
|
}
|
|
break;
|
|
}
|
|
|
|
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
|
|
// Description: Notified as the _panda3d package is downloaded.
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DSession::
|
|
install_progress(P3DPackage *package, double progress) {
|
|
Instances::iterator ii;
|
|
for (ii = _instances.begin(); ii != _instances.end(); ++ii) {
|
|
P3DInstance *inst = (*ii).second;
|
|
inst->install_progress(package, progress);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DSession::start_p3dpython
|
|
// Access: Private
|
|
// Description: Starts Python running in a child process.
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DSession::
|
|
start_p3dpython() {
|
|
string p3dpython = P3D_PLUGIN_P3DPYTHON;
|
|
if (p3dpython.empty()) {
|
|
p3dpython = _python_root_dir + "/p3dpython";
|
|
#ifdef _WIN32
|
|
p3dpython += ".exe";
|
|
#endif
|
|
}
|
|
|
|
// Populate the new process' environment.
|
|
string env;
|
|
|
|
// These are the enviroment variables we forward from the current
|
|
// environment, if they are set.
|
|
const char *keep[] = {
|
|
"TMP", "TEMP", "HOME", "USER",
|
|
#ifdef _WIN32
|
|
"SYSTEMROOT", "USERPROFILE",
|
|
#endif
|
|
NULL
|
|
};
|
|
for (int ki = 0; keep[ki] != NULL; ++ki) {
|
|
char *value = getenv(keep[ki]);
|
|
if (value != NULL) {
|
|
env += keep[ki];
|
|
env += "=";
|
|
env += value;
|
|
env += '\0';
|
|
}
|
|
}
|
|
|
|
// Define some new environment variables.
|
|
env += "PATH=";
|
|
env += _python_root_dir;
|
|
env += '\0';
|
|
|
|
env += "PYTHONPATH=";
|
|
env += _python_root_dir;
|
|
env += '\0';
|
|
|
|
env += "PRC_DIR=";
|
|
env += _python_root_dir;
|
|
env += '\0';
|
|
|
|
env += "PANDA_PRC_DIR=";
|
|
env += _python_root_dir;
|
|
env += '\0';
|
|
|
|
#ifdef _WIN32
|
|
_p3dpython_handle = win_create_process
|
|
(p3dpython, _python_root_dir, env, _output_filename,
|
|
_pipe_read, _pipe_write);
|
|
bool started_p3dpython = (_p3dpython_handle != INVALID_HANDLE_VALUE);
|
|
#else
|
|
_p3dpython_pid = posix_create_process
|
|
(p3dpython, _python_root_dir, env, _output_filename,
|
|
_pipe_read, _pipe_write);
|
|
bool started_p3dpython = (_p3dpython_pid > 0);
|
|
#endif
|
|
|
|
if (!started_p3dpython) {
|
|
nout << "Failed to create process.\n" << flush;
|
|
return;
|
|
}
|
|
_p3dpython_running = true;
|
|
|
|
if (!_pipe_read) {
|
|
nout << "unable to open read pipe\n" << flush;
|
|
}
|
|
if (!_pipe_write) {
|
|
nout << "unable to open write pipe\n" << flush;
|
|
}
|
|
|
|
spawn_read_thread();
|
|
|
|
// The very first command we send to the process is its session_id.
|
|
TiXmlDocument doc;
|
|
TiXmlDeclaration *decl = new TiXmlDeclaration("1.0", "utf-8", "");
|
|
TiXmlElement *xcommand = new TiXmlElement("command");
|
|
xcommand->SetAttribute("cmd", "init");
|
|
xcommand->SetAttribute("session_id", _session_id);
|
|
doc.LinkEndChild(decl);
|
|
doc.LinkEndChild(xcommand);
|
|
nout << "sent: " << doc << "\n" << flush;
|
|
_pipe_write << doc;
|
|
|
|
// Also feed it any commands we may have queued up from before the
|
|
// process was started.
|
|
Commands::iterator ci;
|
|
for (ci = _commands.begin(); ci != _commands.end(); ++ci) {
|
|
nout << "sent: " << *(*ci) << "\n" << flush;
|
|
_pipe_write << *(*ci);
|
|
delete (*ci);
|
|
}
|
|
_commands.clear();
|
|
|
|
_pipe_write << flush;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DSession::spawn_read_thread
|
|
// Access: Private
|
|
// Description: Starts the read thread. This thread is responsible
|
|
// for reading the standard input socket for XML
|
|
// requests and storing them in the _requests queue.
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DSession::
|
|
spawn_read_thread() {
|
|
assert(!_read_thread_continue);
|
|
|
|
_read_thread_continue = true;
|
|
SPAWN_THREAD(_read_thread, rt_thread_run, this);
|
|
_started_read_thread = true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DSession::join_read_thread
|
|
// Access: Private
|
|
// Description: Waits for the read thread to stop.
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DSession::
|
|
join_read_thread() {
|
|
if (!_started_read_thread) {
|
|
return;
|
|
}
|
|
|
|
_read_thread_continue = false;
|
|
_pipe_read.close();
|
|
|
|
JOIN_THREAD(_read_thread);
|
|
_started_read_thread = false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DSession::rt_thread_run
|
|
// Access: Private
|
|
// Description: The main function for the read thread.
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DSession::
|
|
rt_thread_run() {
|
|
while (_read_thread_continue) {
|
|
TiXmlDocument *doc = new TiXmlDocument;
|
|
|
|
_pipe_read >> *doc;
|
|
if (!_pipe_read || _pipe_read.eof()) {
|
|
// Some error on reading. Abort.
|
|
rt_terminate();
|
|
return;
|
|
}
|
|
|
|
// Successfully read an XML document.
|
|
rt_handle_request(doc);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DSession::rt_handle_request
|
|
// Access: Private
|
|
// Description: Processes a single request or notification received
|
|
// from an instance.
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DSession::
|
|
rt_handle_request(TiXmlDocument *doc) {
|
|
TiXmlElement *xresponse = doc->FirstChildElement("response");
|
|
if (xresponse != (TiXmlElement *)NULL) {
|
|
int response_id;
|
|
if (xresponse->QueryIntAttribute("response_id", &response_id) == TIXML_SUCCESS) {
|
|
// This is a response to a previous command-and-response. Send
|
|
// it to the parent thread.
|
|
_response_ready.acquire();
|
|
if (_response != NULL) {
|
|
// Hey, there's already a response there. Since there's only
|
|
// one thread waiting at a time on the command-response cycle,
|
|
// this must be a bogus response that never got picked up.
|
|
// Discard it.
|
|
nout << "Discarding bogus response: " << *_response << "\n";
|
|
delete _response;
|
|
}
|
|
_response = doc;
|
|
_got_response_id = response_id;
|
|
_response_ready.notify();
|
|
_response_ready.release();
|
|
return;
|
|
}
|
|
}
|
|
|
|
TiXmlElement *xrequest = doc->FirstChildElement("request");
|
|
if (xrequest != (TiXmlElement *)NULL) {
|
|
int instance_id;
|
|
if (xrequest->QueryIntAttribute("instance_id", &instance_id) == TIXML_SUCCESS) {
|
|
// Look up the particular instance this is related to.
|
|
ACQUIRE_LOCK(_instances_lock);
|
|
Instances::const_iterator ii;
|
|
ii = _instances.find(instance_id);
|
|
if (ii != _instances.end()) {
|
|
P3DInstance *inst = (*ii).second;
|
|
inst->add_raw_request(doc);
|
|
doc = NULL;
|
|
}
|
|
RELEASE_LOCK(_instances_lock);
|
|
}
|
|
}
|
|
|
|
if (doc != NULL) {
|
|
delete doc;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DSession::rt_terminate
|
|
// Access: Private
|
|
// Description: Got a closed pipe from the sub-process. Send a
|
|
// terminate request for all instances.
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DSession::
|
|
rt_terminate() {
|
|
Instances icopy;
|
|
ACQUIRE_LOCK(_instances_lock);
|
|
icopy = _instances;
|
|
RELEASE_LOCK(_instances_lock);
|
|
|
|
// TODO: got a race condition here. What happens if someone deletes
|
|
// an instance while we're processing this loop?
|
|
|
|
for (Instances::iterator ii = icopy.begin(); ii != icopy.end(); ++ii) {
|
|
P3DInstance *inst = (*ii).second;
|
|
inst->request_stop();
|
|
}
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DSession::win_create_process
|
|
// Access: Private, Static
|
|
// Description: Creates a sub-process to run the named program
|
|
// executable, with the indicated environment string.
|
|
// Standard error is logged to output_filename, if that
|
|
// string is nonempty.
|
|
//
|
|
// Opens the two HandleStreams as the read and write
|
|
// pipes to the child process's standard output and
|
|
// standard input, respectively.
|
|
//
|
|
// Returns the handle to the created process on success,
|
|
// or INVALID_HANDLE_VALUE on falure.
|
|
////////////////////////////////////////////////////////////////////
|
|
HANDLE P3DSession::
|
|
win_create_process(const string &program, const string &start_dir,
|
|
const string &env, const string &output_filename,
|
|
HandleStream &pipe_read, HandleStream &pipe_write) {
|
|
|
|
// Create a bi-directional pipe to communicate with the sub-process.
|
|
HANDLE r_to, w_to, r_from, w_from;
|
|
|
|
// Create the pipe to the process.
|
|
if (!CreatePipe(&r_to, &w_to, NULL, 0)) {
|
|
nout << "failed to create pipe\n" << flush;
|
|
} else {
|
|
// 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, NULL, 0)) {
|
|
nout << "failed to create pipe\n" << flush;
|
|
} 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);
|
|
}
|
|
|
|
HANDLE error_handle = GetStdHandle(STD_ERROR_HANDLE);
|
|
bool got_output_filename = !output_filename.empty();
|
|
if (got_output_filename) {
|
|
// Open the named file for output and redirect the child's stderr
|
|
// into it.
|
|
HANDLE handle = CreateFile
|
|
(output_filename.c_str(), GENERIC_WRITE,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
|
NULL, CREATE_ALWAYS, 0, NULL);
|
|
if (handle != INVALID_HANDLE_VALUE) {
|
|
error_handle = handle;
|
|
SetHandleInformation(error_handle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
|
|
} else {
|
|
nout << "Unable to open " << output_filename << "\n" << flush;
|
|
}
|
|
}
|
|
|
|
// 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 = error_handle;
|
|
startup_info.hStdOutput = w_from;
|
|
startup_info.hStdInput = r_to;
|
|
startup_info.dwFlags |= STARTF_USESTDHANDLES;
|
|
|
|
// Make sure the "python" console window is hidden.
|
|
startup_info.wShowWindow = SW_HIDE;
|
|
startup_info.dwFlags |= STARTF_USESHOWWINDOW;
|
|
|
|
PROCESS_INFORMATION process_info;
|
|
BOOL result = CreateProcess
|
|
(program.c_str(), NULL, NULL, NULL, TRUE, 0,
|
|
(void *)env.c_str(), start_dir.c_str(),
|
|
&startup_info, &process_info);
|
|
bool started_program = (result != 0);
|
|
|
|
// Close the pipe handles that are now owned by the child.
|
|
CloseHandle(w_from);
|
|
CloseHandle(r_to);
|
|
if (got_output_filename) {
|
|
CloseHandle(error_handle);
|
|
}
|
|
|
|
if (!started_program) {
|
|
CloseHandle(r_from);
|
|
CloseHandle(w_to);
|
|
return INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
pipe_read.open_read(r_from);
|
|
pipe_write.open_write(w_to);
|
|
|
|
CloseHandle(process_info.hThread);
|
|
return process_info.hProcess;
|
|
}
|
|
#endif // _WIN32
|
|
|
|
|
|
#ifndef _WIN32
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DSession::posix_create_process
|
|
// Access: Private, Static
|
|
// Description: Creates a sub-process to run the named program
|
|
// executable, with the indicated environment string.
|
|
//
|
|
// Opens the two HandleStreams as the read and write
|
|
// pipes to the child process's standard output and
|
|
// standard input, respectively. Returns true on
|
|
// success, false on failure.
|
|
//
|
|
// Returns the pid of the created process on success, or
|
|
// -1 on falure.
|
|
////////////////////////////////////////////////////////////////////
|
|
int P3DSession::
|
|
posix_create_process(const string &program, const string &start_dir,
|
|
const string &env, const string &output_filename,
|
|
HandleStream &pipe_read, HandleStream &pipe_write) {
|
|
// Create a bi-directional pipe to communicate with the sub-process.
|
|
int to_fd[2];
|
|
if (pipe(to_fd) < 0) {
|
|
perror("failed to create pipe");
|
|
}
|
|
int from_fd[2];
|
|
if (pipe(from_fd) < 0) {
|
|
perror("failed to create pipe");
|
|
}
|
|
|
|
// Fork and exec.
|
|
pid_t child = fork();
|
|
if (child < 0) {
|
|
close(to_fd[0]);
|
|
close(to_fd[1]);
|
|
close(from_fd[0]);
|
|
close(from_fd[1]);
|
|
perror("fork");
|
|
return -1;
|
|
}
|
|
|
|
if (child == 0) {
|
|
// Here we are in the child process.
|
|
bool got_output_filename = !output_filename.empty();
|
|
if (got_output_filename) {
|
|
// Open the named file for output and redirect the child's stderr
|
|
// into it.
|
|
int logfile_fd = open(output_filename.c_str(),
|
|
O_WRONLY | O_CREAT | O_TRUNC, 0666);
|
|
if (logfile_fd < 0) {
|
|
nout << "Unable to open " << output_filename << "\n" << flush;
|
|
} else {
|
|
dup2(logfile_fd, STDERR_FILENO);
|
|
close(logfile_fd);
|
|
}
|
|
}
|
|
|
|
// Set the appropriate ends of the bi-directional pipe as the
|
|
// standard input and standard output of the child process.
|
|
dup2(to_fd[0], STDIN_FILENO);
|
|
dup2(from_fd[1], STDOUT_FILENO);
|
|
close(to_fd[1]);
|
|
close(from_fd[0]);
|
|
|
|
if (chdir(start_dir.c_str()) < 0) {
|
|
nout << "Could not chdir to " << start_dir << "\n" << flush;
|
|
_exit(1);
|
|
}
|
|
|
|
// build up an array of char strings for the environment.
|
|
vector<const char *> ptrs;
|
|
size_t p = 0;
|
|
size_t zero = env.find('\0', p);
|
|
while (zero != string::npos) {
|
|
ptrs.push_back(env.data() + p);
|
|
p = zero + 1;
|
|
zero = env.find('\0', p);
|
|
}
|
|
ptrs.push_back((char *)NULL);
|
|
|
|
execle(program.c_str(), program.c_str(), (char *)0, &ptrs[0]);
|
|
nout << "Failed to exec " << program << "\n" << flush;
|
|
_exit(1);
|
|
}
|
|
|
|
pipe_read.open_read(from_fd[0]);
|
|
pipe_write.open_write(to_fd[1]);
|
|
close(to_fd[0]);
|
|
close(from_fd[1]);
|
|
|
|
return child;
|
|
}
|
|
#endif // _WIN32
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DSession::PackageCallback::Constructor
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
P3DSession::PackageCallback::
|
|
PackageCallback(P3DSession *session) :
|
|
_session(session)
|
|
{
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DSession::PackageCallback::package_ready
|
|
// Access: Public, Virtual
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DSession::PackageCallback::
|
|
package_ready(P3DPackage *package, bool success) {
|
|
if (this == _session->_panda3d_callback) {
|
|
_session->_panda3d_callback = NULL;
|
|
if (package == _session->_panda3d) {
|
|
if (success) {
|
|
_session->start_p3dpython();
|
|
} else {
|
|
nout << "Failed to install " << package->get_package_name()
|
|
<< "_" << package->get_package_version() << "\n";
|
|
}
|
|
} else {
|
|
nout << "Unexpected panda3d package: " << package << "\n";
|
|
}
|
|
} else {
|
|
nout << "Unexpected callback for P3DSession\n";
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DSession::PackageCallback::install_progress
|
|
// Access: Public, Virtual
|
|
// Description: This callback is received during the download process
|
|
// to inform us how much has been installed so far.
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DSession::PackageCallback::
|
|
install_progress(P3DPackage *package, double progress) {
|
|
if (this == _session->_panda3d_callback) {
|
|
_session->install_progress(package, progress);
|
|
}
|
|
}
|