Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
mittens-engine 0.7.0
A hypermedia / web development inspired game engine specially made for social vr, vtubing, visual novels, css UI / spatial layout, and 3D character animation.
Workspace crates
mittens-engine0.7.0 at the workspace root: Vulkan/OpenXR rendering, ECS, engine component materialization, and the Mittens-specific scripting host.meow-meow-script0.6.0: host-neutral syntax, parser, runtime/session evaluator, configurable component/API catalogs, and the generic host protocol.mittens-query0.6.0: CSS/MMQ parsing and host-neutral query-tree evaluation.
The dependency graph is acyclic: mittens-engine depends on both standalone
crates; neither standalone crate depends on the engine.
mittens-engine 0.7 removes the deprecated ActionComponent, ActionSystem,
and Action.* MMS constructors. Deferred animation now uses executable
Keyframe.at(...) { ... } blocks that capture live component handles. The
standalone meow-meow-script and mittens-query crates remain at 0.6.0.
For scripting, see the MMS language overview, the
meow-meow-script crate and its Mittens integration,
and the host API boundary.
Running examples
- Run examples in release mode by default:
cargo run --release --example <name>. - Avoid debug example runs unless you specifically need debug-only diagnostics or faster compile iteration.
- Large
.glbmodel assets are omitted from the crates.io package. Every example callsmittens_engine::example_support::ensure_model_assets()before scene setup; ifassets/modelscontains no.glbfiles, it runsscripts/download-model-assets.shto fetch the example model bundle from GitHub. Run the script manually to prefetch models. SetMITTENS_MODEL_ASSET_BASE_URLto override the download source.
Windowing
- uses winit to make a window and passes the RawDisplayHandle to renderer to render into the window
- provides user input events and frame loop
Universe
- holds all the layers below,
- and provides simple API to build component trees and add them to the world
Universe API (common helpers)
The engine::Universe type is a convenience wrapper around World + SystemWorld + VisualWorld + CommandQueue.
In addition to add(...) and attach(parent, child), it provides a few higher-level helpers for
prefab-style workflows and safe subtree removal:
-
attach_clone(parent, prefab_root) -> Result<ComponentId, String>- Clones the component subtree rooted at
prefab_root(freshComponentIds and fresh GUIDs) and attaches it underparent. - Clone is done via component
encode/decodeusingComponentCodec(no JSON round-trip). - Note: if any components contain references to other components (e.g. action targets stored inside component payloads), those references are currently copied as-is and may need a future fixup pass.
- Clones the component subtree rooted at
-
remove_child(parent, index) -> Result<ComponentId, &'static str>- Detaches the child immediately and queues deletion of that child subtree via the command queue.
- Deletion is applied when the command queue is processed (after systems tick), so systems/visuals can cleanly unregister.
-
remove_children(parent) -> Result<Vec<ComponentId>, &'static str>- Detaches all direct children and queues deletion of each child subtree (applied on command processing).
Example (prefab clone):
use engine;
let prefab_root: ComponentId = /* detached prefab subtree root */;
let parent: ComponentId = /* some TransformComponent in the live scene */;
let instance_root = universe.attach_clone?;
// GUID is stored on the component record:
let guid = universe.world.get_component_record.unwrap.guid;
(component) World
- stores list of components and topology (parent / child relationship between components)
- components can have subcomponents
- specific types of components register with SystemWorld and have methods that also call SystemWorld
- registration / removal and methods of components that affect SystemWorld go through a CommandQueue and get applied after systems.tick() in the update loop.
SystemWorld
- handles the behaviors of components
- can have one system's method invoked and then defer to one or more other systems
- can call methods on components (via CommandQueue)
- calls to component methods are applied after all systems have run their tick() method.
RenderableSystem
- keeps a queue of CPUMesh from RenderableComponent that need to be converted to GpuMesh and uploaded into the GPU.
VisualWorld
- stores a snapshot of GpuRenderables
- and builds cache, sorted by material pipeline, mesh, and texture
- when ever RenderableSystem or LightSystem (or TransformSystem if involving renderables, lights or cameras) updates.
RenderAssets
- converts
CPUMeshintoGPUMesh
VulkanoRenderer
- displays data from VisualWorld through vulkan
- TODO: make WgpuRenderer for web / webasm
Render phases (render graph summary)
Rendering is recorded in a single dynamic-rendering scope, but split into explicit phases (a small “render graph”) built by VisualWorld and recorded by VulkanoRenderer.
Current phase order (high level):
- Background (instanced, no depth write)
- Background occluded+lit (instanced, depth write ON for self-occlusion)
- Then the renderer clears depth so background never occludes the foreground.
- Opaque (instanced, depth write ON)
- Cutout (instanced, alpha-tested)
- Transparent single-layer (instanced)
- Transparent multi-layer (sorted back-to-front, drawn one-by-one for correct blending)
See docs/render-phases.md for details and the relevant code entry points.
Components
See the MMS component guide for the exhaustive component catalog and current scripting support.
Transforms
Transforms are central in Mittens: most component subtrees are rooted at a TransformComponent, and many engine systems interpret the component tree as “a scene graph of nested transforms + things attached under them”.
- Brief intro:
TransformComponent
- Stores local TRS (translation / rotation / scale) and a cached
matrix_world. - Local TRS is represented as a model matrix (
transform.model), and the engine propagates it through the component tree to computematrix_worldfor nested transforms.
- How transforms can be nested
- A
TransformComponentcan parent otherTransformComponents. - Nesting is defined by the ECS topology (parent/child relationships in the component tree).
- What that means for model vs
matrix_worldpropagation
- Each transform has a local
modelmatrix derived from its TRS. - World-space transforms are computed by multiplying ancestor models down the tree:
matrix_world(child) = matrix_world(parent) * model(child)
TransformSystemcachesmatrix_worldon eachTransformComponentand uses it as the source of truth for systems that need world-space.- Topology changes (Attach/Detach) can require recomputation even if local TRS didn’t change; the engine has a dedicated intent for that (
UpdateTransformWorld).
- Which systems are affected by transforms
RenderableSystem/VisualWorld: instance model matrices for renderablesCameraSystem: camera view/projection updates when parent transform changesLightSystem: point light world-space position updatesCollisionSystem: collider world-space updatesSkinnedMeshSystem: joint world matrices / skinning matrices become dirtyBvhSystem+RaycastSystem: BVH refit and raycast correctness depends on world matricesOpenXRSystem: XR devices/cameras often read/write world transforms- Editor gizmos: visual alignment + drag application depend on consistent
matrix_world
- Which systems determine / write transform intents
- User code: calling
TransformComponent::{set_position,set_rotation_*,set_scale}queuesUpdateTransform InputSystem: movement/controls update transforms viaUpdateTransformOpenXRSystem: device pose application usesUpdateTransformCollisionResponseSystem: kinematic collision response integrates motion viaUpdateTransformTransformGizmoSystem: editor gestures call transform setters (which queueUpdateTransform)
4.1 Transform propagation pipelines / transform operators
TransformSystem::transform_changed(...)is the core propagation pipeline: it recomputes cachedmatrix_worldfor a transform subtree and pushes side effects to dependent systems.TransformFilterComponentis a “filter-as-node” operator that changes what descendants inherit (e.g. inherit translation+rotation but not scale). It’s used heavily for editor/gizmo visuals.- For deeper notes/specs:
TransformFilterComponentmotivation:docs/analysis/gizmo-transform-propagation.md- Gizmo coord spaces (Local/World):
docs/spec/editor-gizmo-coord-spaces.md - Transform update flow and refit/rebuild behavior:
docs/analysis/refresh-transform.md
Signals
This engine uses an explicit drain-point signal model.
Instead of letting systems mutate everything immediately (and in arbitrary order), code emits signals into the per-frame queue, and the engine drains them in a consistent sequence.
Signal types:
-
Events (
EventSignal): facts/observations ("parent changed", "drag started", ...).- Dispatched to handlers (global handlers and/or scoped handlers rooted at a scope subtree).
- Event handlers should be observers: they typically emit follow-up intents rather than directly performing large mutations.
-
Intents (
IntentSignalcarrying anIntentValue): requests for side effects ("attach", "set transform", "remove subtree", ...).- Executed at drain points.
- Can be scheduled for the future via
at_beat(...)(timed intents sit in a holding-pen until due).
Execution order (inside SystemWorld::process_signals):
-
Dispatch ready events to handlers.
- Any events emitted by handlers are deferred to the next tick.
- Any intents emitted by handlers are queued for later execution.
-
Promote timed intents that have become due at the current beat.
-
Execute ready intents.
- Intents may emit more intents; those will run later in the same tick (after queue draining), up to the
max_signalscap. - Intents may also emit events, but (like handler-emitted events) those are deferred to the next tick.
- Intents may emit more intents; those will run later in the same tick (after queue draining), up to the
Implementation detail: intent execution is split into two layers:
RxIntentExecutor: “interpretation” intents that expand into follow-up work.RxMutationExecutor: low-level canonical mutations (register/update/remove, etc.).
Scoped handler lifecycle: systems can install handlers rooted at a component subtree (e.g. gizmos). When a subtree is removed, any scoped handlers rooted in that deleted subtree are removed automatically.
See the MMS signal guide for the architecture, exhaustive signal catalog, and current scripting support.
Building Widgets (Panels & Tools)
Complex editor UI (like the inspector_panel, paint_panel, or world_panel) follows a data-driven projection pattern.
1. State & Reducers
Panels define their own domain-specific state and a pure reducer function to handle events.
- State: e.g.,
InspectorWorkspaceStateorPaintState. - Reducer:
fn reduce_state(old: &State, event: &Event) -> State.
2. Event Adapters
Raw engine events (clicks, drags, signal emissions) are converted into high-level domain events by "adapters" (often scoped signal handlers installed at the widget root).
3. Data Renderer System
The DataRendererSystem manages the lifecycle of projecting a list of data items into a live component subtree. It ensures that when data changes, the previous visual subtree is cleaned up and a fresh one is attached to the target slot.
4. RendererSpec
You define how each item in your data list should be rendered using a RendererSpec<T>:
- RendererSpec::Mms: Project data into an MMS component factory.
Mms - RendererSpec::Rust: Build the component tree directly in Rust.
Rust
Working with MMS Components
For simpler widgets or reusable UI elements, you can define factory functions in .mms scripts.
Calling component methods from animation keyframes
Keyframe blocks run in the live world when the keyframe becomes due, so they can call methods on captured component handles directly.
let cube_t = .
cube_t
This is the simplest pattern for authoring animation-driven behavior in MMS: capture a live component handle with let, then mutate it from Keyframe.at(...) blocks.
Playing and pausing an animation from MMS
Animation components are also live handles, so you can store them in a variable and call playback methods from signal handlers or other script logic.
let anim = Animation.
anim
let pause_btn = T..
let play_btn = T..
pause_btn
See examples/component-method-call.mms for a complete runnable example.
Reusable Buttons (button.mms)
The assets/components/button.mms file provides a standard button:
import from "assets/components/button.mms"
let my_btn =
// Attach signal handlers directly in MMS:
See assets/components/ for more reusable primitives.
REPL / CLI
There is a small stdin-driven REPL (processed on the main thread in Universe::update()) for inspecting the component tree.
Commands
help— print commandsls— list children of the current working component (or roots at/)cd <name|index|guid|path>— change working componentcd /goes to rootcd ..goes to parentcd /7v1:root/8v1:childwalks byComponentIdtokens and namescd <guid>supports a global jump by GUID
pwd— print a copy-pastable path for the current working componentcat [path]— pretty-print JSON serialization of the subtreecatwith no args prints from the current working componentcat /prints the whole scene (all roots)
clear/cls— clear the terminal
Pipes
Pipes use | but they pipe component objects (ComponentIds), not strings.
- A trailing
|prints anls-style summary of the piped components.- Example:
cat / |
- Example:
grep
grep <pattern> filters the piped components by matching against component properties (including name, type, guid, and encoded fields), and prints the full serialized value of any matching property.
- Example:
ls | grep color - Example:
cat /6v1:input | grep camera
Lifecycle
Frame loop:
// in engine::Universe:
/// Game/update step
https://github.com/user-attachments/assets/ce4ac311-1087-4792-bec8-5dd012d848f2
Profiling (flamegraph)
You can profile binaries (including examples) using cargo-flamegraph without adding any dependency to this project.
Prereqs:
- Install the Cargo subcommand:
cargo install cargo-flamegraph - Linux: install
perf(Arch/EndeavourOS:sudo pacman -S perf)
Example (profile an optimized release build of an example with debug symbols and frame pointers):
CARGO_PROFILE_RELEASE_DEBUG=true \
RUSTFLAGS="-C debuginfo=1 -C force-frame-pointers=yes" \
Credits:
Special thanks to 2gd4.me for designing font_system.png