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§
fn node_type(&self) -> RString
fn process(&self, input: RVec<u8>) -> FfiFuture<RResult<RVec<u8>, RString>>
Provided Methods§
Sourcefn process_multi(
&self,
input: RVec<u8>,
) -> FfiFuture<RResult<RVec<RVec<u8>>, RString>>
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.
Sourcefn process_streaming(
&self,
input: RVec<u8>,
sink: OutputSinkBox,
) -> FfiFuture<RResult<usize, RString>>
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.
Sourcefn initialize(
&self,
_session_id: RString,
_node_id: RString,
) -> FfiFuture<RResult<(), RString>>
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".