Expand description
Runtime: frame scheduler, event system, subsystem manager; ties every other crate into the main loop.
SubsystemManager is the one place in the workspace allowed to know
about every *-core at once (see docs/dependency-rules.md rule 7) — it
owns real instances of the driver-independent subsystems that exist
today: an ecs-core World, physics-core’s body list and
pipeline, and audio-core’s listener/mixer. graphics-core isn’t
wired into Runtime::tick yet: graphics-driver has a real
windowed wgpu device now (see docs/roadmap.md’s winit/windowing
entry), but graphics-core itself has no scene/material vocabulary or
GPU-submission bridge yet to turn its RenderGraph into actual draw
calls, so there’s no frame in the render sense for Runtime::tick to
schedule. This crate deliberately doesn’t depend on graphics-driver
directly (see docs/dependency-rules.md: engine-core depends on
graphics-core, not drivers) — a windowed app composes
platform_core::run_windowed_app
with its own graphics-driver::Device/Surface and reuses
Runtime::tick’s Time for animation/physics timing (see the
spinning_cube example), rather than Runtime gaining rendering
awareness before graphics-core has anything to submit. Real audio
output follows the same composition pattern: the app owns an
audio-core::AudioOutput (the core→own-driver bridge over
audio-driver) and feeds it Mixer::render_interleaved blocks each
tick — see the audible_scene example; Runtime itself stays
driver-free on the audio side too.
Runtime::tick advances physics, then recomputes audio gains from
the physics-updated emitter frames, in that order — not through
FrameScheduler/task-core’s JobGraph, deliberately: physics and
audio are the only two real per-frame systems today, and they have a
genuine sequential data dependency (audio reads positions physics just
wrote), not two independent branches. Wrapping a strictly sequential
two-step in a job graph would be decorative, not functional — the same
reason compute-runtime’s task-core dependency isn’t wired in yet
(see that crate’s module doc). FrameScheduler is real and tested on
its own terms; it becomes load-bearing once a second real per-frame
system exists that’s genuinely independent of physics (animation,
particles, …) to run alongside it.
Structs§
- Event
System - Workspace-wide event bus: a frame-scoped mailbox, not a persistent log.
publishqueues an event by its concrete type;drainremoves and returns every event of that type published since the last drain. This is what lets subsystems communicate without depending on each other directly — e.g. a futurephysics-corecontact could be published here and consumed byaudio-corefor an impact sound, without either crate knowing the other exists (see docs/dependency-rules.md rule 7: onlyengine-coreis allowed to know about both). - Frame
Completed - Published by
Runtime::tickafter every frame — the one concrete event type this crate defines itself; application code can publish its own event types through the sameEventSystem. - Frame
Scheduler - Runs one frame’s
JobGraphacross worker threads — the engine-layer application oftask-core’s generic scheduler (see docs/threading-model.md). Sized byFrameScheduler::defaultto the real detected CPU thread count viaplatform-core, not a hardcoded guess. - Runtime
- Owns subsystem instances and drives the frame loop. Construct once with
Runtime::new, then callRuntime::tickonce per frame. - Subsystem
Manager - Registry of active subsystems for the current
Runtime— real owned instances, not stubs: anecs-coreWorld(available for application-level entity/Transformuse; not synced withbodiesbelow — no such mapping is defined anywhere in the workspace yet, and inventing one here would be new, undocumented design, not wiring together what already exists),physics-core’s body list plus its broad/narrow-phase and solver/integrator, andaudio-core’s listener, emitters and mixer. The only place in the workspace allowed to know about every*-coreat once — see docs/dependency-rules.md rule 7.