A lightweight, retained-mode, multiplatform UI engine for applications — with Lua as the application language, and a performance contract that cannot silently regress.
Status: pre-alpha (M0 complete, M1 in progress). The perf harness, the scene, the reactive Lua graph and the CI for heavy testing exist. Layout, text, rasterisation, GPU and widgets do not yet. That ordering is deliberate — see Why the harness came first and the milestone plan in docs/PLAN.md.
A rendering engine and widget substrate for desktop, mobile and web apps, built around four claims that will each be measured before they are stated:
- Fastest incremental frame of any lightweight toolkit — proven against egui/iced/Slint on one identical scene, not asserted.
- Zero idle cost — an app doing nothing runs no user code, allocates nothing, and requests no frame.
- Apps written in Lua, with no performance cliff for choosing to.
- Feature parity with egui, then a superset: layout, animation, i18n and accessibility are engine concerns, not bolt-ons.
"Super light" is a constraint with numbers attached, not an adjective.
| Property | Target | Gate |
|---|---|---|
| Steady-state allocations per frame | 0 |
crates/mre-bench/tests/gate_alloc.rs |
| Scene node size | 64 B (one cache line) | crates/mre-bench/tests/gate_structure.rs |
| 50k-row table, virtualised | < 64 KiB of scene memory | crates/mre-bench/tests/gate_structure.rs |
| Draw calls, reference scene | <= 64 | crates/mre-bench/tests/gate_structure.rs |
| Frame time, 120 Hz | p99 <= 8.33 ms | perf/budget.toml + bench |
| Idle cost | zero computations, zero frames | mre-lua tests + bench |
| Compile time, warm | < 5 s | tracked, not yet gated |
| Core binary | <= 2 MB | release.yml (planned) |
local mre = require("mre")
local count = mre.signal(0)
local pop = mre.spring(1.0, { stiffness = 240, damping = 22 })
mre.effect(function() pop.target = 1 + count() * 0.04 end)
mre.mount(function()
return mre.column {
gap = 16, padding = 24,
mre.text { "Clicked " .. count() .. " times", size = 20 },
mre.button {
label = "Increment",
on_click = function() count(count() + 1) end,
transform = { scale = pop() },
},
}
end)Run the working example today:
cargo run -p mre-lua --example headless -- examples/counter.lua --interacttree:
(column (text "MRE counter") (text "Not clicked yet") (row (button) (button)))
stats: Stats { signals: 2, computations: 3, dirty: 0, last_flush_runs: 1, ... }
after two clicks:
tree: (column (text "MRE counter") (text "Clicked 2 times") (row (button) (button)))
reruns: 3 (the view, the memo and its one observer; writes were coalesced)
idle flush ran 0 computations (expected 0)
The API is specified in docs/LUA-API.md. The current
implementation covers signals, effects, memos, untracked, batching, cycle
detection and the declarative vnode surface. Components, fine-grained property
bindings and hot reload are M4.
crates/
mre-core/ ids, generational arena, geometry, colour (no deps, no unsafe)
mre-perf/ budget contract, allocation counter, statistics
mre-scene/ retained tree, dirty flags, display lists, reference table
mre-lua/ reactive graph + Lua host (the only heavyweight dependency)
mre-bench/ benchmark runner + the performance gates
examples/
counter.lua a complete app, in Lua
perf/
budget.toml THE performance contract, compiled in at build time
scenes/table.toml the reference scene every engine must render
results/ machine-readable run output (gitignored)
baselines/ per-engine adapters for comparison (not workspace members)
docs/
PLAN.md the full roadmap, milestones and exit criteria
LUA-API.md the scripting contract
INPUT.md pointer, gestures, keyboard, IME, accessibility
FEATURE-PARITY.md the egui parity matrix
ARCHITECTURE.md layering and dependency rules
PERF-CONTRACT.md how budgets are enforced
COMPARISON.md the fairness protocol and current results
cargo test --workspace # every perf gate + Lua tests
cargo run --release -p mre-bench # benchmark the reference scene
cargo run --release -p mre-bench -- --count-allocs
cargo run --release -p mre-bench -- --strict # fail if the contract is violated
cargo run --release -p mre-bench -- compare # table vs. baseline engines
cargo run -p mre-lua --example headless -- examples/counter.lua --interactBenchmarks, sanitizers, Miri and cross-platform builds run on GitHub Actions so the development machine stays for editing and fast tests.
| Workflow | Trigger | What it does |
|---|---|---|
ci.yml |
every push | fmt, clippy -D warnings, docs, gates, Lua smoke, 3-OS test matrix |
bench.yml |
PR + nightly | strict contract, interleaved A/B/A/B against egui, artifact upload |
nightly.yml |
nightly | Miri, AddressSanitizer, ten-run sustained stress |
cross.yml |
nightly + PR | wasm32 build; Android and iOS compile smoke |
A performance goal written in a README is a wish. A performance goal that fails the build is a property.
So before the first triangle: a counting allocator that turns "zero allocations per frame" into an assertion, a budget file compiled into constants, percentile reporting that makes a 40 ms hitch impossible to hide behind a 4 ms mean, and a reference workload real enough that optimising for it optimises for real apps.
- No
unsafein the engine crates.unsafeis confined to allocator instrumentation, where it is unavoidable and audited. - No allocation in a steady-state frame. Not "few". Zero.
- One arena, no
Rc<RefCell>trees. Nodes are 64 bytes and cache-linear. - Retained and reactive. Work is O(changed), not O(UI).
- Dirty flags, not full rebuilds. An unchanged subtree costs one pointer.
- Compile speed is a feature. Small crates, dependency-light core, no proc macros in the engine path.
- Honest benchmarks. Interleaved runs, published losses, scope caveats next to every number. See docs/COMPARISON.md.
MIT OR Apache-2.0.