Introduction

Mid Engine is a modular, high-speed Rust engine toolkit — "The Middle Man."

Traditional monolithic engines hide their internals behind layers of abstraction, while others demand unlimited hardware just to render a basic scene. Mid Engine takes a different approach: a modular toolkit for developers who want complete control over their hardware stack, with every crate designed to be dropped into a larger project rather than forcing an all-or-nothing commitment.

It's a true "Middle Man" in two senses. First, every crate is FFI-compatible out of the box, built with as few external dependencies as possible, so it can sit between a game's logic and a lower-level system (or a completely different language's codebase) without friction. Second, it's meant to be the layer between "roll everything yourself" and "adopt a full engine" — whether that means assembling a sovereign engine from these crates, or injecting individual modules into an existing C++/C# environment through raw pointers.

Where this book fits

This is the user-facing documentation site — architecture, how to use each crate, and the project's own conventions. It's a different thing from the docs/*.md files in the repository itself, which are internal, AI-facing working notes (design decisions, fix histories, MSRV walls) meant for whoever — human or AI — is actively developing the engine, not for someone using it. If something here seems to assume familiarity a repo-only reader wouldn't have, that's the split working as intended, not a mistake.

See Tests and Benchmarks for live results from each crate's own CI, and the Getting Started page to build the workspace yourself.

Getting Started

Mid Engine is a Cargo workspace. Most crates build on a plain, current stable toolchain, but a handful need a newer Rust than the workspace's usual floor — see Crate Dependency Order and each crate's own page for specifics before assuming a bare, unqualified command below will cover everything.

Build and test

# Everything a bare-minimum, no-flags toolchain can build:
cargo build
cargo test

# A release build:
cargo test --release

# One crate specifically -- always safe, regardless of any MSRV wall
# elsewhere in the workspace:
cargo build -p mid-math
cargo test  -p mid-math

# The headless server example:
cargo run --example headless-server

A note on -p

Several crates deliberately need a newer toolchain than the rest of the workspace (an upstream dependency's own edition2024 requirement, mostly — see each crate's page for the specific wall). A bare cargo build/cargo test with no -p flag pulls every workspace member in, including those. Building a single crate with -p <crate-name> only resolves that crate's own dependency closure, so it stays on whatever toolchain that crate actually needs — usually the workspace floor.

CI

Every crate's tests run through its own workflow_dispatch-only GitHub Actions workflow — nothing runs automatically on push. Trigger one from the repository's Actions tab, then check the Tests page (or the run's own Job Summary) for results. See Contributing & CI for the full convention.

Design Mandates

Four rules apply to every crate in this workspace, no exceptions:

Multiplayer-first

Network sync is baked into the ECS from day one, rather than being bolted on later. Multiplayer isn't a feature added after the fact — it shapes how state is structured from the start.

FFI-ready

Every crate exposes a strict #[repr(C)] FFI boundary to act as a cross-language "Middle Man." In practice: rlib + cdylib + staticlib crate types, and a real extern "C" surface with a C header, not just a Rust API that happens to be #[repr(C)]-annotated internally.

Zero hidden abstractions

If you need to understand the memory layout, you can read the code directly. No macro-generated indirection that obscures what's actually stored where.

Profile before optimize

Every performance claim cites a real, benchmarked [RELEASE] build number — not a theoretical estimate. See Benchmarks for the numbers behind any performance claim made elsewhere in this book.

Zero-to-minimal external dependencies

Every core crate, no exceptions. This project has turned down its own published dixscript crate as a core dependency over its transitive dependency count — the bar is real, not aspirational. When a crate needs a capability an external crate provides (a fast hash algorithm, a spin-based mutex for no_std), the default is to check whether it can be hand-rolled first, and to defer the decision with a named trigger condition when it can't be decided cleanly yet. mid-platform's own page is the clearest example of this in practice.

Crate Dependency Order

mid-math        (no engine deps — pure math foundation)
mid-ptr         (no engine deps — type-erased pointer wrappers)
mid-platform    (no engine deps — Mutex/Arc/atomics/cells)
mid-collections (no engine deps — SparseSet, generational index, FFI span)
mid-arena       (no engine deps — bump/slot/compact arenas)
mid-alloc       (no engine deps — custom allocators, SpinLock)
mid-common      (uses mid-math — shared traits and error types)
mid-log         (uses mid-common)
mid-trace       (uses mid-common)
mid-geom        (uses mid-math — geometric algorithms)
mid-ecs         (uses mid-math, mid-common, mid-collections)
mid-net         (uses mid-math, mid-common)
mid-physics     (uses mid-math, mid-geom)
mid-anim        (uses mid-math, mid-ecs)

The crates with no engine-internal dependency at all (mid-math, mid-ptr, mid-platform, mid-collections, mid-arena, mid-alloc) are the ones safest to work on in parallel — nothing else in the workspace has to land first for one of them to be usable, and nothing about them can be blocked by another crate's own unfinished state.

See each crate's own page for what it actually depends on today versus this list's target shape — a couple of these (mid-physics, mid-anim, mid-app, mid-time) currently exist as early v0 stubs rather than finished crates.

mid-math

SIMD-dispatched vector and matrix math library: Vec2/Vec3/Vec4, Quat, Mat3/Mat4, strictly 16-byte-aligned #[repr(C)] primitives for FFI safety. Includes a hand-rolled MidVec<T, N> small-vector container (union + MaybeUninit) used by curve types and cascaded shadow maps.

Zero external dependencies, comprehensive benchmarking infrastructure (see Benchmarks). The second crate in the workspace (after mid-math itself, chronologically first) to opt into [lints] workspace = true and take on real unsafe for its SIMD intrinsics.

Status: practically done, second optimization pass planned.

mid-common

Shared types and traits used across the workspace — EntityId, TickId, network-sync-related types. Depends on mid-math.

Kept deliberately thin: helper crates (mid-app, mid-time, and others) live as their own workspace members rather than being folded in here, so this crate doesn't accumulate every cross-cutting concern by default.

Status: in progress.

mid-log

Non-blocking tiered logger built on a lock-free SPSC ring buffer — zero frame-time impact on the hot path. Ships a C header for FFI callers (headers/mid_log.h) and has its own C-side smoke test.

Status: done, needs a second optimization pass.

mid-collections

no_std + alloc collection types: a generational-index allocator, a sparse set, and FfiSpan — a type-erased, C-safe view into a Rust array for crossing the FFI boundary safely (built on zerocopy).

FfiSpan specifically is why mid-platform's own Phase 1 doesn't need to solve the "type-erased pointer for FFI" problem again — see that crate's own page and docs/roadmap.md's Decision 3 for the full reasoning.

Status: in progress.

mid-arena

Arena allocators: bump, slot, and compact-slot variants, each benchmarked against comparable crates. No engine-internal dependencies.

Status: in progress — next up for updates and proper benches per the project roadmap.

mid-alloc

Custom memory allocators and layout management, including SpinLock<T> — a hand-rolled spinlock validated under real multi-threaded stress tests (8 real OS threads, thousands of increments each, zero lost updates). That same proven algorithm is the basis for mid-platform::sync::Mutex's own no_std fallback (an independent copy, not a dependency — see that crate's page for why).

Status: in progress; paused pending mid-arena updates.

mid-ptr

Type-erased raw pointer wrappers, ported from Bevy's bevy_ptr crate (MIT/Apache-2.0) and adapted to this workspace's own conventions. no_std, zero dependencies.

What it gives you

  • Ptr, PtrMut, OwningPtr — type-erased pointers mirroring &T, &mut T, and Box<T> respectively, minus the compile-time type information. Useful for storing heterogeneous data (different component types in one column, for instance) without generics at the storage layer.
  • MovingPtr — moves a value to a new location without ever passing it by value, and can be deconstructed into per-field MovingPtrs via the deconstruct_moving_ptr! macro. Useful for migrating a value's bytes between two locations (an archetype table column, say) without an extra copy.
  • ThinSlicePtr — a &[T] with the length stripped out, for callers that already track the length separately.
  • Aligned/Unaligned — marker types threaded through all of the above, so a pointer's alignment guarantee (or lack of one) is visible in its type rather than only in a doc comment.

Full port, including MovingPtr

This is a close port of the real, current upstream source, not a redesign — including the newer MovingPtr + field-deconstruction machinery, which is genuinely the more novel, riskier-to-hand-verify half of the crate. Built on direct instruction, after surfacing that the project's own roadmap had originally deferred this crate.

FFI

Not yet exposed over a C boundary — a known, tracked gap against this project's own FFI-ready mandate, not an oversight left undocumented. See the repository's docs/mid-ptr.md for the current status.

Status

See the Tests page for the latest CI run, or run mid-ptr — Tests from the Actions tab yourself.

mid-platform

Platform-agnostic primitives, bevy_platform-shaped but hand-rolled and dependency-free — unlike mid-ptr, this is not a verbatim port. Real bevy_platform pulls in spin, portable-atomic, foldhash, hashbrown, and critical-section; none of mid-engine's real targets (native desktop, wasm32-unknown-unknown) actually need what most of those exist for.

Phase 1 (built)

  • sync::atomic — a plain re-export of core::sync::atomic. Upstream's portable-atomic fallback only activates on targets lacking a native atomic width; none of this project's targets do.
  • cell::{SyncCell, SyncUnsafeCell} — pure logic, no locking involved.
  • sync::{Mutex, MutexGuard} — a thin std::sync::Mutex pass-through when the std feature (on by default) is enabled, and a self-contained spin-based fallback otherwise — the same algorithm mid-alloc::SpinLock already validated under real multi-threaded stress tests, kept as an independent copy rather than a dependency so this crate stays workspace-dependency-free.
  • sync::{Arc, Weak} — a plain alloc::sync re-export.

Phase 2 (not built yet)

RwLock, Once/OnceLock, LazyLock, and Barrier — each needs genuinely new spin-based concurrency design, not just a port of already-proven logic, so each deserves its own careful pass rather than being rushed in alongside Phase 1.

Phase 3 (not decided)

A fast hasher (hand-rollable, deserves a benchmarked pass of its own) and a HashMap/HashSet (the hard one — reimplementing a real hash table is a project-sized decision on its own, not resolved yet).

Testing both configurations

This crate has real Cargo features (std, default-on; alloc), not just an unconditional no_std. CI runs it twice: default features exercise the std passthrough, --no-default-features exercises the actual hand-rolled fallback — a genuinely different code path default features would never even compile.

FFI

Not yet exposed over a C boundary — same tracked gap as mid-ptr.

Status

See the Tests page, or run mid-platform — Tests from the Actions tab yourself.

mid-net

Reliable UDP transport with a hand-rolled wire codec, targeting a 128 Hz tick rate, plus a DixScript-based transport layer (native via quinn, and a wasm32 browser variant). Awaiting ECS integration.

Status: in progress.

mid-ecs

Data-oriented Entity Component System using a hybrid Archetype-core / Sparse-set-shell layout, built on mid-collections. Benchmarked directly against bevy_ecs (see Benchmarks).

Status: started — archetype core and sparse shell both underway.

mid-geom

Geometry algorithms: BVH construction, Delaunay triangulation, convex hull, mesh operations. Depends on mid-math.

Status: planned.

mid-trace

Distributed tracing. Depends on mid-common.

Status: planned.

Performance Targets

SystemFrequencyBudgetStatus
Network tick (mid-net)128 Hz7.8 ms / tickNot started
Physics (mid-ecs)60 Hz16.6 ms / tickNot started
Max entities (mid-ecs)100,000+ per coreNot started
Log hot path (mid-log)0 µszero frame impactImplemented
Math primitives (mid-math)SSE216-byte aligned Vec3/4/QuatIn progress

Per the Design Mandates, every claim here should eventually link to a real benchmark build number on the Benchmarks page rather than stand as an unverified target. Until a row does, treat it as a target, not a measured result.

Contributing & CI

Manual CI only

Every test and benchmark workflow in this repository is workflow_dispatch only. Nothing runs automatically on push, pull request, or a schedule — tests and benchmarks take real time and shouldn't run on every commit. Run a workflow from the repository's Actions tab when you actually want its results.

Each workflow writes a real, structured summary to its own run's Job Summary (parsed from the raw test/bench output, not a raw log dump), and uploads the raw log plus a JSON results file as a build artifact.

Publishing the site

This site itself deploys the same way: Deploy Site (.github/workflows/deploy-site.yml) is also workflow_dispatch only. It rebuilds this book, gathers the latest JSON results each crate's own test workflow produced, and deploys the whole thing to Cloudflare Pages. Running a crate's test workflow updates that crate's own results; running Deploy Site afterward is what actually publishes them here.

Documentation conventions

  • Every source file starts with a NOTICE header comment pointing to its crate's own docs/<crate-name>.md file and section there. Inline code comments describe what/how only — no fix history or decision logs inline.
  • Each crate's docs/<crate-name>.md (in the repository's top-level docs/, not nested per crate) holds per-file sections, a CI/workflow reference section, and a bottom "Fixes and Problems" section.
  • This book (web/site/) is the separate, user-facing counterpart to those internal docs — see Introduction for the split.