mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-25 10:17:02 -06:00
A filter plug-in gets 50 M instructions and a minute for each run. Filters that order a plotter's paths, pack shapes or run a simulation need minutes, and the app shouldn't freeze while they compute. A filter's manifest may now ask for "budget": "heavy" (Budget): each run gets HEAVY_FUEL (1 G instructions) and HEAVY_WALL_TIME_MS (300 s). Only a filter may ask. plugin.install refuses such a filter without allowHeavy: true, and plugin.inspect returns the entry a module would have without installing it. Install Plug-in… and File › Open ask the user and install on OK; the plug-in store keeps the answer, and folder loads skip heavy filters with a note, since they ask nobody. The app runs a heavy filter on a thread of its own (plugin_runs), not in Background Save's queue, with the status bar and a progress window (Cancel, Continue in Background) that it shares with Background Export (export_progress::window). FilterRun splits plugin.run: prepared on the session, run on any thread, its output handed in, and the next plugin.run with the same params applies that output instead of running the plug-in again, so the edit stays one guarded, journaled command. A result whose document or plug-in changed meanwhile is dropped, and the status bar says to run it again. A cancelled plug-in run is now EngineError::Cancelled. What a filter keeps with the document (#3720) goes with the run: it is given _context.documentData when the run is prepared, and the documentData of its output is applied with its objects, in the same undo step, when the run is handed in (FilterRun::finish). Tests: plugins/tests/abi.rs (100 M loop steps out of a light budget, within a heavy one; folders skip heavy filters), manifest.rs, engine tests_plugins.rs (consent, inspect, the store, hand-in, a handed-in run's document data, cancel) and ui-egui tests_plugin_runs.rs (installing asks, no Preview, a background run as one undo step, a stale result kept out, the progress window). CRAFT_UI_HEAVY_FILTER_WASM=<folder> writes a test filter to try in the app. Its strings and messages have rows in each complete catalog (COMPLETE_MESSAGES). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HhvnnnD5fTFupK8WWq35un
74 lines
3.1 KiB
Rust
74 lines
3.1 KiB
Rust
//! Sandboxed WebAssembly plug-ins for VectorCraft.
|
|
//!
|
|
//! A plug-in is a WebAssembly module that implements the VectorCraft plug-in ABI (v1, documented
|
|
//! in `docs/plugins.md`). It runs in [`wasmi`], a pure-Rust interpreter, with:
|
|
//!
|
|
//! - **no host imports**: no WASI, file system, network, clock or randomness. A module that
|
|
//! imports anything is rejected;
|
|
//! - an **instruction budget** (fuel) per call, metered in slices so a wall-clock deadline is
|
|
//! also enforced (native builds), so infinite loops end with an error. A filter may ask for a
|
|
//! larger one ([`Budget::Heavy`]), which the host lets the user allow before installing it;
|
|
//! - a **linear-memory cap**, a **recursion-depth cap** and wasmi's strict module limits;
|
|
//! - a **fresh instance per run**, so no state leaks between calls.
|
|
//!
|
|
//! Every failure (malformed module, trap, exhausted budget, bad output) is an [`Error`]; nothing
|
|
//! a module does can panic or hang the host.
|
|
//!
|
|
//! Plug-ins see the document as JSON ([`objects`]): the selected paths and compound paths with
|
|
//! their geometry, fills, strokes (gradients included) and opacity. An **object filter** returns
|
|
//! the objects to keep, change or add, applied to the document as one edit
|
|
//! ([`objects::apply_output`]); a **live effect** ([`effect`]) rewrites one object's geometry each
|
|
//! time it is drawn, from an appearance stack; an **exporter** ([`export`]) writes the whole
|
|
//! document in a file format of its own. The process-wide [`registry`] holds the installed
|
|
//! plug-ins. A plug-in's parameters ([`manifest`]) come with what the host's dialogs need to show
|
|
//! them well: labels, units, groups, help and conditions ([`dialog`]).
|
|
#![forbid(unsafe_code)]
|
|
|
|
pub mod dialog;
|
|
pub mod effect;
|
|
pub mod export;
|
|
pub mod manifest;
|
|
pub mod objects;
|
|
pub mod registry;
|
|
mod runtime;
|
|
pub mod wat;
|
|
|
|
pub use dialog::ParamUi;
|
|
pub use manifest::{Budget, Kind, Manifest, Param, ParamSpec};
|
|
pub use runtime::{ABI_VERSION, HEAVY_FUEL, HEAVY_WALL_TIME_MS, Limits, Plugin, heavy_terms};
|
|
|
|
/// What the host puts before a plug-in's id to name what it adds: its live effect
|
|
/// (`plugin.<id>`, [`effect`]) or its file format ([`export`]).
|
|
pub const PREFIX: &str = "plugin.";
|
|
|
|
/// A plug-in error. Every way a module can misbehave ends here, never in a panic.
|
|
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
|
|
pub enum Error {
|
|
#[error("not a valid plug-in module: {0}")]
|
|
Module(String),
|
|
#[error("plug-in ABI: {0}")]
|
|
Abi(String),
|
|
#[error("plug-in manifest: {0}")]
|
|
Manifest(String),
|
|
#[error("plug-in trapped: {0}")]
|
|
Trap(String),
|
|
#[error("plug-in exceeded its {0}")]
|
|
Limit(String),
|
|
#[error("plug-in parameters: {0}")]
|
|
Params(String),
|
|
#[error("plug-in output: {0}")]
|
|
Output(String),
|
|
#[error("plug-in failed: {0}")]
|
|
Failed(String),
|
|
#[error("no plug-in {0:?} is installed")]
|
|
NotFound(String),
|
|
/// The caller asked the run to stop ([`vectorcraft_doc::cancel`]), between two slices of
|
|
/// its instruction budget.
|
|
#[error("cancelled")]
|
|
Cancelled,
|
|
#[error("{0}")]
|
|
Io(String),
|
|
}
|
|
|
|
pub type Result<T> = std::result::Result<T, Error>;
|