Sitelet https://github.com/DreamWeave-MP/l3i
Skip to content

Latest commit

 

History

278 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

l3i

The Luau runtime for DreamWeave. A Rust binder for Luau 0.740 that owns its own Luau build, plans every VM before it exists, and lowers hot script calls to native code.

l3i is its author's second Luau binder. The first is C++: components/luau, which Dave Corley (S3kshun8) wrote for OpenMW on the feat/least-mergeable-branch branch of his fork. That branch is not in OpenMW's master and may never be: OpenMW's own scripting is components/lua, and OpenMW has no Luau binder. l3i is that binder done again in Rust, then given what a multi-crate engine needs on top. "The C++ binder" in the documentation is that one, and every components/luau/... file and testluau*.cpp test the sources cite is a file on that branch, at commit f69579c8. Where the fast paths came from links each optimisation to the C++ lines it started as. l3i is not affiliated with or endorsed by the OpenMW project.

Documentation: https://DreamWeave-MP.github.io/l3i/. This file is the short version.

What it is

No mlua, no binding crate Luau is a git submodule compiled by build.rs. The whole C API is declared by hand in l3i::ffi, and a safe, scoped layer sits over it.
Arguments read from the value layout The typed binder reads stack slots straight from Luau's 16-byte TValue. A bound (f64, f64) -> f64 call retires 369 instructions against 277 for a bare lua_CFunction.
Tags, atoms and slots are plan data A RuntimePlan assigns userdata tags, Luau's 32 compiler type slots, atoms and direct-access slots per VM, so one Rust type can be tag 8 in one runtime and untagged in another.
Typed by construction Every module member carries a Luau signature. The plan renders the .d.luau, and the analysis feature type checks strict scripts against it in the crate's own tests.
The network is not optional Every plan carries the dream.net bridge; the policy's capabilities decide what a script may do with it.
Lowering hooks in Rust With jit, hosts write Luau's userdata and vector lowering hooks against an IrBuilder C ABI. Quaternions, colours, byte reads and vertex writes compile to IR with no C call.

Nothing here is a catalogue: tags, atoms, type names and debug-name roots are host data.

Quick start

[dependencies]
l3i = { version = "1", features = ["jit"] }   # jit, analysis, soft-render, bytes(-codecs, -digests, -text), intern, syntax, fs, process are optional

l3i builds only with clang, lld and cross-language thin LTO, and Cargo does not inherit a dependency's config, so copy the [env] and rustflags lines from this repository's .cargo/config.toml into your own. build.rs refuses anything else and names the missing piece; L3I_UNVERIFIED_TOOLCHAIN=1 turns that into a warning. Start here walks through it.

use l3i::Runtime;
use l3i::userdata::{Owned, Userdata, tagged};

struct Vec3 { x: f32, y: f32, z: f32 }

unsafe impl Userdata for Vec3 {
    const NAME: &'static str = "dreamweave.Vec3";
}

fn main() -> l3i::Result<()> {
    let runtime = Runtime::new()?;
    // The host picks the tag, per runtime, at registration.
    tagged::register::<Vec3>(&runtime, 10, |ty| {
        ty.property("x", |v: &Vec3| v.x)?;
        ty.method("length", |v: &Vec3| (v.x * v.x + v.y * v.y + v.z * v.z).sqrt())
    })?;
    let make = runtime.bind_function("dreamweave.vec3", |x: f32, y: f32, z: f32| Owned(Vec3 { x, y, z }))?;
    runtime.set_global("vec3", &make)?;
    runtime.exec("local v = vec3(3, 4, 0) assert(v.x == 3 and v:length() == 5)")?;
    Ok(())
}

That is the hand-assembled runtime. An engine composes one from extensions instead:

use l3i::Runtime;
use l3i::extension::{RuntimePlan, RuntimePolicy};
use l3i::quat::QuatExtension;
use l3i::raster::RasterExtension;

let plan = RuntimePlan::builder()
    .policy(RuntimePolicy::new().compat_global("@dream/quat", "quat"))
    .extension(QuatExtension)
    .extension(RasterExtension)
    .finalize()?;
let runtime = Runtime::from_plan(&plan)?;            // any number of these per plan
std::fs::write("dream.d.luau", plan.type_definitions())?;

What you get

Each row is a page of the guide.

Area In one line Read
Stack and values Borrowed views bound to a Frame, owned registry pins, and a typed binder that accepts scalars, views, &T receivers, Option<T> with OpenMW's rules, VarArgs, Overload, and returns tuples, Owned<T>, Result<T> and more. Stack and values
Userdata Tagged (inline payload, one tag read to check) or untagged (exact metatable identity, borrowed engine objects), decided per runtime; MetatableBuilder for methods, properties, metamethods and __iter factories; generated dispatchers that cost one Luau call frame. Userdata
Modules and sandboxes Frozen package tables, read-only views, OpenMW's prelude, one environment per script instance cloned from a compiled template, require over a host navigator. Modules and sandboxes
Runtime options Watchdog on time and heap, memory categories, call scopes with self-time accounting, collector control, a sampling profiler, frozen fast flags. Runtime options
Direct access Atoms let GETTABLEKS/NAMECALL reach a native callback with no metatable walk; DirectPlan validates Luau's inline cache in O(1) and serves a type under any tag. Direct access
Extensions and plans A crate declares its Luau surface once (describe), a plan composes crates and assigns every tag, slot and atom (finalize), a runtime is built from it (from_plan); the plan renders the .d.luau and can type check it. Extensions
Primitives BytesView, BufferView, Exact<T>, Integer, Bits64, PackedScalar with a kind registry, strict Options tables, in-place table walks, Sequence and Stream views. Primitives
Built-in extensions @dream/net (in every plan), @dream/quat, @dream/raster, @dream/bytes, @dream/intern, @dream/luau, @dream/soft-render, @dream/fs, @dream/process. Built-in extensions
Native code Luau's CodeGen with lowering hooks written in Rust; which call sites lower and why. Native code
The rest of the VM Coroutines, the debug API, memory and GC controls, libraries, require, Luau's analysis frontend. Coroutines, debugging and the rest
Rust API Every public module. Rust API

Built-in extensions

Extension Module What it is
dream.net @dream/net The dream-net bridge, in every plan: schemas, capability-gated Luau clients and servers, opaque server keys with Luau token minting, pollInto with one payload copy and no allocation.
dream.quat @dream/quat Unit rotations packed into one Luau integer (smallest-three, 18 bits per component) and animation keys; quat.math() lowers rotate to 21 ns native.
dream.raster @dream/raster RGBA8 colours, clip rectangles and RGBA16 colours as packed integers; raster.math() lowers colour arithmetic.
dream.bytes (bytes) @dream/bytes For parsing foreign file formats in script: searching, C strings, varints, big-endian and half-float reads lowered natively, plus codecs, digests and text codepages behind bytes-codecs, bytes-digests, bytes-text.
dream.intern (intern) @dream/intern Textual identity as dense numbers: pools that intern strings or buffer spans exactly, ASCII case-insensitively, or by their normal form under byte rules (case, replaced bytes, collapsed runs, trimmed ends), folding inside the hash, with no Luau string made for a duplicate.
dream.luau (syntax) @dream/luau Luau's own parser for scripts: the syntax tree with exact spans, comments, errors and the token stream, built as tables in native code, typed for strict walkers.
dream.soft_render (soft-render) @dream/soft-render dream-soft-render as a CPU rendering device, byte-identical from Luau and from Rust.
dream.fs (fs) @dream/fs The host filesystem over byte paths: reads, memory-mapped readers, writers, metadata, a columnar walk, links and identity; what the OS refuses is an answer, not an error. Behind filesystem.read and filesystem.write.
dream.process (process) @dream/process Child processes with their exit code and output, behind process.spawn; the environment behind process.environment; unbuffered writes to stdout and stderr.

Features

Feature Adds Extra dependencies
jit Luau's CodeGen, lowering hooks in Rust, IR and assembly dumps none
analysis Luau's type checker, linter, autocomplete and parser; RuntimePlan::check_definitions none
bytes The @dream/bytes extension memchr
bytes-codecs DEFLATE, LZ4, zstd and LZMA/XZ miniz_oxide, lz4_flex, ruzstd, lzma-rs
bytes-digests CRC-32 to BLAKE3, one-shot and incremental crc32fast, xxhash-rust, RustCrypto, blake3
bytes-text Every WHATWG text encoding encoding_rs
bytes-regex Regular expressions over bytes, many ranges in one call regex
intern The @dream/intern extension none
syntax The @dream/luau extension none
fs The @dream/fs extension memmap2, walkdir, same-file
process The @dream/process extension none
soft-render The dream.soft_render extension dream-soft-render

The default feature set is empty. Networking is not a feature: dream-net is a dependency. Everything optional is pure Rust.

The rules

The short form of the safety model, which also records the Luau facts the ecosystem builds on and the deliberate divergences from the C++ binder.

  • Luau is built with C++ exceptions. A Lua error inside a bound function unwinds through Rust frames to Luau's pcall; nothing puts catch_unwind on that path. A panic inside a native call aborts. Host-level raising calls run under lua_pcall.
  • Stack is exclusive and never pops; temporaries live in a Frame; one open child frame per scope; views cannot escape the frame that owns their slot.
  • Receivers are &T, never &mut T, because one userdata can appear in several argument slots of one call. Mutation goes through interior mutability.
  • Bound callables are Fn, not FnMut: a binding can re-enter itself through Lua.
  • Every unsafe block states the invariant it relies on. GC destructors never call the Lua API.
  • Tags are 1..254; tag 0 is Luau's untagged default. Fast flags follow OpenMW's policy (plus LuauExperimentalIfLocalSyntax) and are frozen before the first VM or compile.
  • Luau integers (42i) compare with == only and never equal a number, so identities (peers, events, handles, packed scalars) are integers and everything a script counts or thresholds is a plain number.

Building

Luau git submodule luau/, release 0.740 (the commit OpenMW pins); git clone --recurse-submodules
C++ side compiled by build.rs with cc: LUAI_MAXCSTACK=8000, three-component vectors, LUA_UTAG_LIMIT=254, -fno-math-errno, no -march
Toolchain clang++, lld, cross-language thin LTO, clang and rustc on the same LLVM major; enforced by build.rs
Apple targets clang and lld without -Clinker-plugin-lto (ld64.lld rejects it); macOS forfeits the cross-language inlining
MSVC targets clang-cl compiles, rustc links with lld-link
Rust 1.92 or newer, edition 2024
Network at build time none

The value layout the binder reads is pinned by static_asserts in csrc/extra.cpp and proven against the API once per runtime, so a Luau bump that moves a byte fails at once. Every Luau API function in lua.h, lualib.h, luacode.h, luacodegen.h, luajitinliner.h and Require.h is declared in l3i::ffi, except the varargs lua_pushvfstring. TOOLCHAIN.md has the measurements behind the rule and Building the whole procedure.

Quality

cargo test, cargo test --all-features, cargo clippy --all-targets --all-features -- -W clippy::pedantic -D warnings and cargo fmt --check are clean on every commit. The integration tests link as one binary (cargo test <file>:: runs one file), since each binary pays a full link-time codegen under cross-language LTO. BENCHMARKS.md holds the Criterion numbers for the hot paths.

Wall time is noise past a few nanoseconds, so the binder's own cost is tracked in retired instructions per call from the CPU's counters (cargo bench --bench instructions), the loop subtracted, on the pinned Luau 0.740:

Per call Instructions Cycles
hand-written lua_CFunction (f64, f64) 277 69
bound (f64, f64) -> f64 369 89
typed direct namecall, the VM's leanest method path 368 98
planned method () -> f64 431 107
planned direct field 78 12

Every scenario reports fewer than 0.005 cache, TLB and branch misses per call. A bare VM's heap is 64 KiB after a full collection; the binder's own state is a 600-byte shared block plus 32 bytes per plan slot. Compatibility and performance has every table.

License

MIT OR Apache-2.0. Luau is Roblox's, under the MIT license; its notice ships in the package as luau/LICENSE.txt and luau/lua_LICENSE.txt.

About

DreamWeave's high performance Rust-based Luau binding framework.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages