effectcraft/crates/plugin/src/lib.rs

67 lines
3.0 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! # effectcraft-plugin (L3)
//!
//! Loads **WebAssembly effect plug-ins** (plug-in API v1, see `docs/plugins.md`) into the effect
//! registry ([`effectcraft_effects::plugin`]). A plug-in is a `.wasm` (or `.wat`) module with
//! no imports that exports:
//!
//! | export | signature | |
//! |---|---|---|
//! | `memory` | memory | linear memory shared with the host |
//! | `ec_api_version` | `() -> i32` | must return 1 |
//! | `ec_manifest_ptr`, `ec_manifest_len` | `() -> i32` | a UTF-8 JSON [`PluginManifest`] in memory |
//! | `ec_alloc` | `(bytes: i32) -> i32` | a buffer of at least `bytes` bytes, 8-byte aligned; it may reuse the previous one |
//! | `ec_render` | `(pixels: i32, width: i32, height: i32, params: i32, nparams: i32, time: f64, scale: f64) -> i32` | process the pixels in place; 0 = ok |
//!
//! For each render the host asks `ec_alloc` for one buffer holding the parameters (`nparams`
//! little-endian `f64`s) followed by the pixels (`width × height × 4` little-endian `f32`s,
//! premultiplied RGBA, row-major), calls `ec_render`, and reads the pixels back.
//!
//! Modules run in the [wasmi](https://github.com/wasmi-labs/wasmi) interpreter (pure Rust,
//! MIT/Apache-2.0) with deterministic floats, no imports (no files, network or clock) and a
//! fuel limit per frame, so a plug-in can't escape its sandbox, hang the app, or render
//! differently on another machine. The runtime is the `wasm` feature (on for the desktop app and
//! the CLI); without it [`load_wasm`] says plug-ins are unavailable.
use effectcraft_effects::EffectSpec;
pub use effectcraft_effects::plugin::{EffectPlugin, PLUGIN_API_VERSION, PluginFrame, PluginManifest, PluginParams, register_plugin};
#[cfg(feature = "wasm")]
mod wasm;
/// Whether this build can load WebAssembly plug-ins.
pub fn wasm_available() -> bool {
cfg!(feature = "wasm")
}
/// Load and register a WebAssembly plug-in (`bytes` = a `.wasm` binary or `.wat` text;
/// `source` = where it came from, for listings). Returns the registered effect.
pub fn load_wasm(bytes: &[u8], source: &str) -> Result<&'static EffectSpec, String> {
#[cfg(feature = "wasm")]
{
let p = wasm::WasmPlugin::new(bytes, source)?;
register_plugin(std::sync::Arc::new(p))
}
#[cfg(not(feature = "wasm"))]
{
let _ = (bytes, source);
Err("WebAssembly plug-ins are not available in this build".into())
}
}
/// [`load_wasm`] for the engine's plug-in loader hook: `{id, name, category, version, api}`.
pub fn loader(bytes: &[u8], source: &str) -> Result<serde_json::Value, String> {
let spec = load_wasm(bytes, source)?;
let m = effectcraft_effects::plugin::plugin(spec.id).map(|p| p.manifest().clone());
Ok(serde_json::json!({
"id": spec.id,
"name": spec.name,
"category": spec.category,
"version": m.as_ref().map(|m| m.version.clone()).unwrap_or_default(),
"api": PLUGIN_API_VERSION,
"params": spec.params.len(),
}))
}
#[cfg(all(test, feature = "wasm"))]
mod tests;