9eead719b0
Full project snapshot migrated to new Gitea remote without history: engine, editor, physics, script, examples, tests, docs, and assets. Relicensed from GPLv3 to MIT and updated repo URLs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
161 lines
5.5 KiB
Markdown
161 lines
5.5 KiB
Markdown
# 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`](scene.md),
|
|
- the shared [`AssetServer`](assets.md),
|
|
- the [`TypeRegistry`](reflection.md) (dual-editable components),
|
|
- the project's [`LayerRegistry`](layers.md),
|
|
- frame [`Time`], and
|
|
- arbitrary user **resources** (a type-keyed store).
|
|
|
|
```rust
|
|
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 windowing
|
|
> [`App`](windowing.md) trait (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):
|
|
|
|
```rust
|
|
# 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 |
|
|
|
|
```rust
|
|
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`](Module::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.
|
|
|
|
```rust
|
|
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:
|
|
|
|
```rust
|
|
# 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 renderable `MeshRenderer` component 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.
|
|
|
|
[`App`]: ../engine/src/app/mod.rs
|
|
[`Time`]: ../engine/src/app/mod.rs
|
|
[`Schedule`]: ../engine/src/app/schedule.rs
|
|
[`Module`]: ../engine/src/app/module.rs
|
|
[`Scene`]: ../engine/src/scene/graph.rs
|