Snapshot: V4 plugin API and host integration

This commit is contained in:
Sebastian committed 2026-08-12 21:01:39 +02:00
1 parent 448869ac35
commit 29c41a4041
36 files changed
+5463 -253

No files matched your search

+57 -17
View File
@@ -1,16 +1,19 @@
//! Process manager for out-of-process plugins.
use std::path::Path;
use std::process::Child;
use std::sync::Arc;
use crate::host::HostApi;
use crate::host::{HostApi, HostNotification, PluginNotification};
use crate::process::{PluginError, PluginProcess};
use crate::ribbon::owned::{to_shared_module, SharedCadModule};
/// Callback the host installs to receive plugin-to-host notifications.
pub type NotificationHandler = Arc<dyn Fn(&str, Option<u64>, PluginNotification) + Send + Sync>;
/// Owner of every spawned plugin process.
pub struct PluginManager {
plugins: Vec<LoadedPlugin>,
notification_handler: Option<NotificationHandler>,
}
struct LoadedPlugin {
@@ -42,9 +45,18 @@ impl PluginManager {
pub fn new() -> Self {
Self {
plugins: Vec::new(),
notification_handler: None,
}
}
/// Install a handler for plugin-to-host notifications. The handler runs on
/// the V4 reader thread and must not block or allocate heavily; forward to
/// a channel if heavy work is required. Should be set before [`load`] is
/// called so notifications from newly loaded plugins are delivered.
pub fn set_notification_handler(&mut self, handler: NotificationHandler) {
self.notification_handler = Some(handler);
}
/// Spawn `cdylib_path` as a separate plugin process, build its ribbon
/// module, and store it. Returns the plugin id on success.
pub fn load(
@@ -52,7 +64,11 @@ impl PluginManager {
cdylib_path: &Path,
host: &mut dyn HostApi,
) -> Result<String, PluginError> {
let process = PluginProcess::spawn(cdylib_path, host)?;
let handler: NotificationHandler = match self.notification_handler.as_ref() {
Some(h) => Arc::clone(h),
None => Arc::new(|_id, _cmd, _notif| {}),
};
let process = PluginProcess::spawn(cdylib_path, host, handler)?;
let id = process.id().to_string();
let name = process.manifest().name.clone();
let module = to_shared_module(id.clone(), name, process.ribbon().to_vec());
@@ -63,6 +79,26 @@ impl PluginManager {
Ok(id)
}
/// Fan out a host notification to every alive V4 plugin. Per-process errors
/// are logged; this is best-effort.
pub fn broadcast_notification(
&self,
command_id: Option<u64>,
notification: HostNotification,
) {
for p in &self.plugins {
if !p.process.is_alive() {
continue;
}
if let Err(e) = p.process.notify_plugin(command_id, notification.clone()) {
eprintln!(
"[plugin] broadcast_notification failed for {}: {e}",
p.process.id()
);
}
}
}
/// Ribbon modules for alive, non-disabled plugins, sorted by `ribbon_order`.
pub fn ribbon_modules<F: Fn(&str) -> bool>(
&self,
@@ -140,6 +176,23 @@ impl PluginManager {
out
}
/// Drain any plugin→host requests that have arrived asynchronously across
/// all loaded plugins. Errors are logged; this is best-effort.
pub fn drain_requests(
&self,
host: &mut dyn HostApi,
on_start_interactive: &mut dyn FnMut(u64),
) {
for p in &self.plugins {
if !p.process.is_alive() {
continue;
}
if let Err(e) = p.process.drain_requests(host, on_start_interactive) {
eprintln!("[plugin] drain_requests failed for {}: {e}", p.process.id());
}
}
}
/// Begin asynchronous shutdown of every plugin process.
///
/// Kills every child synchronously on the calling thread and moves the
@@ -147,21 +200,8 @@ impl PluginManager {
/// shutdown is fast regardless of how many plugins are loaded.
pub fn shutdown_all(&mut self) {
let plugins = std::mem::take(&mut self.plugins);
let mut children: Vec<Child> = Vec::with_capacity(plugins.len());
for p in plugins {
let (stream, child) = p.process.take_resources();
drop(stream);
if let Some(mut child) = child {
let _ = child.kill();
children.push(child);
}
}
if !children.is_empty() {
std::thread::spawn(move || {
for mut child in children {
let _ = child.wait();
}
});
p.process.shutdown();
}
}
}