mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-09 17:44:08 +00:00
A plug-in whose manifest gives "api": 2 and lists the new `progress` permission may import three host functions from module `env`: vc_progress(done, total), vc_cancelled() -> i32 and vc_log(level, ptr, len) -> i32. Installing checks every import and refuses, by name, one the host doesn't offer, one with another type and one the manifest doesn't allow. - Progress is clamped and passed on once per tenth of a percent (past 2,000 reports, once per 1,000 changes). A heavy filter's progress window and the export's progress window show it as a percentage bar, and their Cancel makes vc_cancelled return 1. A live effect's progress is not shown. - A run cancelled before it returns ends as cancelled, and one that returns after its time budget ran out ends at that budget, so what a plug-in returns after it sees vc_cancelled changes nothing. This also applies to a plug-in of API level 1 that finishes after a Cancel or past its time budget. - Log text must lie in the module's memory and be UTF-8. Lines go to the app's log as "<id>: <text>" with hidden characters escaped, at most 1,024 characters a line, 200 lines and 32 KiB a run, and the host reads at most 1 MiB of log text a run. - plugin.list and plugin.info give the manifest's `api`; the install prompt names the new permission in plain words. Docs: docs/plugins.md, docs/mcp.md, docs/target-app-parity.md.
79 lines
3.4 KiB
Rust
79 lines
3.4 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 but the three of API level 2** ([`host`]: progress, Cancel and log lines,
|
|
//! with the `progress` permission): no WASI, file system, network, clock or randomness. A module
|
|
//! that imports anything else 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`]). What a plug-in may do is
|
|
//! the set of [`permissions`] its manifest lists, which the user approves when it is installed.
|
|
#![forbid(unsafe_code)]
|
|
|
|
pub mod dialog;
|
|
pub mod effect;
|
|
pub mod export;
|
|
pub mod host;
|
|
pub mod manifest;
|
|
pub mod objects;
|
|
pub mod permissions;
|
|
pub mod registry;
|
|
mod runtime;
|
|
pub mod wat;
|
|
|
|
pub use dialog::ParamUi;
|
|
pub use host::{LogLevel, LogLine, Report};
|
|
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>;
|