//! The EffectCraft engine façade. //! //! Every user-visible action is a command with a stable id (`layer.newSolid`, `prop.set`, //! `keys.easyEase`…) and JSON parameters, dispatched through [`Session::execute`]. The egui UI, //! the CLI, the control channel and the MCP server all use this one entry point; that is what //! makes the UI swappable and the whole app agent-drivable. //! //! The project is an `Arc` edited copy-on-write; undo keeps whole-project snapshots //! (compositions are `Arc`s, so untouched comps are shared). #![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)] mod analysis; mod background; pub mod cmd; mod footage; pub mod history; mod home; mod interchange; pub mod menus; pub mod preview; pub mod render_queue; pub mod scriptui; mod settings; mod system; pub mod viewer; use std::sync::Arc; use effectcraft_project::{Comp, ItemId, Layer, LayerId, Project, Uid}; use effectcraft_raster::Image; use effectcraft_render::{ExprHost, FootageSource, LayerCache, NoFootage, RenderOpts, Renderer}; use effectcraft_time::Tick; use serde::{Deserialize, Serialize}; use serde_json::Value; pub use analysis::camera_track; pub use analysis::mask_track; pub use analysis::models; pub use analysis::roto; pub use analysis::tracking; pub use analysis::warp; pub use background::jobs; pub use background::offload; pub use background::remote; pub use cmd as commands; pub use cmd::{CommandSpec, command_specs, find as find_command}; pub use effectcraft_color as color; pub use effectcraft_effects as effects; pub use effectcraft_geom as geom; pub use effectcraft_keyframe as keyframe; pub use effectcraft_project as project; pub use effectcraft_raster as raster; pub use effectcraft_render as render; pub use effectcraft_segment as segment; pub use effectcraft_text as text; pub use effectcraft_time as time; pub use effectcraft_track as track; pub use footage::doc_path; pub use footage::footage_check; pub use footage::footage_reload; pub use footage::media_browser; pub use footage::media_cache; pub use footage::sequence; pub use history::{Branch, History, HistoryNode}; pub use home::demo; pub use home::learn; pub use home::links; pub use home::templates; pub use interchange::psd_import; pub use interchange::vector; pub use interchange::xml_project; use interchange::{keyframe_text, keyframe_text_effects}; pub use render_queue::{ExportJob, ExportResult, Exporter, JobState}; pub use settings::autosave; pub use settings::config; pub use settings::prefs; pub use settings::shortcuts; pub use system::guard; pub use system::logging; pub use system::perf; pub use system::storage; pub use system::sysinfo; mod doc_state; mod error; mod event; mod fonts; mod services; mod session; pub use cmd::{animation_presets, text_presets}; pub use doc_state::{CompSelection, EditorState, KeyClip, KeyMove, KeyRef, LinkClip, VertexRef}; pub use error::{EngineError, Result}; pub use event::Event; pub use fonts::{FontRow, font_features, font_menu, font_preview, font_styles, text_families}; pub use services::{FsServices, Importer, Services}; pub use session::upgrade_effects; pub struct Session { pub project: Arc, pub history: History, pub revision: u64, /// Process-unique monotonic revision of the on-canvas editor's committed text and session. /// Unlike [`Session::revision`], unrelated project edits do not advance it; the IME receipt /// uses it when surrounding text is unavailable. Read it with /// [`commands::text_edit::ime_stamp_revision`]. /// /// Stamps come from a process-global checked allocator and are never rewound: a failed /// atomic batch keeps the highest intermediate stamp. On allocator exhaustion no stamp is /// issued, so an unavailable receipt cannot be accepted with a reused revision. pub ime_revision: u64, pub saved_revision: u64, /// The project as last opened or saved: the project is unmodified while it is this very /// snapshot, so undoing back to the saved state clears the modified mark. pub saved_project: Arc, pub path: Option, pub state: EditorState, pub services: Arc, pub footage: Arc, pub expr: Option>, /// GPU compositor (Mercury GPU Acceleration), when the frontend has one: used by renders /// that ask for [`effectcraft_render::Backend::Gpu`]/`Auto`, and by Render Queue exports /// when the project's renderer is the GPU. pub accel: Option>, /// Why there is no [`Session::accel`] (`render.backend`'s `why`): the host's reason, e.g. a /// headless start without `--gpu` or the compositor's setup error. pub accel_note: Option, /// The graphics adapter and backend the window draws with (`NVIDIA GeForce RTX 4070 /// (Vulkan)`), as the host reports it: the System Compatibility Report shows it. pub window_adapter: Option, /// Expression syntax checker (set by the host that links the expression engine). pub expr_check: Option std::result::Result<(), String>>, pub importer: Option>, /// Render Queue encoder (the export layer); `None` = export unavailable. pub exporter: Option>, /// Free-space hook for Render Settings ▸ Use Storage Overflow (`None`: never full). pub storage_quota: Option>, /// The running (or finished, not yet polled) render. pub render_job: Option, /// The running (or finished, not yet polled) track analysis. pub track_job: Option, /// The running (or finished, not yet polled) mask track. pub mask_job: Option, /// The running (or finished, not yet polled) Warp Stabilizer analysis. pub warp_job: Option, /// Warp Stabilizers waiting for (re-)analysis: (comp, layer, effect uid). pub warp_pending: Vec<(ItemId, LayerId, Uid)>, /// The running (or finished, not yet polled) 3D Camera Tracker analysis. pub camera_job: Option, /// 3D Camera Trackers waiting for (re-)analysis: (comp, layer, effect uid). pub camera_pending: Vec<(ItemId, LayerId, Uid)>, /// The running (or finished, not yet polled) Roto Brush propagation / Freeze. pub roto_job: Option, /// Trained models for Roto Brush and face tracking (Settings ▸ Roto Brush / Face Tracking). pub models: models::Models, /// Where installed models live (hosts set it; else `models` next to the settings). pub models_dir: Option, /// Roto Brush instances edited since their last propagation: (comp, layer, effect uid). pub roto_pending: Vec<(ItemId, LayerId, Uid)>, pub events: Vec, /// Commands executed: (id, params). pub journal: Vec<(String, Value)>, /// Processed-layer pixels reused across frames and edits (content-keyed, never stale). pub layer_cache: Arc, /// The last Cryptomatte pick probe (see [`Session::crypto_probe_job`]). Public only so that /// other crates can build a `Session { .. }`; its type is crate-private. #[doc(hidden)] pub crypto_probe: Arc>, /// The persistent disk cache (Settings ▸ Media & Disk Cache), when enabled. pub disk_cache: Option>, /// Settings (Preferences). pub prefs: prefs::Prefs, /// Bumped whenever settings change (frontends re-apply theme, labels…). pub prefs_revision: u64, /// Where settings, shortcut presets and the crash-recovery sentinel are stored (`None` = /// nothing persists). pub config: Option>, /// Keyboard shortcut presets. pub keymaps: shortcuts::Keymaps, /// Scripts' `app.settings`, kept in the settings store (see /// [`commands::scripts::SCRIPT_SETTINGS_FILE`]). pub script_settings: commands::scripts::ScriptSettings, /// Frontend-only commands offered for binding. pub ui_commands: Vec, /// Cache of the resolved active preset (read it with [`Session::shortcuts`]). pub shortcut_table: std::sync::OnceLock, /// Auto-save bookkeeping. pub autosave: autosave::AutoSaveState, /// Host-owned isolation for all auto-saves (e.g. one MCP session). Independent of settings /// and project bookkeeping, so opening/saving a project or changing preferences keeps it. pub autosave_folder_override: Option, /// The viewer snapshot (Take Snapshot / Show Snapshot). pub snapshot: Option, /// The JavaScript scripting engine (set by the host that links `effectcraft-script`): /// `script.run`, File ▸ Scripts ▸ Run Script File… (`.jsx`/`.js`) and the Script Console. pub script: Option, /// Loads WebAssembly effect plug-ins (set by the host that links `effectcraft-plugin` with /// its runtime): `effect.plugins.load`. pub plugin_loader: Option, /// ScriptUI windows and panels opened by scripts (see [`scriptui`]). pub script_ui: scriptui::ScriptUi, /// The last physical-memory reading (Settings ▸ Memory & CPU budgets); `None` = unknown /// (headless sessions don't query it, see [`Session::memory_tick`]). pub sys_memory: Option, /// The keyframe drag in progress (`keys.move` with a merge key). pub key_move: Option, /// Reads the system's memory in the background for [`Session::memory_tick`]. pub memory_watch: sysinfo::MemoryWatch, /// Reads the disk cache's free disk space in the background for [`Session::memory_tick`]. pub disk_watch: sysinfo::DiskWatch, /// Switches Affect Nested Comps as last applied (a change empties the layer cache). pub applied_nested_switches: Option, /// Runs renders and analyses off the UI thread where there are no threads (the web app's /// Web Workers, see [`offload`]); `None` = threads (desktop) or inline (wasm32). pub offload: Option>, /// Analyses running in the [`Session::offload`] worker. pub offloaded: Vec, /// What the Media Browser browses (`None`: the local file system where there is one). pub browser: Option>, /// The browser's storage (the web app; Settings ▸ Disk ▸ Browser Storage, `storage.*`). pub storage: Option>, /// Offloaded jobs that ended, newest last (kind, error): how a caller waiting for one /// (`wait: true` over the control channel in the browser) learns its outcome. pub offload_log: Vec<(offload::JobKind, Option)>, /// Number of older completion entries evicted from the bounded offload log. pub offload_log_start: usize, /// Generic background tasks (Content-Aware Fill, Scene Edit Detection), see [`jobs`]. pub tasks: Vec, /// Finished tasks, newest last (Progress panel, `jobs.list`). pub job_log: Vec, pub next_task_id: u64, /// The running Learn tutorial (Home ▸ Learn), see [`learn`]. pub learn: Option, /// Check footage in the background after File ▸ Open ([`footage_check`]): frontends with an /// event loop (the desktop app) turn this on; headless sessions run `footage.check` when /// they want it. pub check_footage_on_open: bool, /// Footage paths that came from a document, with that document ([`doc_path`]). pub stored_paths: doc_path::StoredPaths, /// Each footage file's size and modification time when last seen (Settings ▸ Import ▸ /// Reload Footage Changed on Disk, [`footage_reload`]). pub footage_stamps: std::collections::HashMap, /// The stamps being taken on a worker thread ([`footage_reload::start_scan`]). pub footage_scan: Option, /// The disk cache's footage salt and the footage it was taken for ([`Session::refresh_disk_salt`]). pub disk_salt: Option<(u64, u64)>, /// How many script-issued commands are running ([`Session::execute_for_script`]): while one /// is, settings that guard what scripts may do can't change (see /// [`prefs::Scripting::same_permissions`]). pub script_calls: ScriptCalls, } /// The count behind [`Session::running_for_script`]; only the engine changes it. #[derive(Clone, Copy, Debug, Default)] pub struct ScriptCalls(u32); /// A thumbnail render ([`Session::thumbnail_job`]): (width, height, RGBA8). pub type ThumbnailJob = Box Option<(u32, u32, Vec)> + Send>; /// A script to run (see [`Session::script`]). #[derive(Clone, Copy, Debug)] pub struct ScriptRequest<'a> { pub code: &'a str, /// File name shown in errors and `$.fileName` (`console` for the Script Console). pub name: &'a str, /// Run in the Script Console's persistent context (variables survive between runs). pub console: bool, } /// Runs a script against the session and reports `{ok, result, output, error: {message, line, /// column, file} | null}`. pub type ScriptRunner = fn(&mut Session, &ScriptRequest) -> Value; /// Loads one WebAssembly effect plug-in (`bytes`, `source` path) into the effect registry and /// describes it (`{id, name, category, version, api, params}`). pub type PluginLoader = fn(&[u8], &str) -> std::result::Result; impl Default for Session { /// A new unsaved session: a default project that is both current and saved, no path, no /// active jobs, the plain `FsServices`/`NoFootage` defaults, default preferences and keymaps, /// empty history, events and journal, and a fresh process-wide IME stamp (`u64::MAX` when the /// allocator is exhausted). fn default() -> Self { let project = Arc::new(Project::default()); Session { saved_project: project.clone(), project, history: History::default(), revision: 0, // A fresh session gets its own process-monotonic stamp even before its first edit. ime_revision: commands::text_edit::next_ime_revision().unwrap_or(u64::MAX), saved_revision: 0, path: None, state: EditorState { snapping: true, view_layout: 1, ..Default::default() }, services: Arc::new(FsServices), footage: Arc::new(NoFootage), expr: None, accel: None, accel_note: None, window_adapter: None, expr_check: None, importer: None, exporter: None, storage_quota: None, render_job: None, track_job: None, mask_job: None, warp_job: None, warp_pending: vec![], camera_job: None, camera_pending: vec![], roto_job: None, models: Default::default(), models_dir: None, roto_pending: vec![], events: vec![], journal: vec![], layer_cache: Arc::new(LayerCache::default()), crypto_probe: Default::default(), disk_cache: None, prefs: prefs::Prefs::default(), prefs_revision: 0, config: None, keymaps: shortcuts::Keymaps::default(), script_settings: Default::default(), ui_commands: vec![], shortcut_table: std::sync::OnceLock::new(), autosave: autosave::AutoSaveState::default(), autosave_folder_override: None, snapshot: None, script: None, plugin_loader: None, script_ui: scriptui::ScriptUi::default(), sys_memory: None, key_move: None, memory_watch: Default::default(), disk_watch: Default::default(), applied_nested_switches: None, offload: None, offloaded: vec![], offload_log: vec![], offload_log_start: 0, browser: None, storage: None, tasks: vec![], job_log: vec![], next_task_id: 1, learn: None, check_footage_on_open: false, stored_paths: Default::default(), footage_stamps: Default::default(), disk_salt: None, footage_scan: None, script_calls: ScriptCalls::default(), } } } #[cfg(test)] mod rq_tests; #[cfg(test)] mod tests;