interactive_console

This commit is contained in:
David Rose 2009-08-30 21:10:30 +00:00
parent 365f35a4f2
commit 7b34234edc
16 changed files with 468 additions and 176 deletions

View File

@ -29,7 +29,7 @@ else:
from direct.showbase import VFSImporter
from direct.showbase.DirectObject import DirectObject
from pandac.PandaModules import VirtualFileSystem, Filename, Multifile, loadPrcFileData, unloadPrcFile, getModelPath, HTTPClient, Thread, WindowProperties, readXmlStream, ExecutionEnvironment, PandaSystem, URLSpec
from pandac.PandaModules import VirtualFileSystem, Filename, Multifile, loadPrcFileData, unloadPrcFile, getModelPath, HTTPClient, Thread, WindowProperties, readXmlStream, ExecutionEnvironment, PandaSystem, URLSpec, Notify, StreamWriter, ConfigVariableString
from direct.stdpy import file
from direct.task.TaskManagerGlobal import taskMgr
from direct.showbase.MessengerGlobal import messenger
@ -62,15 +62,21 @@ class AppRunner(DirectObject):
def __init__(self):
DirectObject.__init__(self)
# We need to make sure sys.stdout maps to sys.stderr instead,
# so if someone makes an unadorned print command within Python
# code, it won't muck up the data stream between parent and
# child.
sys.stdout = sys.stderr
# We direct both our stdout and stderr objects onto Panda's
# Notify stream. This ensures that unadorned print statements
# made within Python will get routed into the log properly.
stream = StreamWriter(Notify.out(), False)
sys.stdout = stream
sys.stderr = stream
# This is set true by dummyAppRunner(), below.
self.dummy = False
# These will be set from the application flags when
# setP3DFilename() is called.
self.allowPythonDev = False
self.interactiveConsole = False
self.sessionId = 0
self.packedAppEnvironmentInitialized = False
self.gotWindow = False
@ -303,6 +309,12 @@ class AppRunner(DirectObject):
if hasattr(main, 'main') and callable(main.main):
main.main(self)
if self.interactiveConsole:
# At this point, we have successfully loaded the app.
# If the interactive_console flag is enabled, stop the
# main loop now and give the user a Python prompt.
taskMgr.stop()
def getPandaScriptObject(self):
""" Called by the browser to query the Panda instance's
toplevel scripting object, for querying properties in the
@ -363,8 +375,8 @@ class AppRunner(DirectObject):
print "%s %s is not preloaded." % (
package.packageName, package.packageVersion)
def setP3DFilename(self, p3dFilename, tokens = [], argv = [],
instanceId = None):
def setP3DFilename(self, p3dFilename, tokens, argv, instanceId,
interactiveConsole):
""" Called by the browser to specify the p3d file that
contains the application itself, along with the web tokens
and/or command-line arguments. Once this method has been
@ -402,6 +414,9 @@ class AppRunner(DirectObject):
# Now load the p3dInfo file.
self.p3dInfo = None
self.p3dPackage = None
self.p3dConfig = None
self.allowPythonDev = False
i = mf.findSubfile('p3d_info.xml')
if i >= 0:
stream = mf.openReadSubfile(i)
@ -409,6 +424,24 @@ class AppRunner(DirectObject):
mf.closeReadSubfile(stream)
if self.p3dInfo:
self.p3dPackage = self.p3dInfo.FirstChildElement('package')
if self.p3dPackage:
self.p3dConfig = self.p3dPackage.FirstChildElement('config')
if self.p3dConfig:
allowPythonDev = self.p3dConfig.Attribute('allow_python_dev')
if allowPythonDev:
self.allowPythonDev = int(allowPythonDev)
# The interactiveConsole flag can only be set true if the
# application has allow_python_dev set.
if not self.allowPythonDev and interactiveConsole:
raise StandardError, "Impossible, interactive_console set without allow_python_dev."
self.interactiveConsole = interactiveConsole
if self.allowPythonDev:
# Set the fps text to remind the user that
# allow_python_dev is enabled.
ConfigVariableString('frame-rate-meter-text-pattern').setValue('allow_python_dev %0.1f fps')
self.initPackedAppEnvironment()

View File

@ -41,13 +41,12 @@ Options:
also be specified with the pdef-path Config.prc variable.
-D
Allow the application to be run with -D on the panda3d command
line, or equivalently, "python_dev=1" in the web tokens. If this
is allowed, then it will preserve the PYTHONPATH environment
variable from the user's environment, allowing Python files on
disk to shadow the same-named Python files within the p3d file,
for rapid iteration on the Python code. If the appliation is not
built with -D here, this option does nothing at runtime.
Sets the allow_python_dev flag in the application. This enables
additional runtime debug operations, particularly the -i option
to the panda3d command, which enables a live Python prompt within
the application's environment. Setting this flag may be useful
to develop an application initially, but should not be set on an
application intended for secure deployment.
"""

View File

@ -115,7 +115,7 @@
p3dCInstance.cxx \
p3dCInstance.h p3dCInstance.I \
p3dPythonRun.cxx p3dPythonRun.h p3dPythonRun.I \
run_p3dpython.h
run_p3dpython.h run_p3dpython.cxx
#define WIN_SYS_LIBS user32.lib

View File

@ -39,23 +39,6 @@ static const size_t length_nonce2 = 612811373;
////////////////////////////////////////////////////////////////////
static void
write_xml_node(ostream &out, TiXmlNode *xnode) {
NodeType type = NT_element;
if (xnode->ToDocument() != NULL) {
type = NT_document;
} else if (xnode->ToElement() != NULL) {
type = NT_element;
} else if (xnode->ToText() != NULL) {
type = NT_text;
} else {
type = NT_unknown;
}
out.put((char)type);
// We don't bother to write any data for the unknown types.
if (type == NT_unknown) {
return;
}
const string &value = xnode->ValueStr();
size_t value_length = value.length();
size_t value_proof = (value_length + length_nonce1) * length_nonce2;
@ -69,6 +52,24 @@ write_xml_node(ostream &out, TiXmlNode *xnode) {
out.write((char *)&value_proof, sizeof(value_proof));
out.write(value.data(), value_length);
// Now write out the node type.
NodeType type = NT_element;
if (xnode->ToDocument() != NULL) {
type = NT_document;
} else if (xnode->ToElement() != NULL) {
type = NT_element;
} else if (xnode->ToText() != NULL) {
type = NT_text;
} else {
type = NT_unknown;
}
out.put((char)type);
// We don't bother to write any further data for the unknown types.
if (type == NT_unknown) {
return;
}
if (type == NT_element) {
// Write the element attributes.
TiXmlElement *xelement = xnode->ToElement();
@ -119,11 +120,6 @@ write_xml_node(ostream &out, TiXmlNode *xnode) {
static TiXmlNode *
read_xml_node(istream &in, char *&buffer, size_t &buffer_length,
ostream &logfile) {
NodeType type = (NodeType)in.get();
if (type == NT_unknown) {
return NULL;
}
size_t value_length;
in.read((char *)&value_length, sizeof(value_length));
if (in.gcount() != sizeof(value_length)) {
@ -167,6 +163,12 @@ read_xml_node(istream &in, char *&buffer, size_t &buffer_length,
in.read(buffer, value_length);
string value(buffer, value_length);
// Read the node type.
NodeType type = (NodeType)in.get();
if (type == NT_unknown) {
return NULL;
}
TiXmlNode *xnode = NULL;
if (type == NT_element) {
xnode = new TiXmlElement(value);

View File

@ -21,9 +21,11 @@
#ifdef _WIN32
#include <windows.h>
typedef HANDLE FHandle;
static const HANDLE invalid_fhandle = INVALID_HANDLE_VALUE;
#else
// On POSIX, we use a file descriptor as a "handle".
typedef int FHandle;
static const int invalid_fhandle = -1;
#endif
#endif

View File

@ -164,7 +164,7 @@ load_plugin(const string &p3d_plugin_filename,
#else // _WIN32
// Posix case.
assert(module == NULL);
module = dlopen(filename.c_str(), RTLD_NOW | RTLD_LOCAL);
module = dlopen(filename.c_str(), RTLD_LOCAL);
if (module == NULL) {
// Couldn't load the .so.
return false;

View File

@ -34,6 +34,7 @@
#include <shlobj.h>
#else
#include <sys/stat.h>
#include <signal.h>
#endif
static ofstream logfile;

View File

@ -15,6 +15,7 @@
#include "run_p3dpython.h"
#include <iostream>
#include <sstream>
using namespace std;
#ifndef _WIN32
@ -41,12 +42,29 @@ main(int argc, char *argv[]) {
const char *program_name = argv[0];
const char *dll_file = NULL;
const char *archive_file = NULL;
const char *input_handle_str = NULL;
const char *output_handle_str = NULL;
const char *error_handle_str = NULL;
const char *interactive_console_str = NULL;
if (argc > 1) {
dll_file = argv[1];
}
if (argc > 2) {
archive_file = argv[2];
}
if (argc > 3) {
input_handle_str = argv[3];
}
if (argc > 4) {
output_handle_str = argv[4];
}
if (argc > 5) {
error_handle_str = argv[5];
}
if (argc > 6) {
interactive_console_str = argv[6];
}
if (dll_file == NULL || *dll_file == '\0') {
cerr << "No libp3dpython filename specified on command line.\n";
@ -58,6 +76,47 @@ main(int argc, char *argv[]) {
return 1;
}
FHandle input_handle = invalid_fhandle;
if (input_handle_str != NULL && *input_handle_str) {
stringstream stream(input_handle_str);
stream >> input_handle;
if (!stream) {
input_handle = invalid_fhandle;
}
}
FHandle output_handle = invalid_fhandle;
if (output_handle_str != NULL && *output_handle_str) {
stringstream stream(output_handle_str);
stream >> output_handle;
if (!stream) {
output_handle = invalid_fhandle;
}
}
FHandle error_handle = invalid_fhandle;
if (error_handle_str != NULL && *error_handle_str) {
stringstream stream(error_handle_str);
stream >> error_handle;
if (!stream) {
error_handle = invalid_fhandle;
}
}
bool interactive_console = false;
if (interactive_console_str != NULL && *interactive_console_str) {
stringstream stream(interactive_console_str);
int flag;
stream >> flag;
if (stream) {
interactive_console = (flag != 0);
}
}
cerr << "handles: " << input_handle << ", " << output_handle
<< ", " << error_handle << "\n";
cerr << "interactive_console = " << interactive_console << "\n";
// For some vague idea of security, we insist that this program can
// only run libp3dpython.dll: you can't use it to load just any
// arbitrary DLL on the system. Of course, if you're successfully
@ -72,15 +131,16 @@ main(int argc, char *argv[]) {
slash = backslash;
}
#endif
string basename;
if (slash == NULL) {
slash = dll_file;
basename = dll_file;
} else {
++slash;
//dirname = string(dll_file, slash - dll_file);
basename = (slash + 1);
}
string expected_basename = "libp3dpython" + dll_ext;
if (memcmp(slash, expected_basename.data(), expected_basename.size()) != 0) {
if (basename != expected_basename) {
cerr << dll_file << " does not name " << expected_basename << "\n";
return 1;
}
@ -97,18 +157,40 @@ main(int argc, char *argv[]) {
}
#define get_func GetProcAddress
FHandle input = GetStdHandle(STD_INPUT_HANDLE);
FHandle output = GetStdHandle(STD_OUTPUT_HANDLE);
if (!SetStdHandle(STD_INPUT_HANDLE, INVALID_HANDLE_VALUE)) {
cerr << "unable to reset input handle\n";
}
if (!SetStdHandle(STD_OUTPUT_HANDLE, INVALID_HANDLE_VALUE)) {
cerr << "unable to reset input handle\n";
// Get the default values for the communication handles, if we
// weren't given specific handles.
if (input_handle == invalid_fhandle) {
input_handle = GetStdHandle(STD_INPUT_HANDLE);
// Close the system input handle, so application code won't
// accidentally read from our private input stream.
if (!SetStdHandle(STD_INPUT_HANDLE, INVALID_HANDLE_VALUE)) {
cerr << "unable to reset input handle\n";
}
}
if (output_handle == invalid_fhandle) {
output_handle = GetStdHandle(STD_OUTPUT_HANDLE);
// Close the system output handle, so application code won't
// accidentally write to our private output stream.
if (!SetStdHandle(STD_OUTPUT_HANDLE, INVALID_HANDLE_VALUE)) {
cerr << "unable to reset input handle\n";
}
}
// No matter what error handle we were given, make it
// STD_ERROR_HANDLE.
if (error_handle == invalid_fhandle) {
error_handle = GetStdHandle(STD_ERROR_HANDLE);
} else {
SetStdHandle(STD_ERROR_HANDLE, error_handle);
}
#else // _WIN32
// Posix case.
void *module = dlopen(dll_file, RTLD_GLOBAL);
void *module = dlopen(dll_file, RTLD_LOCAL);
if (module == NULL) {
// Couldn't load the .so.
cerr << "Couldn't load " << dll_file << "\n";
@ -116,8 +198,25 @@ main(int argc, char *argv[]) {
}
#define get_func dlsym
FHandle input = STDIN_FILENO;
FHandle output = STDOUT_FILENO;
// Get the default values for the communication handles, if we
// weren't given specific handles.
if (input_handle == invalid_fhandle) {
input_handle = STDIN_FILENO;
}
if (output_handle == invalid_fhandle) {
output_handle = STDOUT_FILENO;
}
// No matter what error handle we were given, make it STDERR_FILENO.
if (error_handle == invalid_fhandle) {
error_handle = STDERR_FILENO;
} else if (error_handle != STDERR_FILENO) {
dup2(error_handle, STDERR_FILENO);
close(error_handle);
error_handle = STDERR_FILENO;
}
#endif // _WIN32
@ -127,7 +226,8 @@ main(int argc, char *argv[]) {
return 1;
}
if (!run_p3dpython(program_name, archive_file, input, output)) {
if (!run_p3dpython(program_name, archive_file, input_handle, output_handle,
error_handle, interactive_console)) {
cerr << "Failure on startup.\n";
return 1;
}

View File

@ -30,7 +30,8 @@ P3DPythonRun *P3DPythonRun::_global_ptr = NULL;
////////////////////////////////////////////////////////////////////
P3DPythonRun::
P3DPythonRun(const char *program_name, const char *archive_file,
FHandle input, FHandle output) {
FHandle input_handle, FHandle output_handle,
FHandle error_handle, bool interactive_console) {
_read_thread_continue = false;
_program_continue = true;
_session_terminated = false;
@ -40,6 +41,8 @@ P3DPythonRun(const char *program_name, const char *archive_file,
_session_id = 0;
_next_sent_id = 0;
_interactive_console = interactive_console;
if (program_name != NULL) {
_program_name = program_name;
}
@ -70,10 +73,18 @@ P3DPythonRun(const char *program_name, const char *archive_file,
Py_Initialize();
PySys_SetArgv(_py_argc, _py_argv);
// Open the error output before we do too much more.
_error_log.open_write(error_handle);
if (_error_log) {
// Set up the indicated error log as the Notify output.
_error_log.setf(ios::unitbuf);
Notify::ptr()->set_ostream_ptr(&_error_log, false);
}
// Open the pipe streams with the input and output handles from the
// parent.
_pipe_read.open_read(input);
_pipe_write.open_write(output);
_pipe_read.open_read(input_handle);
_pipe_write.open_write(output_handle);
if (!_pipe_read) {
nout << "unable to open read pipe\n";
@ -96,6 +107,10 @@ P3DPythonRun::
join_read_thread();
DESTROY_LOCK(_commands_lock);
// Restore the notify stream in case it tries to write to anything
// else after our shutdown.
Notify::ptr()->set_ostream_ptr(&cerr, false);
}
////////////////////////////////////////////////////////////////////
@ -301,14 +316,45 @@ run_python() {
Py_DECREF(check_comm);
// Finally, get lost in taskMgr.run().
bool okflag = true;
PyObject *done = PyObject_CallMethod(_taskMgr, (char *)"run", (char *)"");
if (done == NULL) {
PyErr_Print();
return false;
okflag = false;
} else {
Py_DECREF(done);
}
Py_DECREF(done);
return true;
if (_interactive_console) {
run_interactive_console();
okflag = true;
}
return okflag;
}
////////////////////////////////////////////////////////////////////
// Function: P3DPythonRun::run_interactive_console
// Access: Private
// Description: Gives the user a chance to type interactive Python
// commands, for easy development of a p3d application.
// This method is only called if "interactive_console=1"
// is set as a web token, and "allow_python_dev" is set
// within the application itself.
////////////////////////////////////////////////////////////////////
void P3DPythonRun::
run_interactive_console() {
// The "readline" module makes the Python prompt friendlier, with
// command history and everything. Simply importing it is
// sufficient.
PyObject *readline_module = PyImport_ImportModule("readline");
if (readline_module == NULL) {
PyErr_Print();
} else {
Py_DECREF(readline_module);
}
PyRun_InteractiveLoop(stdin, "<stdin>");
}
////////////////////////////////////////////////////////////////////
@ -1117,8 +1163,8 @@ set_p3d_filename(P3DCInstance *inst, TiXmlElement *xfparams) {
}
PyObject *result = PyObject_CallMethod
(_runner, (char *)"setP3DFilename", (char *)"sOOi", p3d_filename.c_str(),
token_list, arg_list, inst->get_instance_id());
(_runner, (char *)"setP3DFilename", (char *)"sOOii", p3d_filename.c_str(),
token_list, arg_list, inst->get_instance_id(), _interactive_console);
Py_DECREF(token_list);
Py_DECREF(arg_list);
@ -1552,20 +1598,3 @@ rt_thread_run() {
RELEASE_LOCK(_commands_lock);
}
}
////////////////////////////////////////////////////////////////////
// Function: run_p3dpython
// Description: This externally-visible function is the main entry
// point to this DLL, and it starts the whole thing
// running. Returns true on success, false on failure.
////////////////////////////////////////////////////////////////////
bool
run_p3dpython(const char *program_name, const char *archive_file,
FHandle input, FHandle output) {
P3DPythonRun::_global_ptr =
new P3DPythonRun(program_name, archive_file, input, output);
bool result = P3DPythonRun::_global_ptr->run_python();
delete P3DPythonRun::_global_ptr;
P3DPythonRun::_global_ptr = NULL;
return result;
}

View File

@ -67,12 +67,14 @@ using namespace std;
class P3DPythonRun {
public:
P3DPythonRun(const char *program_name, const char *archive_file,
FHandle input, FHandle output);
FHandle input_handle, FHandle output_handle,
FHandle error_handle, bool interactive_console);
~P3DPythonRun();
bool run_python();
private:
void run_interactive_console();
void handle_command(TiXmlDocument *doc);
void handle_pyobj_command(TiXmlElement *xcommand, bool needs_response,
int want_response_id);
@ -117,6 +119,7 @@ private:
Filename _archive_file;
int _py_argc;
char **_py_argv;
bool _interactive_console;
PyObject *_runner;
PyObject *_undefined_object_class;
@ -152,6 +155,7 @@ private:
HandleStream _pipe_read;
HandleStream _pipe_write;
HandleStream _error_log;
bool _read_thread_continue;
bool _program_continue;

View File

@ -706,20 +706,21 @@ start_p3dpython(P3DInstance *inst) {
nout << "Search path is " << search_path << "\n";
bool python_dev = false;
bool keep_pythonpath = false;
if (inst->_allow_python_dev) {
// If "allow_python_dev" is set in the instance's p3d_info.xml,
// *and* we have python_dev in the tokens, then we set python_dev
// true.
python_dev = (inst->get_fparams().lookup_token_int("python_dev") != 0);
// *and* we have keep_pythonpath in the tokens, then we set
// keep_pythonpath true.
keep_pythonpath = (inst->get_fparams().lookup_token_int("keep_pythonpath") != 0);
}
string dyld_path = search_path;
string python_path = search_path;
string prc_path = search_path;
if (python_dev) {
// With python_dev true, we preserve the PYTHONPATH setting from
// the caller's environment; in fact, we put it in the front.
if (keep_pythonpath) {
// With keep_pythonpath true, we preserve the PYTHONPATH setting
// from the caller's environment; in fact, we put it in the front.
// This allows the caller's on-disk Python files to shadow the
// similar-named files in the p3d file, allowing easy iteration on
// the code in the p3d file.
@ -741,7 +742,7 @@ start_p3dpython(P3DInstance *inst) {
prc_path += search_path;
}
nout << "python_dev is true\n"
nout << "keep_pythonpath is true\n"
<< "PYTHONPATH set to: " << python_path << "\n"
<< "PRC_PATH set to: " << prc_path << "\n";
}
@ -766,6 +767,31 @@ start_p3dpython(P3DInstance *inst) {
#else
_p3dpython_dll += ".so";
#endif
} else {
// We have a custom path to libp3dpython.dylib etc., for
// development.
#ifdef __APPLE__
// For some bizarre reason, Apple's dlopen() goes out of its way to
// ignore whatever full path you specify, and always searches for
// the file's basename along $DYLD_LIBRARY_PATH. Weird. To work
// around this and load the full path we're actually asking for, we
// have to ensure that our desired path appears first on
// $DYLD_LIBRARY_PATH.
// This may also inadvertently put other (incorrect) files first
// on the path, but presumably this won't cause too much trouble,
// since the user is in development mode anyway and maybe won't
// mind.
size_t slash = _p3dpython_dll.rfind('/');
if (slash != string::npos) {
string dirname = _p3dpython_dll.substr(0, slash);
cerr << "dirname is " << dirname << "\n";
dyld_path = dirname + ":" + dyld_path;
cerr << "dyld_path is " << dyld_path << "\n";
}
#endif // __APPLE__
}
// Populate the new process' environment.
@ -800,7 +826,7 @@ start_p3dpython(P3DInstance *inst) {
_env += '\0';
_env += "DYLD_LIBRARY_PATH=";
_env += search_path;
_env += dyld_path;
_env += '\0';
_env += "PYTHONPATH=";
@ -835,6 +861,23 @@ start_p3dpython(P3DInstance *inst) {
_env += '\0';
}
// Check for a few tokens that have special meaning at this level.
bool console_output = (inst->get_fparams().lookup_token_int("console_output") != 0);
bool one_process = (inst->get_fparams().lookup_token_int("one_process") != 0);
_interactive_console = (inst->get_fparams().lookup_token_int("interactive_console") != 0);
if (!inst->_allow_python_dev) {
// interactive_console is only allowed to be enabled if
// allow_python_dev is also set within the p3d file.
_interactive_console = false;
}
if (_interactive_console) {
// If we have interactive_console set, it follows we also need
// console_output.
console_output = true;
}
// Get the log filename from the p3d_info.xml file.
string log_basename = inst->_log_basename;
@ -843,9 +886,6 @@ start_p3dpython(P3DInstance *inst) {
log_basename = inst->get_fparams().lookup_token("log_basename");
}
bool console_output = (inst->get_fparams().lookup_token_int("console_output") != 0);
bool one_process = (inst->get_fparams().lookup_token_int("one_process") != 0);
#ifdef P3D_PLUGIN_LOG_BASENAME3
if (log_basename.empty()) {
// No log_basename specified for the app; use the compiled-in
@ -899,8 +939,8 @@ start_p3dpython(P3DInstance *inst) {
SetHandleInformation(r_from, HANDLE_FLAG_INHERIT, 0);
}
_output = w_from;
_input = r_to;
_output_handle = w_from;
_input_handle = r_to;
_pipe_read.open_read(r_from);
_pipe_write.open_write(w_to);
@ -915,23 +955,57 @@ start_p3dpython(P3DInstance *inst) {
perror("failed to create pipe");
}
_input = to_fd[0];
_output = from_fd[1];
_input_handle = to_fd[0];
_output_handle = from_fd[1];
_pipe_read.open_read(from_fd[0]);
_pipe_write.open_write(to_fd[1]);
#endif // _WIN32
// Get the filename to the Panda3D multifile. We need to pass this
// Create the error stream for log output. This means opening the
// logfile, if we have one, or keeping the standard error if we
// don't.
_got_error_handle = false;
#ifdef _WIN32
_error_handle = GetStdHandle(STD_ERROR_HANDLE);
if (!_log_pathname.empty()) {
HANDLE handle = CreateFile
(_log_pathname.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);
_got_error_handle = true;
} else {
nout << "Unable to open " << _log_pathname << "\n";
}
}
#else // _WIN32
_error_handle = STDERR_FILENO;
if (!_log_pathname.empty()) {
int logfile_fd = open(_log_pathname.c_str(),
O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (logfile_fd < 0) {
nout << "Unable to open " << _log_pathname << "\n";
} else {
_error_handle = logfile_fd;
_got_error_handle = true;
}
}
#endif // _WIN32
// Get the filename of the Panda3D multifile. We need to pass this
// to p3dpython.
_mf_filename = inst->_panda3d->get_archive_file_pathname();
nout << "Attempting to start python from " << _p3dpython_exe
<< " and " << _p3dpython_dll << "\n";
bool started_p3dpython;
if (one_process) {
nout << "one_process is set; running Python within parent process.\n";
started_p3dpython = false;
} else {
nout << "Attempting to start python from " << _p3dpython_exe
<< " and " << _p3dpython_dll << "\n";
#ifdef _WIN32
_p3dpython_handle = win_create_process();
started_p3dpython = (_p3dpython_handle != INVALID_HANDLE_VALUE);
@ -1130,42 +1204,19 @@ rt_terminate() {
////////////////////////////////////////////////////////////////////
HANDLE P3DSession::
win_create_process() {
HANDLE error_handle = GetStdHandle(STD_ERROR_HANDLE);
bool got_log_pathname = !_log_pathname.empty();
if (got_log_pathname) {
// Open the named file for output and redirect the child's stderr
// into it.
HANDLE handle = CreateFile
(_log_pathname.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 " << _log_pathname << "\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 = error_handle;
startup_info.hStdOutput = _output;
startup_info.hStdInput = _input;
startup_info.dwFlags |= STARTF_USESTDHANDLES;
// Make sure the "python" console window is hidden.
startup_info.wShowWindow = SW_HIDE;
startup_info.dwFlags |= STARTF_USESHOWWINDOW;
// If the start directory is empty, meaning not to change the
// current directory, then pass NULL in to CreateProcess().
// If _use_start_dir is false, meaning not to change the current
// directory, then pass NULL in to CreateProcess().
const char *start_dir_cstr = NULL;
if (_use_start_dir) {
start_dir_cstr = _start_dir.c_str();
@ -1175,7 +1226,9 @@ win_create_process() {
// command-line arguments.
ostringstream stream;
stream << "\"" << _p3dpython_exe << "\" \"" << _p3dpython_dll
<< "\" \"" << _mf_filename << "\"";
<< "\" \"" << _mf_filename << "\" \"" << _input_handle
<< "\" \"" << _output_handle << "\" \"" << _error_handle
<< "\" \"" << _interactive_console << "\"";
// I'm not sure why CreateProcess wants a non-const char pointer for
// its command-line string, but I'm not taking chances. It gets a
@ -1194,10 +1247,10 @@ win_create_process() {
delete[] command_line;
// Close the pipe handles that are now owned by the child.
CloseHandle(_output);
CloseHandle(_input);
if (got_log_pathname) {
CloseHandle(error_handle);
CloseHandle(_output_handle);
CloseHandle(_input_handle);
if (_got_error_handle) {
CloseHandle(_error_handle);
}
if (!started_program) {
@ -1227,8 +1280,8 @@ win_create_process() {
// process's standard output and standard input,
// respectively.
//
// Returns the handle to the created process on success,
// or INVALID_HANDLE_VALUE on falure.
// Returns the pid of the created process on success, or
// -1 on falure.
////////////////////////////////////////////////////////////////////
int P3DSession::
posix_create_process() {
@ -1241,24 +1294,8 @@ posix_create_process() {
if (child == 0) {
// Here we are in the child process.
bool got_log_pathname = !_log_pathname.empty();
if (got_log_pathname) {
// Open the named file for output and redirect the child's stderr
// into it.
int logfile_fd = open(_log_pathname.c_str(),
O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (logfile_fd < 0) {
nout << "Unable to open " << _log_pathname << "\n";
} 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(_input, STDIN_FILENO);
dup2(_output, STDOUT_FILENO);
// Close the parent's ends of the pipes.
_pipe_read.close();
_pipe_write.close();
@ -1281,15 +1318,33 @@ posix_create_process() {
}
ptrs.push_back((char *)NULL);
stringstream input_handle_stream;
input_handle_stream << _input_handle;
string input_handle_str = input_handle_stream.str();
stringstream output_handle_stream;
output_handle_stream << _output_handle;
string output_handle_str = output_handle_stream.str();
stringstream error_handle_stream;
error_handle_stream << _error_handle;
string error_handle_str = error_handle_stream.str();
execle(_p3dpython_exe.c_str(),
_p3dpython_exe.c_str(), _p3dpython_dll.c_str(), _mf_filename.c_str(), (char *)0,
&ptrs[0]);
_p3dpython_exe.c_str(), _p3dpython_dll.c_str(),
_mf_filename.c_str(), input_handle_str.c_str(),
output_handle_str.c_str(), error_handle_str.c_str(),
_interactive_console ? "1" : "0", (char *)0, &ptrs[0]);
nout << "Failed to exec " << _p3dpython_exe << "\n";
_exit(1);
}
close(_input);
close(_output);
// Close the handles that are now owned by the child.
close(_input_handle);
close(_output_handle);
if (_got_error_handle) {
close(_error_handle);
}
return child;
}
@ -1307,8 +1362,12 @@ p3dpython_thread_run() {
nout << "running p3dpython_thread_run()\n";
// Set the environment. Hopefully this won't be too destructive to
// the current process, and hopefully these changes will be read
// properly by Python.
// the current process.
// Note that on OSX at least, changing the DYLD_LIBRARY_PATH after
// the process has started has no effect (and furthermore you can't
// specify a full path to dlopen() calls), so this whole one-process
// approach is fatally flawed on OSX.
size_t p = 0;
size_t zero = _env.find('\0', p);
while (zero != string::npos) {
@ -1340,7 +1399,7 @@ p3dpython_thread_run() {
#else // _WIN32
// Posix case.
void *module = dlopen(_p3dpython_dll.c_str(), RTLD_GLOBAL);
void *module = dlopen(_p3dpython_dll.c_str(), RTLD_LOCAL);
if (module == NULL) {
// Couldn't load the .so.
nout << "Couldn't load " << _p3dpython_dll << "\n";
@ -1358,7 +1417,8 @@ p3dpython_thread_run() {
}
if (!run_p3dpython(_p3dpython_dll.c_str(), _mf_filename.c_str(),
_input, _output)) {
_input_handle, _output_handle, _error_handle,
_interactive_console)) {
nout << "Failure on startup.\n";
}
}

View File

@ -100,7 +100,9 @@ private:
string _p3dpython_dll;
string _mf_filename;
string _env;
FHandle _input, _output;
FHandle _input_handle, _output_handle, _error_handle;
bool _got_error_handle;
bool _interactive_console;
typedef map<int, P3DInstance *> Instances;
Instances _instances;

View File

@ -0,0 +1,35 @@
// Filename: run_p3dpython.cxx
// Created by: drose (29Aug09)
//
////////////////////////////////////////////////////////////////////
//
// 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 "run_p3dpython.h"
#include "p3dPythonRun.h"
////////////////////////////////////////////////////////////////////
// Function: run_p3dpython
// Description: This externally-visible function is the main entry
// point to this DLL, and it starts the whole thing
// running. Returns true on success, false on failure.
////////////////////////////////////////////////////////////////////
bool
run_p3dpython(const char *program_name, const char *archive_file,
FHandle input_handle, FHandle output_handle,
FHandle error_handle, bool interactive_console) {
P3DPythonRun::_global_ptr =
new P3DPythonRun(program_name, archive_file, input_handle, output_handle,
error_handle, interactive_console);
bool result = P3DPythonRun::_global_ptr->run_python();
delete P3DPythonRun::_global_ptr;
P3DPythonRun::_global_ptr = NULL;
return result;
}

View File

@ -26,12 +26,15 @@
#define EXPCL_P3DPYTHON
#endif
typedef bool run_p3dpython_func(const char *program_name, const char *archive_file,
FHandle input, FHandle output);
typedef bool
run_p3dpython_func(const char *program_name, const char *archive_file,
FHandle input_handle, FHandle output_handle,
FHandle error_handle, bool interactive_console);
EXPCL_P3DPYTHON extern "C" bool
run_p3dpython(const char *program_name, const char *archive_file,
FHandle input, FHandle output);
FHandle input_handle, FHandle output_handle,
FHandle error_handle, bool interactive_console);
#endif

View File

@ -25,6 +25,8 @@
#include <sstream>
#ifdef _WIN32
#include <windows.h>
#else
#include <signal.h>
#endif
#ifndef HAVE_GETOPT
@ -64,7 +66,7 @@ run(int argc, char *argv[]) {
// We prefix a "+" sign to tell gnu getopt not to parse options
// following the first not-option parameter. (These will be passed
// into the sub-process.)
const char *optstr = "+mu:p:fw:t:s:o:l:Dh";
const char *optstr = "+mu:p:fw:t:s:o:l:ih";
bool allow_multiple = false;
string download_url = PANDA_PACKAGE_HOST_URL;
@ -136,12 +138,26 @@ run(int argc, char *argv[]) {
_log_basename = "panda3d";
break;
case 'D':
case 'i':
{
P3D_token token;
token._keyword = "python_dev";
token._keyword = "keep_pythonpath";
token._value = "1";
_tokens.push_back(token);
token._keyword = "interactive_console";
token._value = "1";
_tokens.push_back(token);
#ifndef _WIN32
// We should also ignore SIGINT in this case, so that a
// control-C operation will be delivered to the subordinate
// Python process and return to a command shell, and won't
// just kill the panda3d process.
struct sigaction ignore;
memset(&ignore, 0, sizeof(ignore));
ignore.sa_handler = SIG_IGN;
sigaction(SIGINT, &ignore, NULL);
#endif // _WIN32
}
break;
@ -789,10 +805,11 @@ usage() {
<< " if a new version is available. Normally, this is done only\n"
<< " if contents.xml cannot be read.\n\n"
<< " -D\n"
<< " Request python_dev mode. This requires that the application was\n"
<< " also built with -D on the packp3d command line. If so, this will\n"
<< " preserve the PYTHONPATH environment variable from the user's\n"
<< " -i\n"
<< " Runs the application interactively. This requires that the application\n"
<< " was built with -D on the packp3d command line. If so, this option will\n"
<< " create an interactive Python prompt after the application has loaded.\n"
<< " It will also the PYTHONPATH environment variable from the user's\n"
<< " environment, allowing Python files on disk to shadow the same-named\n"
<< " Python files within the p3d file, for rapid iteration on the Python\n"
<< " code.\n\n"

View File

@ -832,8 +832,14 @@ class ShowBase(DirectObject.DirectObject):
self.win.setClearStencilActive(oldClearStencilActive)
self.win.setClearStencil(oldClearStencil)
self.setFrameRateMeter(self.config.GetBool(
'show-frame-rate-meter', 0))
flag = self.config.GetBool('show-frame-rate-meter', False)
if self.appRunner is not None and self.appRunner.allowPythonDev:
# In an allow_python_dev p3d application, we always
# start up with the frame rate meter enabled, to
# provide a visual reminder that this flag has been
# set.
flag = True
self.setFrameRateMeter(flag)
return success
def setSleep(self, amount):
@ -2477,13 +2483,12 @@ class ShowBase(DirectObject.DirectObject):
self.startDirect(fWantDirect = fDirect, fWantTk = fTk, fWantWx = fWx)
def run(self):
# This method only does anything when self.appRunner is None,
# which is to say, when we are not running from within a p3d
# file. When we *are* within a p3d file, the Panda runtime
# has to be responsible for running the main loop, so we can't
# allow the application to do it. This is a minor hack, but
# should work for 99% of the cases.
if self.appRunner is None or self.appRunner.dummy:
# This method runs the TaskManager when self.appRunner is
# None, which is to say, when we are not running from within a
# p3d file. When we *are* within a p3d file, the Panda
# runtime has to be responsible for running the main loop, so
# we can't allow the application to do it.
if self.appRunner is None or self.appRunner.dummy or self.appRunner.interactiveConsole:
self.taskMgr.run()