12 KiB
Structure
Modules
src/entity.rs
Manages entity lifecycle with EntityHandle (u64 alias) and EntityManager tracking alive entities. Entities are opaque IDs; all data lives in component storages on World.
src/world.rs
Core ECS container holding EntityManager, 20+ typed Storage<T> collections (one per component type), and intent queues (follow_player_intents, stop_following_intents, camera_transition_intents). One-frame intents are inserted and consumed within a single frame. World also holds singleton state: snow_layer, debug_mode, gizmo_mesh.
src/components/
Component types define entity data. Key storages:
- Transforms (
Transform): position, rotation, scale - Physics (
PhysicsComponent): rapier3d rigidbody/collider handles - Movement (
MovementComponent): walking speed, acceleration, damping state - Jump (
JumpComponent): jump height, air control, jump curve - State machines (stored per-entity):
IdleState,WalkingState,JumpingState,FallingState,LeapingState,RollingState - Input (
InputComponent): current move direction, jump/parry key states - Camera (
CameraComponent): FOV, aspect, yaw/pitch;CameraTransitionfor animated transitions - Dialog (
DialogBubbleComponent,DialogProjectileComponent,DialogSourceComponent): Ink story state, projectile tracking, outcome events - Rendering (
MeshComponent): mesh reference, pipeline, instance buffer, dissolve/snow-light flags - Misc:
FollowComponent,RotateComponent,DissolveComponent,TriggerComponent,ParticleEmitterConfig
src/states/state.rs
Per-entity state machine: StateMachine holds current TypeId, registered state types, and transitions. State trait defines lifecycle (on_enter, on_physics_update, on_exit). Transitions are condition predicates checked each update. Player entity uses this for locomotion states.
src/systems/
Flat list of update functions called in main loop. No cross-system coupling; all communication via world state and intents.
- Camera:
camera_input_system→ generates intents;camera_intent_systemconsumes them;camera_follow_system(follows player),camera_noclip_system,camera_transition_system,camera_ground_clamp_system - Input:
player_input_systemreads SDL3InputState, writesInputComponent - Physics:
state_machine_physics_system(fixed-step),PhysicsManager::physics_step(),physics_sync_system(copies rapier bodies back to transforms),trigger_system(AABB overlap → events) - Dialog:
dialog_system(ticks story state),dialog_projectile_system(moves projectiles),dialog_camera_system(focuses camera on speaker),dialog_bubble_render_system(generates billboard/text draw calls) - Rendering:
render_systemcollectsDrawCallfrom meshes/transforms;spotlight_sync_systemsyncs light positions to shader uniform - State machine:
state_machine_system(per-frame),state_machine_physics_system(fixed-step) tick state lifecycle - Trees:
tree_occlusion_system(culls trees behind camera),tree_dissolve_update_system(animates dissolve),tree_instance_buffer_update_system(writes GPU buffer) - Snow:
snow_systemdeforms snow layer at physics contacts;particle_intent_system/particle_update_systemmanage particle emitters - Rotate:
rotate_systemrotates entities by delta
src/bundles/
Factory functions to spawn pre-configured entity groups:
PlayerBundle: player character with all locomotion componentsTestCharBundle: test NPCCameraBundle: camera entityTerrainBundle: terrain mesh + colliderSpotlightBundle+spawn_spotlights(): light entities from scene data
src/render/
GPU rendering pipeline via wgpu. Singleton Renderer stored in thread-local (global.rs).
Renderer: wgpu device/queue/surface, framebuffer, pipelines, bind groups, shadow map texture, spotlight dataDrawCall(types.rs): vertex/index buffers, model matrix, pipeline enum, instance buffer, entity ref- Pipelines (pipeline.rs):
create_main_pipeline(),create_snow_clipmap_pipeline(),create_wireframe_pipeline(),create_shadow_pipeline(),create_debug_lines_pipeline() Uniforms(types.rs): model/view/projection, 4 spotlights, camera position, height scale, player position, time, tile scale, debug mode- Shadow:
render_shadow_pass()renders scene depth to shadow map from each spotlight - Snow:
SnowLayerdeforms snow heightfield via compute;ClipmapConfigmanages multi-level clipmap grid;deform_at_position()marks terrain changed - Snow light:
SnowLightAccumulationping-pong texture accumulates light contributions from spotlights onto snow surface - Billboard pipeline + Text pipeline: render dialog bubbles and text overlays
- Particle pipeline: billboarded particles with per-instance color/velocity
- Font atlas: pre-rasterized glyph texture from embedded font file
src/loaders/
Load scene data from glTF files:
scene.rsSpace::load_space(): loads meshes, lights, player spawn, test char spawn from single glTFmesh.rsMesh::load_gltf_with_instances(): parses glTF buffers, vertex/index data, multi-instance mesh batches;InstanceData(position/rotation/scale/dissolve);Vertex(position/normal/UV)lights.rs: extracts spotlight transforms/params from glTF nodesempty.rs: extracts named empty (spawn point) transforms from glTFheightmap.rs: loads EXR heightfield texture for terrain collisionterrain.rs: builds rapier heightfield collider from EXR matrix
src/physics.rs
Thread-local PhysicsManager wrapping rapier3d. physics_step() runs one integration step; add_rigidbody(), add_collider() register bodies; raycast() queries. HeightfieldData caches terrain height matrix.
src/utility/
transform.rsTransform: matrix conversions to/from nalgebraIsometry3; getters/setters for position/rotation/scaleinput.rsInputState: SDL3 key states (WASD, Space, Shift, Ctrl) and mouse delta;handle_event()updates state;clear_just_pressed()resets one-frame flagstime.rs:Time::get_time_elapsed()returns seconds since init (static Instant)
src/debug/
mode.rsDebugModeenum: None, Normals, UV, Depth, Wireframe, Colliders, ShadowMap, SnowLight;cycle()steps throughcollider_debug.rs: renders rapier collider AABBs as line meshesgizmo.rs: renders 3D transform gizmo (position/rotation/scale) for editor
src/editor/
inspector.rsInspector: wraps Dear ImGui context;render()draws frame to texture;build_ui()draws entity inspector panelsmod.rsEditorState: manages editor active state, selected entity, mouse capture;editor_loop()calls inspector, handles picking
src/picking.rs, src/postprocess.rs, src/texture.rs, src/paths.rs
Utility modules: ray casting for mouse pick, fullscreen blit/framebuffer downsampling, dither/flowmap texture loading, paths to asset files.
Data Flow
- Initialization (
main.rsinit()): SDL3 window → wgpu renderer (Vulkan) → World creation → load scene (Space from glTF) → spawn bundles (player, terrain, camera, lights) → initialize physics - Main loop (
main.rsmain()with 60 Hz fixed physics + variable-rate graphics):- Per-frame (delta): Input events →
player_input_system(fillsInputComponent) +camera_input_system(generates intents) - Intent processing:
camera_intent_systemconsumes intents, updatesCameraComponent/CameraTransition - Camera systems: follow player, noclip, transition, clamp to ground
- Fixed physics (1/60s accumulator):
state_machine_physics_system: tick state'son_physics_update()PhysicsManager::physics_step(): rapier integrationphysics_sync_system: copy rigidbody poses toTransformtrigger_system: detect collisions, emit eventsdialog_system,dialog_projectile_system: Ink story tick, projectile movement
- Per-frame systems:
state_machine_system(non-physics update), rotate, particle, tree dissolve, snow deformation, spotlight sync - Render collection:
render_system→Vec<DrawCall>from all meshes; snow layer adds clipmap draw calls; debug adds collider/gizmo calls - Submission:
submit_frame()renders draw calls, dialog bubbles/text, particles to framebuffer; blit to screen; optional ImGui overlay
- Per-frame (delta): Input events →
- Frame cleanup:
InputState::clear_just_pressed()(flags reset for next frame)
Key Types
| Type | Module | Description |
|---|---|---|
EntityHandle |
entity | u64 opaque entity ID |
Storage<T> |
world | HashMap storage for per-entity component data |
World |
world | ECS container: entities, 20+ storages, intent queues, singleton state |
Transform |
utility/transform | Position (Vec3), rotation (Quat), scale (Vec3); matrix conversions |
StateMachine |
states/state | Per-entity state machine: current state TypeId, registered states, transitions |
State trait |
states/state | Lifecycle: on_enter, on_physics_update, on_exit, on_update |
PhysicsComponent |
components/physics | Rapier3d rigidbody + optional collider handles |
MovementComponent |
components/movement | Walking speed, acceleration, damping, context (floored, last floor time) |
JumpComponent |
components/jump | Jump height, duration, air control, context (in progress, origin height) |
CameraComponent |
components/camera | FOV, aspect, yaw/pitch angles, is_active flag |
InputComponent |
components/input | Current frame: move direction, jump/parry key states (flags) |
MeshComponent |
components/mesh | Mesh ref, pipeline enum, instance buffer, dissolve/snow-light flags |
DialogBubbleComponent |
components/dialog | Ink story, current text, dialog phase (displaying/projectile in flight), parry button |
DrawCall |
render/types | GPU command: vertex/index buffers, model matrix, pipeline, instance count, entity ID |
Uniforms |
render/types | Per-frame shader data: matrices, spotlight array, debug flags |
Renderer |
render/mod | GPU state: device, queue, surface, all pipelines, framebuffer, texture samplers |
Space |
loaders/scene | Loaded scene: mesh batches, spotlight data, spawn positions |
Mesh |
loaders/mesh | GPU vertex/index buffers, AABB, CPU vertex data for physics |
SnowLayer |
render/snow | Snow heightfield: deform bind groups, depth texture, clipmap grid levels |
SnowLightAccumulation |
render/snow_light | Ping-pong textures + pipeline accumulating spotlight contributions onto snow |
InputState |
utility/input | SDL3 event state: key flags, mouse delta, relative mode |
PhysicsManager |
physics | Rapier3d bodies/colliders/pipeline; thread-local singleton |
DebugMode |
debug/mode | Enum: None, Normals, UV, Depth, Wireframe, Colliders, ShadowMap, SnowLight |
Entry Points
main()in src/main.rs- Calls
init()→ initializes SDL3, wgpu, loads world from scene glTF, spawns entities - Runs infinite loop: event poll → systems (camera/input/physics/render) → frame submit → sleep to 60 Hz
- Calls
Game::init()in src/main.rs- SDL3 window + Vulkan surface
Renderer::new()initializes all GPU pipelines and texturesinit_world()loads space, spawns bundles, sets up terrain/snow/lights
World::new()in src/world.rs- Creates empty storages for all 20+ component types and intent queues
Dependencies
- Engine: wgpu (GPU), rapier3d (physics), glam (math), nalgebra (nalgebra for physics conversions), SDL3 (windowing/input)
- Content: bladeink (Ink story scripting), image crate (EXR heightmaps), gltf (scene loading)
- Editor: Dear ImGui via imgui crate + SDL3 integration
- Internal: All systems read/write
World; systems are called in fixed sequence from main loop; no circular dependencies - Thread-local singletons:
Renderer(render/global.rs),PhysicsManager(physics.rs),GLOBAL_RENDERER,GLOBAL_PHYSICS
Initialization Order
- SDL3 init → window creation → Vulkan adapter/device
Renderer::new()→ wgpu device/queue, create all pipelines, load textures (dither, flowmap, font atlas, shadow map, blue noise)- Load scene from glTF → meshes, lights, spawns
- Spawn bundles: player (with input/movement/jump/state machine), terrain (mesh + heightfield collider), camera (follows player), lights (spotlights)
- Initialize snow layer (deform by tree positions)
- Initialize snow light accumulation (bind to spotlight data)
- Main loop: process events → run systems in sequence → submit frame