App, Modules & Scheduling
oxide_engine::app is where the Stage-5 core framework comes together. An
App owns the shared engine state and a Schedule of systems; functionality
is added by modules. This is the spine the rest of the engine plugs into: the
engine is composed rather than hard-wired, and an exported game compiles in
only the modules it registers.
The App
An App owns:
- the active
Scene, - the shared
AssetServer, - the
TypeRegistry(dual-editable components), - the project's
LayerRegistry, - frame
Time, and - arbitrary user resources (a type-keyed store).
use oxide_engine::app::{App, DefaultModules};
let mut app = App::new();
app.add_modules(DefaultModules);
app.update(1.0 / 60.0); // advance one frame
Note: the application core is
oxide_engine::app::App. It is intentionally not in the prelude, to avoid clashing with the windowingApptrait (the per-window event handler). Import it directly.
Resources
Resources are shared singletons addressed by type — the home for state that isn't per-entity (an input map, a physics world, game settings):
# use oxide_engine::app::App;
# let mut app = App::new();
app.insert_resource(0u32);
*app.get_resource_mut::<u32>().unwrap() += 1;
assert_eq!(app.get_resource::<u32>(), Some(&1));
Systems and the schedule
A system is any FnMut(&mut App) attached to a Schedule phase. Phases run
in a fixed order each frame; within a phase, systems run in registration order,
so behavior is fully deterministic.
| Phase | Purpose |
|---|---|
First |
start-of-frame bookkeeping |
Input |
gather input (Stage 7) |
PreUpdate |
engine work before game logic |
FixedUpdate |
fixed-timestep work; runs 0..n times per frame (physics, Stage 9) |
Update |
per-frame game logic |
PostUpdate |
engine work after game logic |
Render |
drawing (Stage 5 pipeline onward) |
Last |
end-of-frame cleanup |
use oxide_engine::app::{App, Schedule};
use oxide_engine::prelude::*;
let mut app = App::new();
app.scene.spawn("spinner", Transform::IDENTITY);
app.add_system(Schedule::Update, |app| {
let dt = app.time.delta;
for e in app.scene.entities().collect::<Vec<_>>() {
if let Some(mut t) = app.scene.get_mut::<Transform>(e) {
t.translation.x += dt;
}
}
});
Systems get exclusive &mut App while running (the schedule is moved out of the
app for the duration), so a system can freely read and mutate the scene,
resources, and assets.
Fixed timestep
FixedUpdate is driven by an accumulator so simulation is frame-rate
independent: each update(dt) runs as many whole fixed_delta steps as the
accumulated time allows. The number of steps per frame is capped so a long
stall (a breakpoint, a hitch) cannot trigger an unbounded catch-up "spiral of
death". Set the rate with app.set_fixed_timestep(seconds) (default 1/60).
The whole frame's scheduling overhead is a few dozen nanoseconds even with
several systems registered (see cargo bench -p oxide-engine --bench app), so it
is lost in the noise next to any real per-frame work.
Modules
A Module is the unit of engine extension. Its build method
registers systems, component types, asset loaders, and resources through the
App facade. Everything registered during build is attributed to the
module, so it can be enabled, disabled, or removed as one unit.
use oxide_engine::app::{App, Module, Schedule};
struct HeartbeatModule;
impl Module for HeartbeatModule {
fn name(&self) -> &'static str { "heartbeat" }
fn build(&self, app: &mut App) {
app.insert_resource(0u64);
app.add_system(Schedule::Update, |app| {
*app.get_resource_mut::<u64>().unwrap() += 1;
});
}
}
let mut app = App::new();
app.add_module(HeartbeatModule);
Enable / disable / remove
These power the editor's module management (Stage 6) and the "ship only what you use" principle:
# use oxide_engine::app::{App, Module, Schedule};
# struct HeartbeatModule;
# impl Module for HeartbeatModule {
# fn name(&self) -> &'static str { "heartbeat" }
# fn build(&self, app: &mut App) {}
# }
# let mut app = App::new();
# app.add_module(HeartbeatModule);
app.set_module_enabled("heartbeat", false); // systems skipped, nothing removed
app.set_module_enabled("heartbeat", true); // resumes
app.remove_module("heartbeat"); // systems, types, loaders, resources gone
remove_module undoes every registration the module made — its systems,
reflected types, asset loaders, and resources — leaving no dangling references.
Disabling is the cheap, reversible version (systems are skipped but kept).
Built-in modules
DefaultModules bundles the engine's standard set:
core— registers the always-present scene component types (Transform,Node,Layer,Tags) for reflection, exposing them to the editor and scripts.render— registers the renderableMeshRenderercomponent and (as the data-driven render pipeline grows) the render-phase systems.
Subsystems from Stage 9 on (physics, audio, …) are built as their own
feature-gated crates, each exposing a Module, so a project pays for them only
by registering them.