Import Oxide engine (Stages 0–10) under MIT license
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>
This commit is contained in:
+160
@@ -0,0 +1,160 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user