// Filename: p3dInstanceManager.cxx // Created by: drose (29May09) // //////////////////////////////////////////////////////////////////// // // 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 "p3dInstanceManager.h" #include "p3dInstance.h" #include "p3dSession.h" #include "p3dPackage.h" #include "p3d_plugin_config.h" #include "p3dWinSplashWindow.h" #include "p3dUndefinedObject.h" #include "p3dNoneObject.h" #include "p3dBoolObject.h" #include "find_root_dir.h" #include "mkdir_complete.h" #include "fileSpec.h" #include "get_tinyxml.h" // We can include this header file to get the DTOOL_PLATFORM // definition, even though we don't link with dtool. #include "dtool_platform.h" #ifdef _WIN32 #include #else //#include #endif P3DInstanceManager *P3DInstanceManager::_global_ptr; //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::Constructor // Access: Private // Description: //////////////////////////////////////////////////////////////////// P3DInstanceManager:: P3DInstanceManager() { _is_initialized = false; _unique_id = 0; _xcontents = NULL; _notify_thread_continue = false; _started_notify_thread = false; INIT_THREAD(_notify_thread); // Initialize the singleton objects. _undefined_object = new P3DUndefinedObject(); _none_object = new P3DNoneObject(); _true_object = new P3DBoolObject(true); _false_object = new P3DBoolObject(false); #ifdef _WIN32 // Ensure the appropriate Windows common controls are available to // this application. INITCOMMONCONTROLSEX icc; icc.dwSize = sizeof(icc); icc.dwICC = ICC_PROGRESS_CLASS; InitCommonControlsEx(&icc); #endif } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::Destructor // Access: Private // Description: //////////////////////////////////////////////////////////////////// P3DInstanceManager:: ~P3DInstanceManager() { if (_started_notify_thread) { _notify_ready.acquire(); _notify_thread_continue = false; _notify_ready.notify(); _notify_ready.release(); JOIN_THREAD(_notify_thread); _started_notify_thread = false; } if (_xcontents != NULL) { delete _xcontents; } assert(_instances.empty()); assert(_sessions.empty()); nout << "counts: " << _undefined_object->_ref_count << " " << _none_object->_ref_count << " " << _true_object->_ref_count << " " << _false_object->_ref_count << "\n"; /* assert(_undefined_object->_ref_count == 1); assert(_none_object->_ref_count == 1); assert(_true_object->_ref_count == 1); assert(_false_object->_ref_count == 1); */ P3D_OBJECT_DECREF(_undefined_object); P3D_OBJECT_DECREF(_none_object); P3D_OBJECT_DECREF(_true_object); P3D_OBJECT_DECREF(_false_object); #ifdef _WIN32 P3DWinSplashWindow::unregister_window_class(); #endif } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::initialize // Access: Public // Description: Called by the host at application startup. It // returns true if the DLL is successfully initialized, // false if it should be immediately shut down and // redownloaded. //////////////////////////////////////////////////////////////////// bool P3DInstanceManager:: initialize(const string &contents_filename, const string &download_url, const string &platform) { _root_dir = find_root_dir(); _download_url = download_url; if (_download_url.empty()) { _download_url = P3D_PLUGIN_DOWNLOAD; } _platform = platform; if (_platform.empty()) { _platform = DTOOL_PLATFORM; } // Ensure that the download URL ends with a slash. if (!_download_url.empty() && _download_url[_download_url.size() - 1] != '/') { _download_url += "/"; } nout << "_root_dir = " << _root_dir << ", contents = " << contents_filename << ", download = " << _download_url << ", platform = " << _platform << "\n"; if (_root_dir.empty()) { nout << "Could not find root directory.\n"; return false; } _is_initialized = true; // Attempt to read the supplied contents.xml file. if (!contents_filename.empty()) { if (!read_contents_file(contents_filename)) { nout << "Couldn't read " << contents_filename << "\n"; } } return true; } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::read_contents_file // Access: Public // Description: Reads the contents.xml file in the indicated // filename. On success, copies the contents.xml file // into the standard location. // // Returns true on success, false on failure. //////////////////////////////////////////////////////////////////// bool P3DInstanceManager:: read_contents_file(const string &contents_filename) { TiXmlDocument doc(contents_filename.c_str()); if (!doc.LoadFile()) { return false; } TiXmlElement *xcontents = doc.FirstChildElement("contents"); if (xcontents == NULL) { return false; } if (_xcontents != NULL) { delete _xcontents; } _xcontents = (TiXmlElement *)xcontents->Clone(); string standard_filename = _root_dir + "/contents.xml"; if (standard_filename != contents_filename) { copy_file(contents_filename, standard_filename); } return true; } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::create_instance // Access: Public // Description: Returns a newly-allocated P3DInstance with the // indicated startup information. //////////////////////////////////////////////////////////////////// P3DInstance *P3DInstanceManager:: create_instance(P3D_request_ready_func *func, const P3D_token tokens[], size_t num_tokens, void *user_data) { P3DInstance *inst = new P3DInstance(func, tokens, num_tokens, user_data); inst->ref(); _instances.insert(inst); return inst; } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::start_instance // Access: Public // Description: Actually starts the instance running. Before this // call, the instance is in an indeterminate state. It // is an error to call this more than once for a // particular instance. //////////////////////////////////////////////////////////////////// bool P3DInstanceManager:: start_instance(P3DInstance *inst, const string &p3d_filename) { if (inst->is_started()) { nout << "Instance started twice: " << inst << "\n"; return false; } inst->set_p3d_filename(p3d_filename); P3DSession *session; Sessions::iterator si = _sessions.find(inst->get_session_key()); if (si == _sessions.end()) { session = new P3DSession(inst); session->ref(); bool inserted = _sessions.insert(Sessions::value_type(session->get_session_key(), session)).second; assert(inserted); } else { session = (*si).second; } session->start_instance(inst); return inst->is_started(); } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::finish_instance // Access: Public // Description: Terminates and removes a previously-returned // instance. //////////////////////////////////////////////////////////////////// void P3DInstanceManager:: finish_instance(P3DInstance *inst) { Instances::iterator ii; ii = _instances.find(inst); assert(ii != _instances.end()); _instances.erase(ii); Sessions::iterator si = _sessions.find(inst->get_session_key()); if (si != _sessions.end()) { P3DSession *session = (*si).second; session->terminate_instance(inst); // If that was the last instance in this session, terminate the // session. if (session->get_num_instances() == 0) { _sessions.erase(session->get_session_key()); session->shutdown(); unref_delete(session); } } unref_delete(inst); } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::validate_instance // Access: Public // Description: Returns the P3DInstance pointer corresponding to the // indicated P3D_instance if it is valid, or NULL if it // is not. //////////////////////////////////////////////////////////////////// P3DInstance *P3DInstanceManager:: validate_instance(P3D_instance *instance) { Instances::iterator ii; ii = _instances.find((P3DInstance *)instance); if (ii != _instances.end()) { return (*ii); } return NULL; } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::check_request // Access: Public // Description: If a request is currently pending on any instance, // returns its pointer. Otherwise, returns NULL. //////////////////////////////////////////////////////////////////// P3DInstance *P3DInstanceManager:: check_request() { Instances::iterator ii; for (ii = _instances.begin(); ii != _instances.end(); ++ii) { P3DInstance *inst = (*ii); if (inst->has_request()) { return inst; } } return NULL; } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::wait_request // Access: Public // Description: Does not return until a request is pending on some // instance, or until no instances remain. Use // check_request to retrieve the pending request. Due // to the possibility of race conditions, it is possible // for this function to return when there is in fact no // request pending (another thread may have extracted // the request first). //////////////////////////////////////////////////////////////////// void P3DInstanceManager:: wait_request() { _request_ready.acquire(); while (true) { if (check_request() != (P3DInstance *)NULL) { _request_ready.release(); return; } if (_instances.empty()) { _request_ready.release(); return; } // No pending requests; go to sleep. _request_ready.wait(); } _request_ready.release(); } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::get_package // Access: Public // Description: Returns a (possibly shared) pointer to the indicated // package. //////////////////////////////////////////////////////////////////// P3DPackage *P3DInstanceManager:: get_package(const string &package_name, const string &package_version) { string package_platform = get_platform(); string key = package_name + "_" + package_platform + "_" + package_version; Packages::iterator pi = _packages.find(key); if (pi != _packages.end()) { return (*pi).second; } P3DPackage *package = new P3DPackage(package_name, package_platform, package_version); bool inserted = _packages.insert(Packages::value_type(key, package)).second; assert(inserted); return package; } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::get_package_desc_file // Access: Public // Description: Fills the indicated FileSpec with the hash // information for the package's desc file. Returns // true if successful, false if the package is unknown. // This requires has_contents_file() to return true in // order to be successful. //////////////////////////////////////////////////////////////////// bool P3DInstanceManager:: get_package_desc_file(FileSpec &desc_file, const string &package_name, const string &package_version) { if (_xcontents == NULL) { return false; } string package_platform = get_platform(); // Scan the contents data for the indicated package. TiXmlElement *xpackage = _xcontents->FirstChildElement("package"); while (xpackage != NULL) { const char *name = xpackage->Attribute("name"); const char *platform = xpackage->Attribute("platform"); const char *version = xpackage->Attribute("version"); if (name != NULL && platform != NULL && version != NULL && package_name == name && package_platform == platform && package_version == version) { // Here's the matching package definition. desc_file.load_xml(xpackage); return true; } xpackage = xpackage->NextSiblingElement("package"); } // Couldn't find the named package. return false; } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::get_unique_id // Access: Public // Description: Returns a number used to uniquify different // instances. This number is guaranteed to be different // at each call, at least until the int space rolls // over. //////////////////////////////////////////////////////////////////// int P3DInstanceManager:: get_unique_id() { ++_unique_id; return _unique_id; } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::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 P3DInstanceManager:: signal_request_ready(P3DInstance *inst) { if (inst->get_request_ready_func() != NULL) { // This instance requires asynchronous notifications of requests. // Thus, we should tell the notify thread to wake up and make the // callback. _notify_ready.acquire(); _notify_instances.push_back(inst); _notify_ready.notify(); _notify_ready.release(); // Oh, and we should spawn the thread if we haven't already. if (!_started_notify_thread) { _notify_thread_continue = true; SPAWN_THREAD(_notify_thread, nt_thread_run, this); _started_notify_thread = true; } } // Then, wake up the main thread, in case it's sleeping on // wait_request(). _request_ready.acquire(); _request_ready.notify(); _request_ready.release(); } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::make_class_definition // Access: Public // Description: //////////////////////////////////////////////////////////////////// P3D_class_definition *P3DInstanceManager:: make_class_definition() const { P3D_class_definition *new_class = new P3D_class_definition(P3DObject::_generic_class); // TODO: save this pointer so we can delete it on destruction. return new_class; } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::get_global_ptr // Access: Public, Static // Description: //////////////////////////////////////////////////////////////////// P3DInstanceManager *P3DInstanceManager:: get_global_ptr() { if (_global_ptr == NULL) { _global_ptr = new P3DInstanceManager; } return _global_ptr; } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::delete_global_ptr // Access: Public, Static // Description: This is called only at plugin shutdown time; it // deletes the global instance manager pointer and // clears it to NULL. //////////////////////////////////////////////////////////////////// void P3DInstanceManager:: delete_global_ptr() { if (_global_ptr != NULL) { delete _global_ptr; _global_ptr = NULL; } } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::copy_file // Access: Private // Description: Copies the data in the file named by from_filename // into the file named by to_filename. //////////////////////////////////////////////////////////////////// bool P3DInstanceManager:: copy_file(const string &from_filename, const string &to_filename) { ifstream in(from_filename.c_str(), ios::in | ios::binary); ofstream out(to_filename.c_str(), ios::out | ios::binary); static const size_t buffer_size = 4096; char buffer[buffer_size]; in.read(buffer, buffer_size); size_t count = in.gcount(); while (count != 0) { out.write(buffer, count); if (out.fail()) { return false; } in.read(buffer, buffer_size); count = in.gcount(); } if (!in.eof()) { return false; } return true; } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::nt_thread_run // Access: Private // Description: The main function for the notify thread. //////////////////////////////////////////////////////////////////// void P3DInstanceManager:: nt_thread_run() { // The notify thread exists because we need to be able to send // asynchronous notifications of request events. These request // events were detected in the various read threads associated with // each session, but we can't call back into the host space from the // read thread, since if the host immediately response to a callback // by calling back into the p3d_plugin space, now we have our read // thread doing stuff in here that's not related to the read thread. // Even worse, some of the things it might need to do might require // a separate read thread to be running! _notify_ready.acquire(); while (_notify_thread_continue) { NotifyInstances instances; while (!_notify_instances.empty()) { instances.clear(); instances.swap(_notify_instances); // Go ahead and drop the lock while we make the callback, to // reduce the risk of deadlock. We don't want to be holding any // locks when we call into client code. _notify_ready.release(); NotifyInstances::iterator ni; for (ni = instances.begin(); ni != instances.end(); ++ni) { // TODO: a race condition here when instances are deleted. P3DInstance *inst = (*ni); assert(inst != NULL); P3D_request_ready_func *func = inst->get_request_ready_func(); assert(func != NULL); (*func)(inst); } _notify_ready.acquire(); } _notify_ready.wait(); } _notify_ready.release(); }