From 38c00a66f93a93be066db682d33631d5972fc52c Mon Sep 17 00:00:00 2001 From: David Rose Date: Tue, 30 Jun 2009 20:42:57 +0000 Subject: [PATCH] restructure for OOP, use polling instead of threads --- direct/src/plugin_standalone/panda3d.cxx | 718 +++++++++++++---------- 1 file changed, 395 insertions(+), 323 deletions(-) diff --git a/direct/src/plugin_standalone/panda3d.cxx b/direct/src/plugin_standalone/panda3d.cxx index 8dfabdee00..06a7451144 100644 --- a/direct/src/plugin_standalone/panda3d.cxx +++ b/direct/src/plugin_standalone/panda3d.cxx @@ -12,31 +12,14 @@ // //////////////////////////////////////////////////////////////////// -// This program must link with Panda for HTTPClient support. This -// means it probably should be built with LINK_ALL_STATIC defined, so -// we won't have to deal with confusing .dll or .so files that might -// compete on the disk with the dynamically-loaded versions. There's -// no competition in memory address space, though, because -// p3d_plugin--the only file we dynamically link in--doesn't itself -// link with Panda. - -#include "pandabase.h" +#include "panda3d.h" +#include "httpClient.h" +#include "load_plugin.h" #ifdef _WIN32 #include -#else -#include #endif -#include "httpClient.h" -#include "httpChannel.h" -#include "ramfile.h" -#include "thread.h" -#include "pset.h" - -#include "p3d_plugin.h" -#include "load_plugin.h" - #ifndef HAVE_GETOPT #include "gnu_getopt.h" #else @@ -45,302 +28,24 @@ #endif #endif -typedef pset Instances; -Instances _instances; -class URLGetterThread : public Thread { -public: - URLGetterThread(P3D_instance *instance, - int unique_id, - const URLSpec &url, - const string &post_data); -protected: - virtual void thread_main(); - -private: - P3D_instance *_instance; - int _unique_id; - URLSpec _url; - string _post_data; -}; - -URLGetterThread:: -URLGetterThread(P3D_instance *instance, - int unique_id, - const URLSpec &url, - const string &post_data) : - Thread(url, "URLGetter"), - _instance(instance), - _unique_id(unique_id), - _url(url), - _post_data(post_data) -{ +//////////////////////////////////////////////////////////////////// +// Function: Panda3D::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +Panda3D:: +Panda3D() { } -void URLGetterThread:: -thread_main() { - HTTPClient *http = HTTPClient::get_global_ptr(); - - cerr << "Getting URL " << _url << "\n"; - - PT(HTTPChannel) channel = http->make_channel(false); - if (_post_data.empty()) { - channel->begin_get_document(_url); - } else { - channel->begin_post_form(_url, _post_data); - } - - Ramfile rf; - channel->download_to_ram(&rf); - - size_t bytes_sent = 0; - while (channel->run() || rf.get_data_size() != 0) { - if (rf.get_data_size() != 0) { - // Got some new data. - bool download_ok = P3D_instance_feed_url_stream - (_instance, _unique_id, P3D_RC_in_progress, - channel->get_status_code(), - channel->get_file_size(), - (const unsigned char *)rf.get_data().data(), rf.get_data_size()); - bytes_sent += rf.get_data_size(); - rf.clear(); - - if (!download_ok) { - // The plugin doesn't care any more. Interrupt the download. - cerr << "Download interrupted: " << _url - << ", after " << bytes_sent << " of " << channel->get_file_size() - << " bytes.\n"; - return; - } - } - } - - // All done. - P3D_result_code status = P3D_RC_done; - if (!channel->is_valid()) { - if (channel->get_status_code() != 0) { - status = P3D_RC_http_error; - } else { - status = P3D_RC_generic_error; - } - cerr << "Error getting URL " << _url << "\n"; - } else { - cerr << "Done getting URL " << _url << ", got " << bytes_sent << " bytes\n"; - } - - P3D_instance_feed_url_stream - (_instance, _unique_id, status, - channel->get_status_code(), - bytes_sent, NULL, 0); -} - -void -handle_request(P3D_request *request) { - bool handled = false; - - switch (request->_request_type) { - case P3D_RT_stop: - cerr << "Got P3D_RT_stop\n"; - P3D_instance_finish(request->_instance); - _instances.erase(request->_instance); -#ifdef _WIN32 - // Post a silly message to spin the event loop. - PostMessage(NULL, WM_USER, 0, 0); -#endif - handled = true; - break; - - case P3D_RT_get_url: - cerr << "Got P3D_RT_get_url\n"; - { - PT(URLGetterThread) thread = new URLGetterThread - (request->_instance, request->_request._get_url._unique_id, - URLSpec(request->_request._get_url._url), ""); - thread->start(TP_normal, false); - } - break; - - case P3D_RT_post_url: - cerr << "Got P3D_RT_post_url\n"; - { - PT(URLGetterThread) thread = new URLGetterThread - (request->_instance, request->_request._post_url._unique_id, - URLSpec(request->_request._post_url._url), - string(request->_request._post_url._post_data, request->_request._post_url._post_data_size)); - thread->start(TP_normal, false); - } - break; - - case P3D_RT_notify: - // Ignore notifications. - break; - - default: - // Some request types are not handled. - cerr << "Unhandled request: " << request->_request_type << "\n"; - break; - }; - - P3D_request_finish(request, handled); -} - -#ifdef _WIN32 -LONG WINAPI -window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { - switch (msg) { - case WM_DESTROY: - PostQuitMessage(0); - break; - }; - - return DefWindowProc(hwnd, msg, wparam, lparam); -} - -void -make_parent_window(P3D_window_handle &parent_window, - int win_width, int win_height) { - WNDCLASS wc; - - HINSTANCE application = GetModuleHandle(NULL); - ZeroMemory(&wc, sizeof(WNDCLASS)); - wc.lpfnWndProc = window_proc; - wc.hInstance = application; - wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); - wc.lpszClassName = "panda3d"; - - if (!RegisterClass(&wc)) { - cerr << "Could not register window class!\n"; - exit(1); - } - - DWORD window_style = - WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | - WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | - WS_SIZEBOX | WS_MAXIMIZEBOX; - - HWND toplevel_window = - CreateWindow("panda3d", "Panda3D", window_style, - CW_USEDEFAULT, CW_USEDEFAULT, win_width, win_height, - NULL, NULL, application, 0); - if (!toplevel_window) { - cerr << "Could not create toplevel window!\n"; - exit(1); - } - - ShowWindow(toplevel_window, SW_SHOWNORMAL); - - parent_window._hwnd = toplevel_window; -} -#endif // _WIN32 - -#ifdef __APPLE__ - -void -make_parent_window(P3D_window_handle &parent_window, - int win_width, int win_height) { - // TODO. - assert(false); -} - -#endif // __APPLE__ - -P3D_instance * -create_instance(const string &arg, P3D_window_type window_type, - int win_x, int win_y, int win_width, int win_height, - P3D_window_handle parent_window, - const Filename &output_filename) { - - string os_output_filename = output_filename.to_os_specific(); - P3D_token tokens[] = { - { "output_filename", os_output_filename.c_str() }, - { "src", arg.c_str() }, - }; - int num_tokens = sizeof(tokens) / sizeof(P3D_token); - - // If the supplied parameter name is a real file, pass it in on the - // parameter list. Otherwise, assume it's a URL and let the plugin - // download it. - Filename p3d_filename = Filename::from_os_specific(arg); - string os_p3d_filename; - if (p3d_filename.exists()) { - p3d_filename.make_absolute(); - os_p3d_filename = p3d_filename.to_os_specific(); - } - - P3D_instance *inst = P3D_new_instance(NULL, NULL); - - if (inst != NULL) { - P3D_instance_setup_window - (inst, window_type, win_x, win_y, win_width, win_height, parent_window); - P3D_instance_start(inst, os_p3d_filename.c_str(), tokens, num_tokens); - } - - return inst; -} - -void -usage() { - cerr - << "\nUsage:\n" - << " panda3d [opts] file.p3d [file_b.p3d file_c.p3d ...]\n\n" - - << "This program is used to execute a Panda3D application bundle stored\n" - << "in a .p3d file. Normally you only run one p3d bundle at a time,\n" - << "but it is possible to run multiple bundles simultaneously.\n\n" - - << "Options:\n\n" - - << " -p " << get_plugin_basename() << "\n" - << " Specify the full path to the particular Panda plugin DLL to\n" - << " run. Normally, this will be found by searching in the usual\n" - << " places.\n\n" - - << " -l output.log\n" - << " Specify the name of the file to receive the log output of the\n" - << " plugin process(es). The default is to send this output to the\n" - << " console.\n\n" - - << " -t [toplevel|embedded|fullscreen|hidden]\n" - << " Specify the type of graphic window to create. If you specify\n" - << " \"embedded\", a new window is created to be the parent.\n\n" - - << " -s width,height\n" - << " Specify the size of the graphic window.\n\n" - - << " -o x,y\n" - << " Specify the position (origin) of the graphic window on the\n" - << " screen, or on the parent window.\n\n"; -} - -bool -parse_int_pair(char *arg, int &x, int &y) { - char *endptr; - x = strtol(arg, &endptr, 10); - if (*endptr == ',') { - y = strtol(endptr + 1, &endptr, 10); - if (*endptr == '\0') { - return true; - } - } - - // Some parse error on the string. - return false; -} - -int -main(int argc, char *argv[]) { -/* -int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { - char *targv[] = { - "panda3d", - "-tembedded", - "c:/cygwin/home/drose/ralph.p3d", - NULL, - }; - char **argv = targv; - int argc = 3; -*/ - +//////////////////////////////////////////////////////////////////// +// Function: Panda3D::run +// Access: Public +// Description: Starts the program going. Returns 0 on success, +// nonzero on failure. +//////////////////////////////////////////////////////////////////// +int Panda3D:: +run(int argc, char *argv[]) { extern char *optarg; extern int optind; const char *optstr = "p:l:t:s:o:h"; @@ -500,10 +205,10 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { inst = P3D_check_request(false); } - while (!PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) { - // spin, so we don't starve the download threads. - // TODO: use a better mechanism here. - Thread::force_yield(); + while (!_url_getters.empty() && + !PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) { + // If there are no Windows messages, check the download tasks. + run_getters(); } retval = GetMessage(&msg, NULL, 0, 0); } @@ -511,11 +216,10 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { cerr << "WM_QUIT\n"; // WM_QUIT has been received. Terminate all instances, and fall // through. - Instances::iterator ii; - for (ii = _instances.begin(); ii != _instances.end(); ++ii) { - P3D_instance_finish(*ii); + while (!_instances.empty()) { + P3D_instance *inst = *(_instances.begin()); + delete_instance(inst); } - _instances.clear(); } else { // Not an embedded window, so we don't have our own window to @@ -528,7 +232,7 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { handle_request(request); } } - Thread::force_yield(); + run_getters(); } } @@ -543,10 +247,378 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { handle_request(request); } } - Thread::force_yield(); + run_getters(); } // All instances have finished; we can exit. return 0; } + + +//////////////////////////////////////////////////////////////////// +// Function: Panda3D::run_getters +// Access: Private +// Description: Polls all of the active URL requests. +//////////////////////////////////////////////////////////////////// +void Panda3D:: +run_getters() { + URLGetters::iterator gi; + gi = _url_getters.begin(); + while (gi != _url_getters.end()) { + URLGetter *getter = (*gi); + if (getter->run()) { + // This URLGetter is still working. Leave it. + ++gi; + } else { + // This URLGetter is done. Remove it and delete it. + URLGetters::iterator dgi = gi; + ++gi; + _url_getters.erase(dgi); + delete getter; + } + } +} + +//////////////////////////////////////////////////////////////////// +// Function: Panda3D::handle_request +// Access: Private +// Description: Handles a single request received via the plugin API +// from a p3d instance. +//////////////////////////////////////////////////////////////////// +void Panda3D:: +handle_request(P3D_request *request) { + bool handled = false; + + switch (request->_request_type) { + case P3D_RT_stop: + cerr << "Got P3D_RT_stop\n"; + delete_instance(request->_instance); +#ifdef _WIN32 + // Post a silly message to spin the event loop. + PostMessage(NULL, WM_USER, 0, 0); +#endif + handled = true; + break; + + case P3D_RT_get_url: + cerr << "Got P3D_RT_get_url\n"; + { + URLGetter *getter = new URLGetter + (request->_instance, request->_request._get_url._unique_id, + URLSpec(request->_request._get_url._url), ""); + _url_getters.insert(getter); + } + break; + + case P3D_RT_post_url: + cerr << "Got P3D_RT_post_url\n"; + { + URLGetter *getter = new URLGetter + (request->_instance, request->_request._post_url._unique_id, + URLSpec(request->_request._post_url._url), + string(request->_request._post_url._post_data, request->_request._post_url._post_data_size)); + _url_getters.insert(getter); + } + break; + + case P3D_RT_notify: + // Ignore notifications. + break; + + default: + // Some request types are not handled. + cerr << "Unhandled request: " << request->_request_type << "\n"; + break; + }; + + P3D_request_finish(request, handled); +} + +#ifdef _WIN32 +LONG WINAPI +window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + switch (msg) { + case WM_DESTROY: + PostQuitMessage(0); + break; + }; + + return DefWindowProc(hwnd, msg, wparam, lparam); +} + +//////////////////////////////////////////////////////////////////// +// Function: Panda3D::make_parent_window +// Access: Private +// Description: Creates a toplevel window to contain the embedded +// instances. Windows implementation. +//////////////////////////////////////////////////////////////////// +void Panda3D:: +make_parent_window(P3D_window_handle &parent_window, + int win_width, int win_height) { + WNDCLASS wc; + + HINSTANCE application = GetModuleHandle(NULL); + ZeroMemory(&wc, sizeof(WNDCLASS)); + wc.lpfnWndProc = window_proc; + wc.hInstance = application; + wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); + wc.lpszClassName = "panda3d"; + + if (!RegisterClass(&wc)) { + cerr << "Could not register window class!\n"; + exit(1); + } + + DWORD window_style = + WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | + WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | + WS_SIZEBOX | WS_MAXIMIZEBOX; + + HWND toplevel_window = + CreateWindow("panda3d", "Panda3D", window_style, + CW_USEDEFAULT, CW_USEDEFAULT, win_width, win_height, + NULL, NULL, application, 0); + if (!toplevel_window) { + cerr << "Could not create toplevel window!\n"; + exit(1); + } + + ShowWindow(toplevel_window, SW_SHOWNORMAL); + + parent_window._hwnd = toplevel_window; +} +#endif // _WIN32 + +#ifdef __APPLE__ + +//////////////////////////////////////////////////////////////////// +// Function: Panda3D::make_parent_window +// Access: Private +// Description: Creates a toplevel window to contain the embedded +// instances. OS X implementation. +//////////////////////////////////////////////////////////////////// +void Panda3D:: +make_parent_window(P3D_window_handle &parent_window, + int win_width, int win_height) { + // TODO. + assert(false); +} + +#endif // __APPLE__ + +//////////////////////////////////////////////////////////////////// +// Function: Panda3D::create_instance +// Access: Private +// Description: Uses the plugin API to create a new P3D instance to +// play a particular .p3d file. +//////////////////////////////////////////////////////////////////// +P3D_instance *Panda3D:: +create_instance(const string &arg, P3D_window_type window_type, + int win_x, int win_y, int win_width, int win_height, + P3D_window_handle parent_window, + const Filename &output_filename) { + + string os_output_filename = output_filename.to_os_specific(); + P3D_token tokens[] = { + { "output_filename", os_output_filename.c_str() }, + { "src", arg.c_str() }, + }; + int num_tokens = sizeof(tokens) / sizeof(P3D_token); + + // If the supplied parameter name is a real file, pass it in on the + // parameter list. Otherwise, assume it's a URL and let the plugin + // download it. + Filename p3d_filename = Filename::from_os_specific(arg); + string os_p3d_filename; + if (p3d_filename.exists()) { + p3d_filename.make_absolute(); + os_p3d_filename = p3d_filename.to_os_specific(); + } + + P3D_instance *inst = P3D_new_instance(NULL, NULL); + + if (inst != NULL) { + P3D_instance_setup_window + (inst, window_type, win_x, win_y, win_width, win_height, parent_window); + P3D_instance_start(inst, os_p3d_filename.c_str(), tokens, num_tokens); + } + + return inst; +} + +//////////////////////////////////////////////////////////////////// +// Function: Panda3D::delete_instance +// Access: Private +// Description: Deletes the indicated instance and removes it from +// the internal structures. +//////////////////////////////////////////////////////////////////// +void Panda3D:: +delete_instance(P3D_instance *inst) { + P3D_instance_finish(inst); + _instances.erase(inst); + + // Make sure we also terminate any pending URLGetters associated + // with this instance. + URLGetters::iterator gi; + gi = _url_getters.begin(); + while (gi != _url_getters.end()) { + URLGetter *getter = (*gi); + if (getter->get_instance() == inst) { + URLGetters::iterator dgi = gi; + ++gi; + _url_getters.erase(dgi); + delete getter; + } + } +} + +//////////////////////////////////////////////////////////////////// +// Function: Panda3D::usage +// Access: Private +// Description: Reports the available command-line options. +//////////////////////////////////////////////////////////////////// +void Panda3D:: +usage() { + cerr + << "\nUsage:\n" + << " panda3d [opts] file.p3d [file_b.p3d file_c.p3d ...]\n\n" + + << "This program is used to execute a Panda3D application bundle stored\n" + << "in a .p3d file. Normally you only run one p3d bundle at a time,\n" + << "but it is possible to run multiple bundles simultaneously.\n\n" + + << "Options:\n\n" + + << " -p " << get_plugin_basename() << "\n" + << " Specify the full path to the particular Panda plugin DLL to\n" + << " run. Normally, this will be found by searching in the usual\n" + << " places.\n\n" + + << " -l output.log\n" + << " Specify the name of the file to receive the log output of the\n" + << " plugin process(es). The default is to send this output to the\n" + << " console.\n\n" + + << " -t [toplevel|embedded|fullscreen|hidden]\n" + << " Specify the type of graphic window to create. If you specify\n" + << " \"embedded\", a new window is created to be the parent.\n\n" + + << " -s width,height\n" + << " Specify the size of the graphic window.\n\n" + + << " -o x,y\n" + << " Specify the position (origin) of the graphic window on the\n" + << " screen, or on the parent window.\n\n"; +} + +//////////////////////////////////////////////////////////////////// +// Function: Panda3D::parse_int_pair +// Access: Private +// Description: Parses a string into an x,y pair of integers. +// Returns true on success, false on failure. +//////////////////////////////////////////////////////////////////// +bool Panda3D:: +parse_int_pair(char *arg, int &x, int &y) { + char *endptr; + x = strtol(arg, &endptr, 10); + if (*endptr == ',') { + y = strtol(endptr + 1, &endptr, 10); + if (*endptr == '\0') { + return true; + } + } + + // Some parse error on the string. + return false; +} + +//////////////////////////////////////////////////////////////////// +// Function: Panda3D::URLGetter::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +Panda3D::URLGetter:: +URLGetter(P3D_instance *instance, int unique_id, + const URLSpec &url, const string &post_data) : + _instance(instance), + _unique_id(unique_id), + _url(url), + _post_data(post_data) +{ + HTTPClient *http = HTTPClient::get_global_ptr(); + + cerr << "Getting URL " << _url << "\n"; + + _channel = http->make_channel(false); + if (_post_data.empty()) { + _channel->begin_get_document(_url); + } else { + _channel->begin_post_form(_url, _post_data); + } + + _channel->download_to_ram(&_rf); + _bytes_sent = 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: Panda3D::URLGetter::run +// Access: Public +// Description: Polls the URLGetter for new results. Returns true if +// the URL request is still in progress and run() should +// be called again later, or false if the URL request +// has been completed and run() should not be called +// again. +//////////////////////////////////////////////////////////////////// +bool Panda3D::URLGetter:: +run() { + if (_channel->run() || _rf.get_data_size() != 0) { + if (_rf.get_data_size() != 0) { + // Got some new data. + bool download_ok = P3D_instance_feed_url_stream + (_instance, _unique_id, P3D_RC_in_progress, + _channel->get_status_code(), + _channel->get_file_size(), + (const unsigned char *)_rf.get_data().data(), _rf.get_data_size()); + _bytes_sent += _rf.get_data_size(); + _rf.clear(); + + if (!download_ok) { + // The plugin doesn't care any more. Interrupt the download. + cerr << "Download interrupted: " << _url + << ", after " << _bytes_sent << " of " << _channel->get_file_size() + << " bytes.\n"; + return false; + } + } + + // Still more to come; call this method again later. + return true; + } + + // All done. + P3D_result_code status = P3D_RC_done; + if (!_channel->is_valid()) { + if (_channel->get_status_code() != 0) { + status = P3D_RC_http_error; + } else { + status = P3D_RC_generic_error; + } + cerr << "Error getting URL " << _url << "\n"; + } else { + cerr << "Done getting URL " << _url << ", got " << _bytes_sent << " bytes\n"; + } + + P3D_instance_feed_url_stream + (_instance, _unique_id, status, + _channel->get_status_code(), + _bytes_sent, NULL, 0); + return false; +} + + +int +main(int argc, char *argv[]) { + Panda3D program; + return program.run(argc, argv); +}