Skip to main content

FfiNode

Trait FfiNode 

Source
pub trait FfiNode:
    Send
    + Sync
    + 'static {
    // Required methods
    fn node_type(&self) -> RString;
    fn process(&self, input: RVec<u8>) -> FfiFuture<RResult<RVec<u8>, RString>>;

    // Provided methods
    fn process_multi(
        &self,
        input: RVec<u8>,
    ) -> FfiFuture<RResult<RVec<RVec<u8>>, RString>> { ... }
    fn process_streaming(
        &self,
        input: RVec<u8>,
        sink: OutputSinkBox,
    ) -> FfiFuture<RResult<usize, RString>> { ... }
    fn initialize(
        &self,
        _session_id: RString,
        _node_id: RString,
    ) -> FfiFuture<RResult<(), RString>> { ... }
}
Expand description

FFI-safe node.

process returns an FfiFuture — an ABI-stable future the host can .await directly. Plugin-side async runtimes (or none) work as long as the future polls to completion without referencing runtime-specific globals; in practice, plugins that call back into host services do so by polling synchronous state from the async block.

§Forward compatibility (multi-output extension)

process is marked #[sabi(last_prefix_field)] — that’s the cut between the original FFI surface (1.x) and any methods added in minor versions. Methods added below it must carry a default impl so older plugins (whose vtables only expose process) continue to load. Hosts compiled against the newer ABI then transparently fall back to the default whenever a plugin omits the new method.

process_multi is the multi-output sibling of process: a node’s process_streaming callback can fire N times per input (think SileroVAD emitting Json(event) plus the audio passthrough), and the single-output process would silently drop everything but the first emission. process_multi returns the full RVec so the host can dispatch each blob into the streaming callback chain.

The default impl wraps process as a 1-element RVec so plugins that only implement single-output stay correct (just lossy when the underlying node was actually multi-output — same behaviour as before this method existed).

Required Methods§

Source

fn node_type(&self) -> RString

Source

fn process(&self, input: RVec<u8>) -> FfiFuture<RResult<RVec<u8>, RString>>

Provided Methods§

Source

fn process_multi( &self, input: RVec<u8>, ) -> FfiFuture<RResult<RVec<RVec<u8>>, RString>>

Multi-output process. Returns ALL emissions from one input as a flat RVec<RVec<u8>> (each inner vec is one rmp-serde RuntimeData blob, in emission order).

Default impl: delegate to process and wrap its single output as a 1-element vec. Plugins that haven’t been rebuilt against the multi-output ABI keep working — just without multi-output semantics. Plugins that override this method get full N-output fidelity through the LoadableNodeAdapter host wiring.

Source

fn process_streaming( &self, input: RVec<u8>, sink: OutputSinkBox, ) -> FfiFuture<RResult<usize, RString>>

Per-frame streaming process. Same wire format as process_multi (msgpack-encoded RuntimeData blobs in emission order) but each frame is delivered to sink as it arrives rather than accumulated and returned at the end. Returns the total emission count on completion.

Why this exists: process_multi collects every emission into an RVec<RVec<u8>> before returning, so a streaming node that yields audio chunks over wall time (TTS, STT) emits nothing downstream until the whole generation finishes, then bursts every chunk at once. Real-time playback then perceives latency equal to total generation time. process_streaming fixes this by handing the plugin a OutputSinkBox that forwards each emission immediately.

Default impl: delegates to process_multi and pushes each returned frame to the sink. Functionally correct but loses the real-time benefit — plugins must override this method (in practice via LoadableNodeAdapter, which forwards inside the inner node’s own callback) to actually stream. Plugins on older ABI versions (vtable lacks this slot) fall back to the default, preserving load compatibility.

Source

fn initialize( &self, _session_id: RString, _node_id: RString, ) -> FfiFuture<RResult<(), RString>>

One-time, per-session initialization hook for lazy-load plugins.

Forwarded from the host’s AsyncStreamingNode::initialize() once per session, before the first process call. Plugins that do all their work eagerly inside FfiNodeFactory::create() (e.g. audio2face’s Audio2FaceLipSyncNode::load, live2d-render’s WgpuBackend::new) can leave this defaulted. Plugins with a non-trivial init (e.g. llama-cpp spawning a worker thread that loads a multi-GB GGUF) must override it — without forwarding, the worker is never spawned and process returns “worker not running”.

session_id and node_id are forwarded as RStrings so the plugin can log / tag work with them. emit_progress is NOT forwarded today — progress events emitted from inside a loadable plugin’s initialize() are silently dropped. Plugin authors who need progress visibility should wrap heavy init in the host (e.g. via WarmSessionPool::prewarm which fires its own progress before delegating).

Default impl: no-op. Older plugins not rebuilt against this method keep compiling, just without lazy-init semantics.

Dyn Compatibility§

This trait is dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementors§

Source§

impl<_ErasedPtr> FfiNode for FfiNode_TO<_ErasedPtr>
where Self: Send + Sync + 'static, _ErasedPtr: AsPtr<PtrTarget = ()>,