Architecture Overview
Nightshade is organized as a layered dependency graph (DAG). Each layer builds on the one below it, and no layer references anything above it. This chapter shows how the pieces fit together.
Feature Layer: Terrain, Particles, NavMesh, Grass, SDF, Lattice, Scripting
|
Application Layer: State Trait, Main Loop, Event Bus
|
Rendering Layer: Render Graph -> Passes -> Materials -> Textures -> Shaders
|
Simulation Layer: Physics (Rapier), Animation, Audio (Kira)
|
Core Layer: World, Transform Hierarchy, Input, Time, Windowing
|
Foundation Layer: ECS (freecs), Math (nalgebra_glm), GPU (wgpu)
Foundation Layer
The lowest layer contains three independent systems that everything else depends on.
ECS (freecs) provides compile-time code-generated entity storage with struct-of-arrays layout. The ecs! macro generates the World struct, component accessors, query methods, and entity management. Zero unsafe code, all statically dispatched.
Math (nalgebra_glm) provides vectors (Vec2, Vec3, Vec4), matrices (Mat4), quaternions (Quat), and all standard linear algebra operations. Nightshade uses nalgebra_glm exclusively for all math.
GPU (wgpu) provides cross-platform GPU access. wgpu targets Vulkan, Metal, DirectX 12, and WebGPU from a single API surface.
Core Layer
Built on the foundation, the core layer manages per-frame state and the entity hierarchy.
World is the central data container generated by the freecs::ecs! macro. It holds all entity storage and a Resources struct containing global singletons (timing, input, graphics settings, caches, physics world, audio engine, etc.).
Transform Hierarchy propagates LocalTransform through parent-child relationships to compute GlobalTransform matrices each frame. Dirty-flag tracking ensures only modified subtrees are recomputed.
Input aggregates keyboard, mouse, and gamepad state each frame into world.resources.input. Provides both polling (is_key_pressed) and event-driven (on_keyboard_input) access patterns.
Time is accessed through world.resources.window.timing and provides delta_time, frames_per_second, uptime_milliseconds, frame_counter, and raw/speed-adjusted variants.
Windowing wraps winit for window creation, event handling, and surface management. On native platforms, secondary windows are supported via SecondaryWindows.
Simulation Layer
Systems that update world state each frame, independent of rendering.
Physics (Rapier3D) runs at a fixed 60Hz timestep with interpolation for smooth rendering. Provides rigid bodies, colliders, character controllers, joints, and raycasting. Gated behind the physics feature flag.
Animation plays back skeletal animations loaded from glTF files. Supports blending, crossfading, speed control, and looping. Bone transforms are written directly into the ECS each frame.
Audio (Kira) handles sound playback with spatial positioning, distance attenuation, and FFT analysis. Gated behind the audio feature flag.
Rendering Layer
Transforms ECS data into pixels on screen.
Render Graph is a dependency-driven frame graph built on petgraph. Passes declare which resources they read and write via named slots. The graph automatically builds dependency edges, topologically sorts passes, computes resource lifetimes, aliases transient GPU memory, and determines optimal load/store operations.
Passes implement the PassNode trait. Each pass owns its GPU pipelines and bind group layouts. Built-in passes cover shadow mapping, PBR mesh rendering, skeletal animation, water, grass, particles, text, UI, and post-processing (SSAO, SSGI, SSR, bloom, depth of field, tonemapping, effects).
Materials use a PBR metallic-roughness workflow stored in a MaterialRegistry. Materials reference textures by name and are assigned to entities via MaterialRef.
Textures are managed by a TextureCache that handles GPU upload, format conversion, and atlas packing (for sprites). Textures are loaded via WorldCommand::LoadTexture.
Shaders are written in WGSL and embedded at compile time via include_str!.
Application Layer
The interface between engine and game code.
State Trait is implemented by your game struct. Its methods (initialize, run_systems, ui, configure_render_graph, etc.) are called by the engine at specific points in the frame lifecycle.
Main Loop drives the frame lifecycle: process events, update input, call run_systems, dispatch events, run the frame schedule (engine systems), render, present.
Frame Schedule is a data-driven ordered list of engine system functions stored as a resource (world.resources.frame_schedule). It dispatches audio, camera, physics, scripting, animation, transform propagation, and cleanup systems each frame. Users can insert, remove, or reorder systems in State::initialize().
Event Bus provides decoupled communication via world.resources.event_bus. Supports typed app events and input messages.
Feature Layer
High-level gameplay systems built on everything below.
| Feature | Dependencies |
|---|---|
| Terrain | Rendering (mesh generation, tessellation), Physics (heightfield collider) |
| Particles | Rendering (GPU billboard pass), ECS (emitter component) |
| NavMesh | Physics (geometry), ECS (agent component), Core (transforms) |
| Grass | Rendering (instanced GPU pass), ECS (region component) |
| SDF Sculpting | Rendering (compute + raymarching), ECS (SDF world resource) |
| Lattice Deformation | Core (transforms), ECS (control points) |
| Scripting | WASM plugin runtime, ECS (component access API) |
Data Flow
A typical frame flows data through the layers like this:
Input Events (winit)
|
v
Input State (world.resources.input)
|
v
Game Logic (State::run_systems)
|
v
ECS Mutations (spawn, despawn, set components)
|
v
FrameSchedule Dispatch:
|-- Audio initialization and update
|-- Camera aspect ratios
|-- Physics Step (Rapier simulation, sync back to ECS)
|-- Script execution
|-- Animation (bone transforms written to ECS)
|-- Transform Propagation (LocalTransform -> GlobalTransform)
|-- Instanced mesh caches, text sync
|-- Input reset, deferred commands, cleanup
|
v
Render Graph Execution
|-- Shadow Depth Pass (reads GlobalTransform, Light)
|-- Geometry Passes (reads GlobalTransform, RenderMesh, MaterialRef)
|-- Post-Processing (reads scene_color, depth, normals)
|-- UI Pass (reads egui UI state)
|-- Blit Pass (writes to swapchain)
|
v
Present (wgpu surface present)
Feature Flags
Most subsystems are opt-in via Cargo feature flags:
| Flag | What it enables |
|---|---|
engine | Core rendering, ECS, transforms, input |
physics | Rapier3D physics simulation |
audio | Kira audio playback |
egui | egui immediate-mode UI |
gamepad | Gamepad input via gilrs |
assets | Image/HDR loading via the image crate |
openxr | OpenXR VR support |
steam | Steamworks integration |
scripting | WASM plugin system |
sdf_sculpt | SDF sculpting tools |
scene_graph | Scene serialization |