forked from 1b-agent/opencadstudio-upstream
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:
1 parent
f6219a9609
commit
29c71be101
14 files changed
+402
-36
No files matched your search
+38
-5
@@ -44,6 +44,16 @@ pub fn ribbon_modules_enabled(
|
||||
.filter(|p| !disabled.contains(p.manifest().id))
|
||||
.map(|p| (p.manifest().ribbon_order, p.ribbon()))
|
||||
.collect();
|
||||
// Dynamically-loaded external plugins contribute tabs too (their libraries
|
||||
// stay resident for the session, so these vtables remain valid).
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
crate::plugin::external::with_loaded(|loaded| {
|
||||
for lp in loaded {
|
||||
if !disabled.contains(lp.id.as_str()) {
|
||||
addons.push((lp.plugin().manifest().ribbon_order, lp.plugin().ribbon()));
|
||||
}
|
||||
}
|
||||
});
|
||||
addons.sort_by_key(|(order, _)| *order);
|
||||
core.extend(addons.into_iter().map(|(_, ribbon)| ribbon));
|
||||
core
|
||||
@@ -53,12 +63,35 @@ pub fn ribbon_modules_enabled(
|
||||
/// Disabled plugins (toggled off in the Plugin Manager) are skipped.
|
||||
pub(crate) fn try_dispatch(app: &mut OpenCADStudio, tab: usize, cmd: &str) -> bool {
|
||||
let disabled = app.disabled_plugin_ids();
|
||||
let mut host = HostSession::new(app, tab);
|
||||
for plugin in all_plugins() {
|
||||
if disabled.contains(plugin.manifest().id) {
|
||||
continue;
|
||||
{
|
||||
let mut host = HostSession::new(app, tab);
|
||||
for plugin in all_plugins() {
|
||||
if disabled.contains(plugin.manifest().id) {
|
||||
continue;
|
||||
}
|
||||
if plugin.dispatch(&mut host, cmd) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if plugin.dispatch(&mut host, cmd) {
|
||||
}
|
||||
// Then the dynamically-loaded external plugins. The store is a separate
|
||||
// borrow from `app`, so wrapping `app` in a fresh `HostSession` here is
|
||||
// sound.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
let handled = crate::plugin::external::with_loaded(|loaded| {
|
||||
let mut host = HostSession::new(app, tab);
|
||||
for lp in loaded {
|
||||
if disabled.contains(lp.id.as_str()) {
|
||||
continue;
|
||||
}
|
||||
if lp.plugin().dispatch(&mut host, cmd) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
});
|
||||
if handled {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user