feat(plugin): load external cdylib plugins at runtime (phase 2)
Move BuiltinPlugin into ocs_plugin_api (host feature) so out-of-tree crates can implement it, and add export_plugin! to emit the two C symbols a cdylib exposes: ocs_plugin_api_version (checked first, so an ABI-incompatible build never runs) and ocs_plugin_register -> boxed BuiltinPlugin. The host loads every compatible package from the plugins folder at startup via libloading (desktop only), keeps the library resident for the session, and merges its ribbon tab + command dispatch into the same paths as built-ins (honouring enable/disable). The Plugin Manager shows external packages with a Loaded / incompatible status. Approach B: the plugin hands back a boxed trait object, assuming a matching toolchain + ocs_plugin_api version (the version symbol enforces the latter). crates/ocs_example_plugin is the reference cdylib. Part of the #100 extensibility epic (phase 2). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
f6219a9609
commit
29c71be101
14 changed files with 402 additions and 36 deletions
|
|
@ -16,6 +16,47 @@ use std::any::Any;
|
|||
use acadrust::xdata::ExtendedDataRecord;
|
||||
use acadrust::{CadDocument, EntityType, Handle};
|
||||
|
||||
use crate::manifest::PluginManifest;
|
||||
use crate::ribbon::CadModule;
|
||||
|
||||
/// An add-on package's entry point: its manifest, optional ribbon tab, and
|
||||
/// command dispatch. Built-in (in-tree) and dynamically-loaded (cdylib) plugins
|
||||
/// implement the same trait from this crate, so an out-of-tree add-on targets
|
||||
/// the stable contract rather than the host binary.
|
||||
pub trait BuiltinPlugin: Send + Sync {
|
||||
fn manifest(&self) -> &'static PluginManifest;
|
||||
fn ribbon(&self) -> Box<dyn CadModule>;
|
||||
fn dispatch(&self, host: &mut dyn HostApi, cmd: &str) -> bool;
|
||||
}
|
||||
|
||||
/// Export a `BuiltinPlugin` from a `cdylib` so the host can load it at runtime.
|
||||
///
|
||||
/// Emits the two C symbols the loader looks for: `ocs_plugin_api_version`
|
||||
/// (checked before anything else, so an ABI-incompatible build is rejected
|
||||
/// without running its code) and `ocs_plugin_register` (constructs the plugin
|
||||
/// and hands ownership to the host as a boxed trait object).
|
||||
///
|
||||
/// ```ignore
|
||||
/// ocs_plugin_api::export_plugin!(MyPlugin::new());
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! export_plugin {
|
||||
($ctor:expr) => {
|
||||
#[no_mangle]
|
||||
pub extern "C" fn ocs_plugin_api_version() -> u32 {
|
||||
$crate::API_VERSION
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn ocs_plugin_register(
|
||||
) -> *mut ::std::boxed::Box<dyn $crate::host::BuiltinPlugin> {
|
||||
let plugin: ::std::boxed::Box<dyn $crate::host::BuiltinPlugin> =
|
||||
::std::boxed::Box::new($ctor);
|
||||
::std::boxed::Box::into_raw(::std::boxed::Box::new(plugin))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// The plugin-facing runtime surface for one active document tab.
|
||||
pub trait HostApi {
|
||||
/// Index of the tab this session targets.
|
||||
|
|
|
|||
Loading…
Reference in a new issue