Browse Source

smooth out package transitions by threading uncompress and unpack

David Rose 16 years ago
parent
commit
29b376c1e3

+ 54 - 22
direct/src/plugin/p3dInstance.cxx

@@ -703,29 +703,46 @@ get_request() {
   _request_pending = !_baked_requests.empty();
 
   if (request != NULL) {
-    if (request->_request_type == P3D_RT_notify) {
-      // Also eval the associated HTML token, if any.
-      string message = request->_request._notify._message;
-      string expression = _fparams.lookup_token(message);
-      nout << "notify: " << message << " " << expression << "\n";
-      if (!expression.empty() && _browser_script_object != NULL) {
-        P3D_object *result = P3D_OBJECT_EVAL(_browser_script_object, expression.c_str());
-        P3D_OBJECT_XDECREF(result);
+    switch (request->_request_type) {
+    case P3D_RT_notify:
+      {
+        // Also eval the associated HTML token, if any.
+        string message = request->_request._notify._message;
+        string expression = _fparams.lookup_token(message);
+        nout << "notify: " << message << " " << expression << "\n";
+        if (!expression.empty() && _browser_script_object != NULL) {
+          P3D_object *result = P3D_OBJECT_EVAL(_browser_script_object, expression.c_str());
+          P3D_OBJECT_XDECREF(result);
+        }
       }
-
-    } else if (request->_request_type == P3D_RT_stop) {
-      // We also send an implicit message when Python requests itself
-      // to shutdown.
-      _panda_script_object->set_pyobj(NULL);
-      _panda_script_object->set_string_property("status", "stopped");
-
-      string message = "onpythonstop";
-      string expression = _fparams.lookup_token(message);
-      nout << "notify: " << message << " " << expression << "\n";
-      if (!expression.empty() && _browser_script_object != NULL) {
-        P3D_object *result = P3D_OBJECT_EVAL(_browser_script_object, expression.c_str());
-        P3D_OBJECT_XDECREF(result);
+      break;
+
+    case P3D_RT_stop:
+      {
+        // We also send an implicit message when Python requests itself
+        // to shutdown.
+        _panda_script_object->set_pyobj(NULL);
+        _panda_script_object->set_string_property("status", "stopped");
+        
+        string message = "onpythonstop";
+        string expression = _fparams.lookup_token(message);
+        nout << "notify: " << message << " " << expression << "\n";
+        if (!expression.empty() && _browser_script_object != NULL) {
+          P3D_object *result = P3D_OBJECT_EVAL(_browser_script_object, expression.c_str());
+          P3D_OBJECT_XDECREF(result);
+        }
       }
+      break;
+
+    case P3D_RT_callback:
+      {
+        // And when the callback request is extracted, we make the
+        // callback.
+        P3D_callback_func *func = request->_request._callback._func;
+        void *data = request->_request._callback._data;
+        (*func)(data);
+      }
+      break;
     }
   }
   
@@ -843,7 +860,7 @@ finish_request(P3D_request *request, bool handled) {
 
   P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
   if (inst_mgr->validate_instance(request->_instance) == NULL) {
-    nout << "Ignoring unknown request " << request << "\n";
+    //    nout << "Ignoring unknown request " << request << "\n";
     return;
   }
 
@@ -1235,6 +1252,21 @@ request_refresh() {
   add_baked_request(request);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: P3DInstance::request_callback
+//       Access: Public
+//  Description: Asks the host to make a callback later.
+////////////////////////////////////////////////////////////////////
+void P3DInstance::
+request_callback(P3D_callback_func *func, void *data) {
+  P3D_request *request = new P3D_request;
+  request->_instance = NULL;
+  request->_request_type = P3D_RT_callback;
+  request->_request._callback._func = func;
+  request->_request._callback._data = data;
+  add_baked_request(request);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: P3DInstance::make_xml
 //       Access: Public

+ 1 - 0
direct/src/plugin/p3dInstance.h

@@ -108,6 +108,7 @@ public:
   void request_stop_sub_thread();
   void request_stop_main_thread();
   void request_refresh();
+  void request_callback(P3D_callback_func *func, void *data);
 
   TiXmlElement *make_xml();
   void splash_button_clicked_sub_thread();

+ 2 - 3
direct/src/plugin/p3dMultifileReader.cxx

@@ -87,7 +87,7 @@ close() {
 ////////////////////////////////////////////////////////////////////
 bool P3DMultifileReader::
 extract_all(const string &to_dir, P3DPackage *package, 
-            P3DPackage::InstallStep *step) {
+            P3DPackage::InstallStepThreaded *step) {
   assert(_is_open);
   if (_in.fail()) {
     return false;
@@ -131,8 +131,7 @@ extract_all(const string &to_dir, P3DPackage *package,
     chmod(output_pathname.c_str(), 0555);
 
     if (step != NULL && package != NULL) {
-      step->_bytes_done += s._data_length;
-      step->report_step_progress();
+      step->thread_add_bytes_done(s._data_length);
     }
   }
 

+ 1 - 1
direct/src/plugin/p3dMultifileReader.h

@@ -35,7 +35,7 @@ public:
   void close();
 
   bool extract_all(const string &to_dir, P3DPackage *package, 
-                   P3DPackage::InstallStep *step);
+                   P3DPackage::InstallStepThreaded *step);
 
   bool extract_one(ostream &out, const string &filename);
 

+ 203 - 30
direct/src/plugin/p3dPackage.cxx

@@ -64,6 +64,7 @@ P3DPackage(P3DHost *host, const string &package_name,
   _xconfig = NULL;
   _temp_contents_file = NULL;
 
+  _computed_plan_size = false;
   _info_ready = false;
   _download_size = 0;
   _allow_data_download = false;
@@ -81,7 +82,9 @@ P3DPackage(P3DHost *host, const string &package_name,
 P3DPackage::
 ~P3DPackage() {
   // Tell any pending callbacks that we're no good any more.
-  report_done(false);
+  if (!_ready && !_failed) {
+    report_done(false);
+  }
 
   // Ditto the outstanding instances.
   Instances::iterator ii;
@@ -785,6 +788,7 @@ clear_install_plans() {
   }
 
   _install_plans.clear();
+  _computed_plan_size = false;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -809,6 +813,7 @@ build_install_plans(TiXmlDocument *doc) {
 
   _install_plans.push_front(InstallPlan());
   InstallPlan &plan = _install_plans.front();
+  _computed_plan_size = false;
 
   bool needs_redownload = false;
   
@@ -910,8 +915,8 @@ build_install_plans(TiXmlDocument *doc) {
 ////////////////////////////////////////////////////////////////////
 void P3DPackage::
 follow_install_plans(bool download_finished) {
-  if (!_allow_data_download) {
-    // Not authorized yet.
+  if (!_allow_data_download || _failed) {
+    // Not authorized yet, or something went wrong.
     return;
   }
 
@@ -921,15 +926,26 @@ follow_install_plans(bool download_finished) {
     InstallPlan &plan = _install_plans.front();
     bool plan_failed = false;
 
-    _total_plan_size = 0.0;
-    InstallPlan::iterator si;
-    for (si = plan.begin(); si != plan.end(); ++si) {
-      _total_plan_size += (*si)->get_effort();
+    if (!_computed_plan_size) {
+      _total_plan_size = 0.0;
+      _total_plan_completed = 0.0;
+      InstallPlan::iterator si;
+      for (si = plan.begin(); si != plan.end(); ++si) {
+        double step_effort = (*si)->get_effort();
+        _total_plan_size += step_effort;
+        _total_plan_completed += (*si)->get_progress() * step_effort;
+      }
+      
+      _download_progress = 0.0;
+      if (_total_plan_size > 0.0) {
+        _download_progress = _total_plan_completed / _total_plan_size;
+      }
+      _computed_plan_size = true;
+      nout << "Selected install plan for " << get_package_name()
+           << ": " << _total_plan_completed << " of "
+           << _total_plan_size << "\n";
     }
 
-    _total_plan_completed = 0.0;
-    _download_progress = 0.0;
-
     while (!plan.empty() && !plan_failed) {
       InstallStep *step = plan.front();
       _current_step_effort = step->get_effort();
@@ -951,6 +967,11 @@ follow_install_plans(bool download_finished) {
         // A callback hook has been attached; we'll come back later.
         return;
 
+      case IT_needs_callback:
+        // We need to install a callback hook and come back later.
+        request_callback();
+        return;
+
       case IT_step_complete:
         // So far, so good.  Go on to the next step.
         _total_plan_completed += _current_step_effort;
@@ -970,12 +991,39 @@ follow_install_plans(bool download_finished) {
     // That plan failed.  Go on to the next plan.
     nout << "Plan failed.\n";
     _install_plans.pop_front();
+    _computed_plan_size = false;
   }
 
   // All plans failed.  Too bad for us.
   report_done(false);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::st_callback
+//       Access: Private, Static
+//  Description: This function is registered as the callback hook when
+//               a package is in the middle of processing in a
+//               sub-thread.
+////////////////////////////////////////////////////////////////////
+void P3DPackage::
+st_callback(void *self) {
+  ((P3DPackage *)self)->follow_install_plans(false);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::request_callback
+//       Access: Private
+//  Description: Requests that follow_install_plans() will be called
+//               again in the future.
+////////////////////////////////////////////////////////////////////
+void P3DPackage::
+request_callback() {
+  Instances::iterator ii;
+  for (ii = _instances.begin(); ii != _instances.end(); ++ii) {
+    (*ii)->request_callback(&st_callback, this);
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: P3DPackage::report_progress
 //       Access: Private
@@ -984,13 +1032,14 @@ follow_install_plans(bool download_finished) {
 ////////////////////////////////////////////////////////////////////
 void P3DPackage::
 report_progress(P3DPackage::InstallStep *step) {
-  double size = _total_plan_completed + _current_step_effort * step->get_progress();
-  _download_progress = min(size / _total_plan_size, 1.0);
-  //  nout << get_package_name() << " progress " << _download_progress << "\n";
-
-  Instances::iterator ii;
-  for (ii = _instances.begin(); ii != _instances.end(); ++ii) {
-    (*ii)->report_package_progress(this, _download_progress);
+  if (_computed_plan_size) {
+    double size = _total_plan_completed + _current_step_effort * step->get_progress();
+    _download_progress = min(size / _total_plan_size, 1.0);
+  
+    Instances::iterator ii;
+    for (ii = _instances.begin(); ii != _instances.end(); ++ii) {
+      (*ii)->report_package_progress(this, _download_progress);
+    }
   }
 }
 
@@ -1022,6 +1071,13 @@ report_info_ready() {
 ////////////////////////////////////////////////////////////////////
 void P3DPackage::
 report_done(bool success) {
+  // Don't call report_done() twice.
+  if (_ready || _failed) {
+    nout << get_package_name() << ": report_done() called twice\n";
+    _failed = true;
+    return;
+  }
+
   if (success) {
     _info_ready = true;
     _ready = true;
@@ -1491,6 +1547,122 @@ output(ostream &out) {
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::InstallStepThreaded::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+P3DPackage::InstallStepThreaded::
+InstallStepThreaded(P3DPackage *package, size_t bytes, double factor) :
+  InstallStep(package, bytes, factor)
+{
+  INIT_THREAD(_thread);
+  INIT_LOCK(_thread_lock);
+  _thread_started = false;
+  _thread_token = IT_needs_callback;
+  _thread_bytes_done = 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::InstallStepThreaded::Destructor
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+P3DPackage::InstallStepThreaded::
+~InstallStepThreaded() {
+  if (_thread_started) {
+    JOIN_THREAD(_thread);
+    _thread_started = false;
+  }
+  DESTROY_LOCK(_thread_lock);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::InstallStepThreaded::do_step
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+P3DPackage::InstallToken P3DPackage::InstallStepThreaded::
+do_step(bool download_finished) {
+  // This method is called within the main thread.  It simply checks
+  // the thread status, and returns.
+
+  // Spawn a thread and wait for it to finish.
+  if (!_thread_started) {
+    nout << "Spawning thread to handle " << _package->get_package_name() << "\n";
+    _thread_started = true;
+    SPAWN_THREAD(_thread, thread_main, this);
+  }
+
+  InstallToken token;
+  bool made_progress = false;
+
+  ACQUIRE_LOCK(_thread_lock);
+  token = _thread_token;
+  if (_bytes_done != _thread_bytes_done) {
+    _bytes_done = _thread_bytes_done;
+    made_progress = true;
+  }
+  RELEASE_LOCK(_thread_lock);
+
+  if (made_progress) {
+    report_step_progress();
+  }
+
+  return token;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::InstallStepThreaded::thread_main
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void P3DPackage::InstallStepThreaded::
+thread_main() {
+  // This method is called within the sub-thread.  It calls
+  // thread_step() to do its work.
+
+  InstallToken token = IT_needs_callback;
+  do {
+    // Perform the nested step, then update the token.
+    token = thread_step();
+
+    ACQUIRE_LOCK(_thread_lock);
+    _thread_token = token;
+    RELEASE_LOCK(_thread_lock);
+    
+    // Do it again if needed.
+  } while (token == IT_needs_callback);
+
+  // All done.
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::InstallStepThreaded::thread_set_bytes_done
+//       Access: Public
+//  Description: Should be called from time to time within the
+//               sub-thread to update the number of bytes processed.
+////////////////////////////////////////////////////////////////////
+void P3DPackage::InstallStepThreaded::
+thread_set_bytes_done(size_t bytes_done) {
+  ACQUIRE_LOCK(_thread_lock);
+  _thread_bytes_done = bytes_done;
+  RELEASE_LOCK(_thread_lock);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::InstallStepThreaded::thread_add_bytes_done
+//       Access: Public
+//  Description: Should be called from time to time within the
+//               sub-thread to update the number of bytes processed.
+////////////////////////////////////////////////////////////////////
+void P3DPackage::InstallStepThreaded::
+thread_add_bytes_done(size_t bytes_done) {
+  ACQUIRE_LOCK(_thread_lock);
+  _thread_bytes_done += bytes_done;
+  RELEASE_LOCK(_thread_lock);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: P3DPackage::InstallStepUncompressFile::Constructor
 //       Access: Public
@@ -1499,20 +1671,21 @@ output(ostream &out) {
 P3DPackage::InstallStepUncompressFile::
 InstallStepUncompressFile(P3DPackage *package, const FileSpec &source,
                           const FileSpec &target, bool verify_target) :
-  InstallStep(package, target.get_size(), _uncompress_factor),
+  InstallStepThreaded(package, target.get_size(), _uncompress_factor),
   _source(source),
   _target(target),
   _verify_target(verify_target)
 {
 }
 
+
 ////////////////////////////////////////////////////////////////////
-//     Function: P3DPackage::InstallStepUncompressFile::do_step
+//     Function: P3DPackage::InstallStepUncompressFile::thread_step
 //       Access: Public, Virtual
 //  Description: 
 ////////////////////////////////////////////////////////////////////
 P3DPackage::InstallToken P3DPackage::InstallStepUncompressFile::
-do_step(bool download_finished) {
+thread_step() {
   string source_pathname = _package->get_package_dir() + "/" + _source.get_filename();
   string target_pathname = _package->get_package_dir() + "/" + _target.get_filename();
 
@@ -1582,8 +1755,8 @@ do_step(bool download_finished) {
         nout << "Couldn't write entire file to " << target_pathname << "\n";
         return IT_step_failed;
       }
-      _bytes_done += (write_buffer_size - z.avail_out);
-      report_step_progress();
+
+      thread_add_bytes_done(write_buffer_size - z.avail_out);
     }
 
     if (result == Z_STREAM_END) {
@@ -1650,17 +1823,17 @@ output(ostream &out) {
 ////////////////////////////////////////////////////////////////////
 P3DPackage::InstallStepUnpackArchive::
 InstallStepUnpackArchive(P3DPackage *package, size_t unpack_size) :
-  InstallStep(package, unpack_size, _unpack_factor)
+  InstallStepThreaded(package, unpack_size, _unpack_factor)
 {
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: P3DPackage::InstallStepUnpackArchive::do_step
+//     Function: P3DPackage::InstallStepUnpackArchive::thread_step
 //       Access: Public, Virtual
 //  Description: 
 ////////////////////////////////////////////////////////////////////
 P3DPackage::InstallToken P3DPackage::InstallStepUnpackArchive::
-do_step(bool download_finished) {
+thread_step() {
   string source_pathname = _package->get_archive_file_pathname();
   P3DMultifileReader reader;
 
@@ -1696,18 +1869,18 @@ output(ostream &out) {
 P3DPackage::InstallStepApplyPatch::
 InstallStepApplyPatch(P3DPackage *package, const FileSpec &patchfile,
                       const FileSpec &source, const FileSpec &target) :
-  InstallStep(package, target.get_size(), _patch_factor),
+  InstallStepThreaded(package, target.get_size(), _patch_factor),
   _reader(package->get_package_dir(), patchfile, source, target)
 {
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: P3DPackage::InstallStepApplyPatch::do_step
+//     Function: P3DPackage::InstallStepApplyPatch::thread_step
 //       Access: Public, Virtual
 //  Description: 
 ////////////////////////////////////////////////////////////////////
 P3DPackage::InstallToken P3DPackage::InstallStepApplyPatch::
-do_step(bool download_finished) {
+thread_step() {
   // Open the patchfile
   if (!_reader.open_read()) {
     _reader.close();
@@ -1716,8 +1889,8 @@ do_step(bool download_finished) {
 
   // Apply the patch.
   while (_reader.step()) {
-    _bytes_done = _reader.get_bytes_written();
-    report_step_progress();
+    size_t bytes_written = _reader.get_bytes_written();
+    thread_set_bytes_done(bytes_written);
   }
 
   // Close and verify.

+ 30 - 6
direct/src/plugin/p3dPackage.h

@@ -114,6 +114,7 @@ private:
     IT_step_complete,
     IT_step_failed,
     IT_continue,
+    IT_needs_callback,
     IT_terminate,
   };
 
@@ -149,11 +150,31 @@ private:
     Download *_download;
   };
 
-  class InstallStepUncompressFile : public InstallStep {
+  class InstallStepThreaded : public InstallStep {
+  public:
+    InstallStepThreaded(P3DPackage *package, size_t bytes, double factor);
+    virtual ~InstallStepThreaded();
+
+    virtual InstallToken do_step(bool download_finished);
+
+    THREAD_CALLBACK_DECLARATION(InstallStepThreaded, thread_main);
+    void thread_main();
+    virtual InstallToken thread_step()=0;
+    void thread_set_bytes_done(size_t bytes_done);
+    void thread_add_bytes_done(size_t bytes_done);
+
+    THREAD _thread;
+    LOCK _thread_lock;
+    bool _thread_started;
+    InstallToken _thread_token;
+    size_t _thread_bytes_done;
+  };    
+
+  class InstallStepUncompressFile : public InstallStepThreaded {
   public:
     InstallStepUncompressFile(P3DPackage *package, const FileSpec &source,
                               const FileSpec &target, bool verify_target);
-    virtual InstallToken do_step(bool download_finished);
+    virtual InstallToken thread_step();
     virtual void output(ostream &out);
 
     FileSpec _source;
@@ -161,20 +182,20 @@ private:
     bool _verify_target;
   };
 
-  class InstallStepUnpackArchive : public InstallStep {
+  class InstallStepUnpackArchive : public InstallStepThreaded {
   public:
     InstallStepUnpackArchive(P3DPackage *package, size_t unpack_size);
-    virtual InstallToken do_step(bool download_finished);
+    virtual InstallToken thread_step();
     virtual void output(ostream &out);
   };
 
-  class InstallStepApplyPatch : public InstallStep {
+  class InstallStepApplyPatch : public InstallStepThreaded {
   public:
     InstallStepApplyPatch(P3DPackage *package,
                           const FileSpec &patchfile,
                           const FileSpec &source,
                           const FileSpec &target);
-    virtual InstallToken do_step(bool download_finished);
+    virtual InstallToken thread_step();
     virtual void output(ostream &out);
 
     P3DPatchfileReader _reader;
@@ -184,6 +205,7 @@ private:
   typedef deque<InstallPlan> InstallPlans;
   InstallPlans _install_plans;
 
+  bool _computed_plan_size;
   double _total_plan_size;
   double _total_plan_completed;
   double _download_progress;
@@ -203,6 +225,8 @@ private:
   void clear_install_plans();
   void build_install_plans(TiXmlDocument *doc);
   void follow_install_plans(bool download_finished);
+  static void st_callback(void *self);
+  void request_callback();
 
   void report_progress(InstallStep *step);
   void report_info_ready();

+ 15 - 0
direct/src/plugin/p3d_plugin.h

@@ -766,6 +766,7 @@ typedef enum {
   P3D_RT_get_url,
   P3D_RT_notify,
   P3D_RT_refresh,
+  P3D_RT_callback,
 } P3D_request_type;
 
 /* Structures corresponding to the request types in the above enum. */
@@ -804,6 +805,19 @@ typedef struct {
 typedef struct {
 } P3D_request_refresh;
 
+/* A callback request.  The instance wants to yield control
+   temporarily back to JavaScript, to allow JavaScript to maintain
+   interactivity during a long computation, and expects to get control
+   again some short while later (when the callback request is pulled
+   off the queue and the request is handled).  The data within this
+   structure is used internally by the core API and shouldn't be
+   interpreted by the caller. */
+typedef void P3D_callback_func(void *);
+typedef struct {
+  P3D_callback_func *_func;
+  void *_data;
+} P3D_request_callback;
+
 /* This is the overall structure that represents a single request.  It
    is returned by P3D_instance_get_request(). */
 typedef struct {
@@ -814,6 +828,7 @@ typedef struct {
     P3D_request_get_url _get_url;
     P3D_request_notify _notify;
     P3D_request_refresh _refresh;
+    P3D_request_callback _callback;
   } _request;
 } P3D_request;
 

+ 65 - 65
direct/src/plugin_activex/PPInstance.cpp

@@ -599,26 +599,26 @@ std::string PPInstance::GetP3DFilename( )
     return p3dFilename;
 }
 
-void PPInstance::HandleRequestLoop() 
-{
-    P3D_instance *p3d_inst = P3D_check_request_ptr(0.0);
-    while ( p3d_inst != NULL ) 
-    {
-        P3D_request *request = P3D_instance_get_request_ptr(p3d_inst);
-        if ( request != NULL ) 
-        {
-            CP3DActiveXCtrl* parent = ( CP3DActiveXCtrl* )(p3d_inst->_user_data);
-            if ( parent )
-            {
-                parent->m_instance.HandleRequest( request );
-            }
-            else
-            {
-                nout << "Error handling P3D request. Instance's user data is not a Control \n";
-            }
+void PPInstance::HandleRequestLoop() {
+  P3D_instance *p3d_inst = P3D_check_request_ptr(0.0);
+  while ( p3d_inst != NULL ) {
+    P3D_request *request = P3D_instance_get_request_ptr(p3d_inst);
+    if ( request != NULL ) {
+      CP3DActiveXCtrl* parent = ( CP3DActiveXCtrl* )(p3d_inst->_user_data);
+      if ( parent ) {
+        if (!parent->m_instance.HandleRequest( request )) {
+          // If handling the request is meant to yield control
+          // temporarily to JavaScript (e.g. P3D_RT_callback), then do
+          // so now.
+          return;
         }
-        p3d_inst = P3D_check_request_ptr(0.0);
+          
+      } else {
+        nout << "Error handling P3D request. Instance's user data is not a Control \n";
+      }
     }
+    p3d_inst = P3D_check_request_ptr(0.0);
+  }
 }
 
 void PPInstance::HandleRequestGetUrl( void* data )
@@ -663,57 +663,57 @@ void PPInstance::HandleRequestGetUrl( void* data )
     P3D_request_finish_ptr( request, true );
 }
 
-void PPInstance::HandleRequest( P3D_request *request ) 
-{
-    if ( !request ) 
+bool PPInstance::
+HandleRequest( P3D_request *request ) {
+  if ( !request ) {
+    return false;
+  }
+  bool handled = false;
+  bool continue_loop = true;
+  
+  switch ( request->_request_type ) {
+  case P3D_RT_stop:
     {
-        return;
+      P3D_instance_finish_ptr( request->_instance );
+      handled = true;
+      break;
     }
-    bool handled = false;
-
-    switch ( request->_request_type ) 
+  case P3D_RT_get_url:
     {
-        case P3D_RT_stop:
-        {
-            P3D_instance_finish_ptr( request->_instance );
-            handled = true;
-            break;
-        }
-        case P3D_RT_get_url:
-        {
-            if ( !m_handleRequestOnUIThread )
-            {
-                _beginthread( HandleRequestGetUrl, 0, request );
-            }
-            else
-            {
-                HandleRequestGetUrl( request );
-            }
-            handled = true;
+      if ( !m_handleRequestOnUIThread ) {
+        _beginthread( HandleRequestGetUrl, 0, request );
+      } else {
+        HandleRequestGetUrl( request );
+      }
+      handled = true;
+      return true;
+    }
+  case P3D_RT_notify:
+    {
+      CString notification = request->_request._notify._message;
+      if ( notification == "ondownloadbegin" ) {
+        m_handleRequestOnUIThread = false;
+      } else if ( notification == "ondownloadcomplete" ) {
+        m_handleRequestOnUIThread = true;
+      }
+      handled = true;
+      break;
+    }
+  case P3D_RT_callback:
+    {
+      // In the case of a callback, yield control temporarily to JavaScript.
+      continue_loop = false;
+      break;
+    }
+  default:
+    {
+      // Some request types are not handled.
+      break;
+    }
+  };
 
-            return;
-        }
-        case P3D_RT_notify:
-        {
-            CString notification = request->_request._notify._message;
-            if ( notification == "ondownloadbegin" )
-            {
-                m_handleRequestOnUIThread = false;
-            }
-            else if ( notification == "ondownloadcomplete" )
-            {
-                m_handleRequestOnUIThread = true;
-            }
-            handled = true;
-            break;
-        }
-        default:
-        {
-            // Some request types are not handled.
-            break;
-        }
-    };
-    P3D_request_finish_ptr( request, handled );
+  P3D_request_finish_ptr( request, handled );
+  return continue_loop;
 }
 
 ////////////////////////////////////////////////////////////////////

+ 1 - 1
direct/src/plugin_activex/PPInstance.h

@@ -74,7 +74,7 @@ protected:
     void add_mirror(std::string mirror_url);
     void choose_random_mirrors(std::vector<std::string> &result, int num_mirrors);
 
-    void HandleRequest( P3D_request *request );
+    bool HandleRequest( P3D_request *request );
     static void HandleRequestGetUrl( void *data );
 
     void set_failed();

+ 19 - 5
direct/src/plugin_npapi/ppInstance.cxx

@@ -666,16 +666,18 @@ stream_as_file(NPStream *stream, const char *fname) {
 //     Function: PPInstance::handle_request
 //       Access: Public
 //  Description: Handles a request from the plugin, forwarding
-//               it to the browser as appropriate.
+//               it to the browser as appropriate.  Returns true if we
+//               should continue the request loop, or false to return
+//               (temporarily) to JavaScript.
 ////////////////////////////////////////////////////////////////////
-void PPInstance::
+bool PPInstance::
 handle_request(P3D_request *request) {
   if (_p3d_inst == NULL || _failed) {
-    return;
+    return false;
   }
   assert(request->_instance == _p3d_inst);
-
   bool handled = false;
+  bool continue_loop = true;
 
   switch (request->_request_type) {
   case P3D_RT_stop:
@@ -717,6 +719,11 @@ handle_request(P3D_request *request) {
     }
     break;
 
+  case P3D_RT_callback:
+    // In the case of a callback, yield control temporarily to JavaScript.
+    continue_loop = false;
+    break;
+
   default:
     // Some request types are not handled.
     nout << "Unhandled request: " << request->_request_type << "\n";
@@ -724,6 +731,7 @@ handle_request(P3D_request *request) {
   };
 
   P3D_request_finish_ptr(request, handled);
+  return continue_loop;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -1797,7 +1805,13 @@ handle_request_loop() {
     if (request != (P3D_request *)NULL) {
       PPInstance *inst = (PPInstance *)(p3d_inst->_user_data);
       assert(inst != NULL);
-      inst->handle_request(request);
+      if (!inst->handle_request(request)) {
+        // If handling the request is meant to yield control
+        // temporarily to JavaScript (e.g. P3D_RT_callback), then do
+        // so now.
+        return;
+      }
+
       if (!is_plugin_loaded()) {
         // Oops, we may have unloaded the plugin as an indirect effect
         // of handling the request.  If so, get out of here.

+ 1 - 1
direct/src/plugin_npapi/ppInstance.h

@@ -56,7 +56,7 @@ public:
   void url_notify(const char *url, NPReason reason, void *notifyData);
   void stream_as_file(NPStream *stream, const char *fname);
 
-  void handle_request(P3D_request *request);
+  bool handle_request(P3D_request *request);
   static void generic_browser_call();
 
   bool handle_event(void *event);