open_toontown_panda3d/direct/src/plugin/p3dSession.cxx

349 lines
11 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 <tinyxml.h>
#include <malloc.h>
////////////////////////////////////////////////////////////////////
// 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) {
_session_key = inst->get_session_key();
_python_version = inst->get_python_version();
_python_root_dir = "C:/p3drun";
INIT_LOCK(_instances_lock);
_read_thread_continue = false;
string p3dpython = "c:/cygwin/home/drose/player/direct/built/bin/p3dpython.exe";
// string p3dpython = _python_root_dir + "/p3dpython.exe";
// 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[] = {
"TEMP", "HOME", "USER",
#ifdef _WIN32
"SYSTEMROOT",
#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';
// Create a bi-directional pipe to communicate with the sub-process.
#ifdef _WIN32
HANDLE r_to, w_to, r_from, w_from;
// Create the pipe to the process.
if (!CreatePipe(&r_to, &w_to, NULL, 0)) {
cerr << "failed to create pipe\n";
} 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)) {
cerr << "failed to create pipe\n";
} 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);
}
// 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(), _python_root_dir.c_str(),
&startup_info, &_p3dpython);
_started_p3dpython = (result != 0);
if (!_started_p3dpython) {
cerr << "Failed to create process.\n";
} else {
cerr << "Created process: " << _p3dpython.dwProcessId << "\n";
}
// Close the pipe handles that are now owned by the child.
CloseHandle(w_from);
CloseHandle(r_to);
_pipe_read.open_read(r_from);
_pipe_write.open_write(w_to);
#endif // _WIN32
if (!_pipe_read) {
cerr << "unable to open read pipe\n";
}
if (!_pipe_write) {
cerr << "unable to open write pipe\n";
}
spawn_read_thread();
}
////////////////////////////////////////////////////////////////////
// Function: P3DSession::Destructor
// Access: Public
// Description: Terminates the session by shutting down Python and
// stopping the subprocess.
////////////////////////////////////////////////////////////////////
P3DSession::
~P3DSession() {
if (_started_p3dpython) {
// Tell the process we're going away.
TiXmlDocument doc;
TiXmlDeclaration *decl = new TiXmlDeclaration("1.0", "", "");
TiXmlElement *xcommand = new TiXmlElement("command");
xcommand->SetAttribute("cmd", "exit");
doc.LinkEndChild(decl);
doc.LinkEndChild(xcommand);
_pipe_write << doc << flush;
// Also close the pipe, to help underscore the point.
_pipe_write.close();
_pipe_read.close();
#ifdef _WIN32
// Now give the process a chance to terminate itself cleanly.
if (WaitForSingleObject(_p3dpython.hProcess, 2000) == WAIT_TIMEOUT) {
// It didn't shut down cleanly, so kill it the hard way.
cerr << "Terminating process.\n";
TerminateProcess(_p3dpython.hProcess, 2);
}
CloseHandle(_p3dpython.hProcess);
CloseHandle(_p3dpython.hThread);
#endif
}
join_read_thread();
DESTROY_LOCK(_instances_lock);
}
////////////////////////////////////////////////////////////////////
// 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);
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;
TiXmlDeclaration *decl = new TiXmlDeclaration("1.0", "", "");
TiXmlElement *xcommand = new TiXmlElement("command");
xcommand->SetAttribute("cmd", "start_instance");
TiXmlElement *xinstance = inst->make_xml();
doc.LinkEndChild(decl);
doc.LinkEndChild(xcommand);
xcommand->LinkEndChild(xinstance);
_pipe_write << doc << flush;
}
////////////////////////////////////////////////////////////////////
// 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;
TiXmlDeclaration *decl = new TiXmlDeclaration("1.0", "", "");
TiXmlElement *xcommand = new TiXmlElement("command");
xcommand->SetAttribute("cmd", "terminate_instance");
xcommand->SetAttribute("id", inst->get_instance_id());
doc.LinkEndChild(decl);
doc.LinkEndChild(xcommand);
_pipe_write << doc << flush;
ACQUIRE_LOCK(_instances_lock);
if (inst->_session == this) {
inst->_session = NULL;
_instances.erase(inst->get_instance_id());
}
RELEASE_LOCK(_instances_lock);
}
////////////////////////////////////////////////////////////////////
// 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);
// We have to use direct OS calls to create the thread instead of
// Panda constructs, because it has to be an actual thread, not
// necessarily a Panda thread (we can't use Panda's simple threads
// implementation, because we can't get overlapped I/O on an
// anonymous pipe in Windows).
_read_thread_continue = true;
#ifdef _WIN32
_read_thread = CreateThread(NULL, 0, &win_rt_thread_run, this, 0, NULL);
#endif
}
////////////////////////////////////////////////////////////////////
// Function: P3DSession::join_read_thread
// Access: Private
// Description: Waits for the read thread to stop.
////////////////////////////////////////////////////////////////////
void P3DSession::
join_read_thread() {
cerr << "session waiting for thread\n";
_read_thread_continue = false;
_pipe_read.close();
#ifdef _WIN32
assert(_read_thread != NULL);
WaitForSingleObject(_read_thread, INFINITE);
CloseHandle(_read_thread);
_read_thread = NULL;
#endif
cerr << "session done waiting for thread\n";
}
////////////////////////////////////////////////////////////////////
// Function: P3DSession::rt_thread_run
// Access: Private
// Description: The main function for the read thread.
////////////////////////////////////////////////////////////////////
void P3DSession::
rt_thread_run() {
cerr << "session thread reading.\n";
while (_read_thread_continue) {
TiXmlDocument *doc = new TiXmlDocument;
_pipe_read >> *doc;
if (!_pipe_read || _pipe_read.eof()) {
// Some error on reading. Abort.
cerr << "Error on session reading.\n";
rt_terminate();
return;
}
// Successfully read an XML document.
cerr << "Session got request: " << *doc << "\n";
// TODO: feed the request up to the parent.
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;
P3D_request *request = new P3D_request;
request->_instance = inst;
request->_request_type = P3D_RT_stop;
inst->add_request(request);
}
}
#ifdef _WIN32
////////////////////////////////////////////////////////////////////
// Function: P3DPython::win_rt_thread_run
// Access: Private, Static
// Description: The Windows flavor of the thread callback function.
////////////////////////////////////////////////////////////////////
DWORD P3DSession::
win_rt_thread_run(LPVOID data) {
((P3DSession *)data)->rt_thread_run();
return 0;
}
#endif