From 996f6f7299232cc701ae695aa5e32ed2eed4af47 Mon Sep 17 00:00:00 2001 From: Sebastian <106036+schoeller@users.noreply.github.com> Date: Sat, 29 Aug 2026 11:12:33 +0200 Subject: [PATCH] feat(plugins): add BuiltinPlugin::on_load lifecycle callback OpenCADStudio/plugins#901 (comment) - Add BuiltinPlugin::on_load(&mut self, host: &mut dyn HostApi) with a default no-op implementation so existing plugins remain source-compatible. - Invoke on_load once for V4 plugins immediately after the runner connects and before entering the request loop, giving plugins a HostApi surface to cache plugin_request_sender() or set up worker threads before any dispatch. - V2/V3 plugins are unchanged: HostApi is only available during dispatch. --- crates/ocs_plugin_api/src/host.rs | 10 ++++++++++ crates/ocs_plugin_api/src/runner.rs | 12 ++++++++++++ 2 files changed, 22 insertions(+) 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,