1
Architecture
Jaroslav Beneš edited this page 2026-08-08 18:11:58 +02:00

Architecture

This document explains how Oxide is structured and the principles that govern how it is built. For per-system detail, see the topic documents (e.g. math.md).

Goals

Oxide is a general-purpose 3D game engine written in Rust, built to make any 3D game — scaling from stylized low-poly to realistic graphics — and to ship only what each game uses. It is built in two phases: a general-purpose engine (Phase 1, scene graph, render pass pipeline, input, UI, physics, scripting, animation, particles, shaders, audio, content kit, and game export) and a set of optional, feature-gated built-in modules (Phase 2: ray-traced sound, developer console, procedural toolkit, terrain, open world, pathfinding/AI, water). The guiding idea is build tools, not games, all driven through a first-class in-engine editor. See PLAN.md for the staged roadmap.

Design philosophy

These principles are non-negotiable and shape every decision:

  1. Build in stages. Each stage produces a usable, standalone artifact and must be fully tested and stable before the next begins. See development.md and PLAN.md.
  2. Composability. Every system should be usable independently of the others. You should be able to pull in the math module, or the scene graph, without dragging in the renderer.
  3. Correctness and clarity over premature optimization. Optimize when a benchmark says to, not before.
  4. Minimal public surface. Internal complexity is fine; the external API should be small and clean. Modules expose a curated set of types through pub use, not their whole internal structure.
  5. No feature creep between stages. New ideas go to the backlog, not into the current stage.
  6. The editor grows with the engine. oxide-editor is a first-class deliverable, gaining panels and tools as each stage adds systems.
  7. Build tools, not games. Ship composable building blocks; genre-specific behavior belongs in game code or optional modules.
  8. Ship only what's used. Subsystems are feature-gated modules (from Stage 5); an exported game compiles in only the modules it registers.
  9. Scalable fidelity. A data-driven render pass pipeline lets a project run anything from a flat low-poly/stylized look to a full realistic stack, paying only for the passes it enables.
  10. Modules are the primary extension point. A module registers engine logic and editor UI and its own settings through one documented API; anyone — including AI agents — can write one.

Workspace layout

Oxide is a single Cargo workspace. Crates share version, edition, license, and dependency versions through [workspace.package] and [workspace.dependencies] in the root Cargo.toml.

Oxide/
├── engine/      # oxide-engine  — the core library (all engine systems)
├── editor/      # oxide-editor  — the in-engine editor binary
├── examples/    # oxide-examples — runnable examples, one+ per stage
├── tests/       # oxide-tests   — integration / end-to-end test harness
├── docs/        # this documentation
├── assets/      # logos and shared assets
├── install.sh   # release build + system install
├── PLAN.md      # authoritative staged roadmap
├── README.md    # short project overview
└── CLAUDE.md    # rules and context for AI-assisted development

Crate responsibilities

  • oxide-engine — the library that contains every engine system. It is organized as one module per system (math, and later scene, render, physics, …). Each module is independently usable and re-exports its public types. A prelude module collects the most common imports.
  • oxide-editor — the binary users run. It depends on oxide-engine and builds a UI on top of engine systems (its own window with a placeholder viewport since Stage 2; egui panels from Stage 3). It never contains engine logic itself; it is a consumer of the engine.
  • oxide-examples — small, focused programs that each demonstrate one stage's capabilities. They double as manual-review artifacts and as living documentation. publish = false; they are never installed.
  • oxide-tests — integration tests that exercise the engine the way a real consumer would, including cross-module scenarios and fuzz/property tests.

Engine module structure

Inside oxide-engine, each system is a module under engine/src/. The pattern, established by the math module, is:

engine/src/
├── lib.rs              # declares modules, defines the prelude
├── math/
│   ├── mod.rs          # module docs + curated `pub use` re-exports
│   ├── transform.rs    # one type/concept per file, with its own tests
│   ├── aabb.rs
│   └── ...
├── render/             # Stage 2: GPU acquisition + surface clear loop
│   ├── mod.rs          # RenderError, clear_view, re-exports
│   ├── gpu.rs          # Gpu (instance/adapter/device/queue)
│   └── context.rs      # RenderContext (surface, resize, render_frame)
└── window/             # Stage 2: window + event loop + App trait
    ├── mod.rs          # WindowConfig, `event` re-export module
    ├── app.rs          # App trait, AppCtx
    └── runner.rs       # winit ApplicationHandler internals

Rules of thumb:

  • One concept per file. Prefer many small focused files over a few large ones.
  • Tests live with the code. Each file has a #[cfg(test)] mod tests block covering core behavior and edge cases.
  • The module root curates the API. mod.rs decides what is public via pub use; submodules are private (mod foo;, not pub mod foo;) unless there is a reason to expose the path.
  • The prelude is the front door. oxide_engine::prelude::* brings in the types a typical consumer needs, including re-exported third-party math types so downstream code needs only one dependency for everyday work.

Key dependencies

Chosen for portability and a lightweight footprint:

Concern Crate Notes
Math glam SIMD-friendly vectors/quats/matrices; serde feature enabled
Graphics wgpu Portable across Vulkan/Metal/DX12 (since Stage 2; re-exported as oxide_engine::wgpu)
Windowing winit Cross-platform windows and events (since Stage 2; re-exported as oxide_engine::winit)
ECS hecs Lightweight archetypal ECS (Stage 3+)
Physics rapier3d Rigid bodies and collision (Stage 6+)
Editor UI egui Immediate-mode UI (Stage 3+)
Logging log + env_logger Facade + env-driven backend
Errors anyhow + thiserror Application vs. library error handling
Serialization serde + ron Scene/asset (de)serialization (Stage 3+)

Error handling and logging

  • Libraries (oxide-engine) define their own error types with thiserror so callers can match on failure modes.
  • Binaries (oxide-editor, examples) use anyhow for ergonomic error propagation at the top level.
  • Logging uses the log facade throughout the engine; binaries initialize a backend (env_logger). Control verbosity with RUST_LOG, e.g. RUST_LOG=oxide_engine=debug cargo run -p oxide-examples --bin math_demo.

Installability

Oxide must remain installable as a Linux package at all times. install.sh builds in release mode and installs the oxide-editor binary and assets under a prefix (/usr/local by default, overridable with PREFIX). Any new installed binary or asset must be reflected in install.sh in the same change.

See also