standard-plugin-sdk 0.1.1

Write Standard Code plugins in Rust: wasm components against standard:plugin@2.0.0
Documentation
//! The runtime the `#[daemon]` export forwards to.

use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec::Vec;
use core::cell::RefCell;

use super::executor::{self, Task};
use super::{Caller, Context, DaemonPlugin};
use crate::api::Json;
use crate::error::Result;
use crate::ui_runtime::Event;

crate::local::instance_local! {
    fn caller_slot() -> Option<Caller> = None;
}

crate::local::instance_local! {
    fn interest_slot() -> super::Interest = super::Interest::default();
}

/// The viewers' interest as the host last reported it.
pub(crate) fn current_interest() -> super::Interest {
    interest_slot(|slot| slot.clone())
}

/// The caller of the `handle-call` running now.
pub(crate) fn current_caller() -> Option<Caller> {
    caller_slot(|slot| slot.clone())
}

struct State<P: 'static> {
    /// Leaked at activate and reclaimed at deactivate, after the run loop
    /// that borrows it is dropped.
    plugin: &'static P,
    tasks: Vec<Task>,
    cx: Context,
}

#[doc(hidden)]
pub struct DaemonRuntime<P: 'static> {
    state: RefCell<Option<State<P>>>,
}

// SAFETY: a component instance runs on one thread and the host calls one
// export at a time; the exports are the only users of the static runtime.
unsafe impl<P> Sync for DaemonRuntime<P> {}

impl<P> Default for DaemonRuntime<P> {
    fn default() -> Self {
        Self::new()
    }
}

impl<P> DaemonRuntime<P> {
    pub const fn new() -> Self {
        Self {
            state: RefCell::new(None),
        }
    }
}

impl<P: DaemonPlugin> DaemonRuntime<P> {
    pub fn activate(&self, config: String) {
        self.deactivate();
        interest_slot(|slot| *slot = super::Interest::default());
        let cx = Context::new(config);
        let plugin: &'static P = Box::leak(Box::new(P::activate(&cx)));
        let run = plugin.run(cx.clone());
        *self.state.borrow_mut() = Some(State {
            plugin,
            tasks: alloc::vec![Task::new(Box::pin(run))],
            cx,
        });
    }

    pub fn event(&self, event: Event) {
        // The viewers' interest: kept for `Context::interest`, and heard as
        // `Event::Interest` only when it changed.
        if let Event::Plugin { name, payload } = &event
            && name == super::INTEREST_EVENT
        {
            let Some(interest) = super::Interest::from_payload(&payload.0) else {
                return;
            };
            let changed = interest_slot(|slot| {
                let changed = *slot != interest;
                *slot = interest.clone();
                changed
            });
            if changed {
                executor::push_event(Event::Interest(interest));
            }
            return;
        }
        executor::push_event(event);
    }

    pub fn drive(&self, now_ms: u64) -> Option<u64> {
        // Tasks run outside the state borrow, so a call handler or spawned
        // task can never observe it held.
        let mut tasks = core::mem::take(&mut self.state.borrow_mut().as_mut()?.tasks);
        let next = executor::run(&mut tasks, now_ms);
        if let Some(state) = self.state.borrow_mut().as_mut() {
            state.tasks.append(&mut tasks);
        }
        next
    }

    pub fn handle_call(&self, method: &str, payload: String, caller: Caller) -> Result<String> {
        let (plugin, cx) = match self.state.borrow().as_ref() {
            Some(state) => (state.plugin, state.cx.clone()),
            None => {
                return Err(crate::Error::Unavailable("not active".into()));
            }
        };
        caller_slot(|slot| *slot = Some(caller));
        let answer = plugin
            .call(method, Json(payload), &cx)
            .map(Json::into_string);
        caller_slot(|slot| *slot = None);
        answer
    }

    /// The plugin, for tests.
    pub fn with_plugin<R>(&self, f: impl FnOnce(&P) -> R) -> Option<R> {
        let plugin = self.state.borrow().as_ref().map(|state| state.plugin)?;
        Some(f(plugin))
    }

    pub fn deactivate(&self) {
        let Some(state) = self.state.borrow_mut().take() else {
            return;
        };
        let State { plugin, tasks, .. } = state;
        // The run loop and tasks borrow the plugin: drop them first.
        drop(tasks);
        executor::reset();
        plugin.deactivate();
        // SAFETY: `plugin` came from `Box::leak` in `activate`, and nothing
        // that borrowed it survives: the tasks are dropped and the state
        // is gone.
        drop(unsafe { Box::from_raw(core::ptr::from_ref(plugin).cast_mut()) });
    }
}

impl From<crate::bindings::daemon::exports::standard::plugin::daemon_events::DaemonEvent>
    for Event
{
    fn from(
        event: crate::bindings::daemon::exports::standard::plugin::daemon_events::DaemonEvent,
    ) -> Self {
        use super::PaneChangeKind;
        use crate::bindings::daemon::exports::standard::plugin::daemon_events::{
            DaemonEvent as Wit, PaneChangeKind as WitKind,
        };
        match event {
            Wit::FileChanged(change) => Self::FileChanged {
                watch: change.watch,
                paths: change.paths,
            },
            Wit::WatchFailed((watch, error)) => Self::WatchFailed { watch, error },
            Wit::ProcessOutput(output) => Self::ProcessOutput {
                pid: output.pid,
                stderr: output.stderr,
                bytes: output.bytes,
            },
            Wit::ProcessExited((pid, status)) => Self::ProcessExited { pid, status },
            Wit::PaneChanged(change) => Self::PaneChanged {
                kind: match change.kind {
                    WitKind::Created => PaneChangeKind::Created,
                    WitKind::Changed => PaneChangeKind::Changed,
                    WitKind::Exited => PaneChangeKind::Exited,
                    WitKind::Closed => PaneChangeKind::Closed,
                },
                pane: change.pane.into(),
            },
        }
    }
}

impl From<crate::bindings::daemon::exports::standard::plugin::companion::Caller> for Caller {
    fn from(caller: crate::bindings::daemon::exports::standard::plugin::companion::Caller) -> Self {
        Self {
            machine_id: caller.machine_id,
            plugin_id: caller.plugin_id,
            kind: caller.kind,
            epoch: caller.epoch,
            controller_machine_id: caller.controller_machine_id,
            controller_fencing_token: caller.controller_fencing_token,
        }
    }
}