diff --git a/crates/ocs_plugin_api/src/host.rs b/crates/ocs_plugin_api/src/host.rs index f146c23ab..39ae9a04f 100644 --- a/crates/ocs_plugin_api/src/host.rs +++ b/crates/ocs_plugin_api/src/host.rs @@ -298,6 +298,16 @@ pub trait BuiltinPlugin: Send + Sync { /// the V4 runner guarantees it is only called for plugins that report /// API major 4 or newer. fn on_notification(&mut self, _command_id: Option, _notification: HostNotification) {} + + /// Lifecycle callback invoked once after the plugin has been loaded and its + /// manifest/ribbon have been accepted by the host. The plugin receives the + /// active document tab's `HostApi` so it can set up worker threads or cache + /// `plugin_request_sender()` before any user command runs. + /// + /// Added after API v4. The default implementation does nothing, so existing + /// plugins remain source-compatible. The runner only calls this for plugins + /// that report API major 4 or newer. + fn on_load(&mut self, _host: &mut dyn HostApi) {} } /// A point-driven interactive command a plugin starts via diff --git a/crates/ocs_plugin_api/src/runner.rs b/crates/ocs_plugin_api/src/runner.rs index 03f0097fb..dc90e4b34 100644 --- a/crates/ocs_plugin_api/src/runner.rs +++ b/crates/ocs_plugin_api/src/runner.rs @@ -56,15 +56,27 @@ pub fn run(socket_name: &str, cdylib_path: &Path) -> Result<(), Box= 4 { let client = V4Client::connect_handshake(socket_name, &token)?; + // Give the plugin a chance to set up worker threads / cache the request + // sender before the host starts dispatching user commands. + invoke_on_load(&mut *plugin, &client, &interactive); run_v4(&mut *plugin, &interactive, client) } else { let client = IpcClient::connect(socket_name)?; eprintln!("[runner] connected to host"); client.send_handshake(&token)?; + // V2/V3 plugins receive HostApi only during dispatch, so on_load is not + // meaningful there; skip it to keep the old protocol unchanged. run_v3(&*plugin, &interactive, client) } } +fn invoke_on_load(plugin: &mut dyn BuiltinPlugin, client: &V4Client, interactive: &InteractiveRegistry) { + let mut proxy = client.plugin_host_api(0, interactive.clone()); + let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + plugin.on_load(&mut proxy) + })); +} + fn run_v3( plugin: &dyn BuiltinPlugin, interactive: &InteractiveRegistry,