How oxframe works
You don't need any of this to make animations. Read it if you are curious why scrubbing is instant, why renders are reproducible, or how the playground runs Rust in your browser.
A scene is a coroutine
#[oxframe::scene] turns your async fn into a future. oxframe polls it
with a small single-threaded executor: one poll is one frame. Each awaited
animation updates its signals and returns "pending" until its time is up, so the code after it
runs on the right frame.
Time is virtual: each task keeps its own clock, advanced by exactly one frame per poll. A .to(x, 1.0) that ends between two frames hands the remaining fraction to the next animation,
so a long chain of steps never drifts from where the timeline says it should be.
Everything is deterministic
There is no wall clock and no thread pool in the scene code. Randomness comes from a ChaCha8
generator seeded by Project::seed, and every spawned task runs in a
fixed order. The same code and the same meta.json produce the same frames — on your laptop,
in CI and in the browser.
That is what makes seeking possible: to show frame 900, oxframe replays the scene from its start without drawing anything, which takes milliseconds, and then draws only the frame you asked for.
Signals
Every property — position, color, text — is a Signal. A signal is either a value or a
function of other signals (.bind(…)). Derived signals are lazy: they recompute only
when read and only if something they depend on changed. Cycles are detected and reported instead
of hanging.
From nodes to pixels
- After each frame, the node tree is laid out (flexbox and grid via taffy) and flattened into a display list: fills, strokes, images and layers.
- A renderer draws the list. vello does it on the GPU through wgpu; tiny-skia on the CPU. Layers with blur, shadow or glow are rasterized only within their bounds.
- For video, frames stream to ffmpeg through a pipe while the next ones are being drawn.
Both renderers are checked against the same reference images: the average color difference must stay below ΔE 1 in OKLab, which is below what the eye can see.
The editor and the engine
Your project compiles into an ordinary binary. oxframe dev runs it as a preview
server; the editor in your browser talks to it over a WebSocket with a small JSON protocol (seek, play, set marker…) plus binary frames. When you save, the CLI rebuilds and
restarts the binary, and the editor reconnects to the same frame.
Edits to markers, eases and tunables go the other way: the editor sends them, the engine writes meta.json and re-runs the scene — no compilation needed.
The playground
The playground sends your code to a compile service that builds it to WebAssembly in a sandbox
(results are cached by the hash of the source and SDK version). The same engine then runs inside a
Web Worker in your page. With WebGPU it renders on your GPU through vello into an OffscreenCanvas; without it, it falls back to the CPU renderer. The editor speaks the
same protocol to the worker as it does to oxframe dev, so they behave the same.
Where the code lives
oxframe-sdk | The Rust crates: core (executor, signals, easing), 2d (nodes, layout), text, render, export, preview, protocol. |
oxframe-cli | The oxframe command and the embedded editor build. |
oxframe-web | The editor UI, this site, the playground and the compile service. |