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:
@@ -0,0 +1,146 @@
|
||||
//! The system schedule: the ordered phases an [`App`](super::App) runs each
|
||||
//! frame, and the per-phase lists of systems modules attach to.
|
||||
|
||||
use super::App;
|
||||
|
||||
/// The ordered phases of one frame.
|
||||
///
|
||||
/// Systems are attached to a phase and run in phase order; within a phase they
|
||||
/// run in registration order, so behavior is fully deterministic. The phases
|
||||
/// mirror a conventional game loop:
|
||||
///
|
||||
/// - [`First`](Self::First) — start-of-frame bookkeeping.
|
||||
/// - [`Input`](Self::Input) — gather input (Stage 7).
|
||||
/// - [`PreUpdate`](Self::PreUpdate) — engine work before game logic.
|
||||
/// - [`FixedUpdate`](Self::FixedUpdate) — fixed-timestep work; runs **zero or
|
||||
/// more** times per frame so simulation is frame-rate independent. Physics
|
||||
/// (Stage 9) lives here.
|
||||
/// - [`Update`](Self::Update) — per-frame game logic.
|
||||
/// - [`PostUpdate`](Self::PostUpdate) — engine work after game logic.
|
||||
/// - [`Render`](Self::Render) — drawing (Stage 5 pipeline onward).
|
||||
/// - [`Last`](Self::Last) — end-of-frame cleanup.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub enum Schedule {
|
||||
First,
|
||||
Input,
|
||||
PreUpdate,
|
||||
FixedUpdate,
|
||||
Update,
|
||||
PostUpdate,
|
||||
Render,
|
||||
Last,
|
||||
}
|
||||
|
||||
impl Schedule {
|
||||
/// The once-per-frame phases, in order (everything except `FixedUpdate`,
|
||||
/// which is driven separately by the fixed-timestep accumulator).
|
||||
pub(crate) const PER_FRAME: [Schedule; 7] = [
|
||||
Schedule::First,
|
||||
Schedule::Input,
|
||||
Schedule::PreUpdate,
|
||||
Schedule::Update,
|
||||
Schedule::PostUpdate,
|
||||
Schedule::Render,
|
||||
Schedule::Last,
|
||||
];
|
||||
}
|
||||
|
||||
/// One registered system: a closure plus the module that contributed it (so the
|
||||
/// module can be disabled or removed).
|
||||
pub(crate) struct SystemEntry {
|
||||
pub(crate) module: Option<&'static str>,
|
||||
pub(crate) run: Box<dyn FnMut(&mut App)>,
|
||||
}
|
||||
|
||||
/// The collection of systems, grouped by phase, in registration order.
|
||||
#[derive(Default)]
|
||||
pub(crate) struct Systems {
|
||||
first: Vec<SystemEntry>,
|
||||
input: Vec<SystemEntry>,
|
||||
pre_update: Vec<SystemEntry>,
|
||||
fixed_update: Vec<SystemEntry>,
|
||||
update: Vec<SystemEntry>,
|
||||
post_update: Vec<SystemEntry>,
|
||||
render: Vec<SystemEntry>,
|
||||
last: Vec<SystemEntry>,
|
||||
}
|
||||
|
||||
impl Systems {
|
||||
fn phase_mut(&mut self, phase: Schedule) -> &mut Vec<SystemEntry> {
|
||||
match phase {
|
||||
Schedule::First => &mut self.first,
|
||||
Schedule::Input => &mut self.input,
|
||||
Schedule::PreUpdate => &mut self.pre_update,
|
||||
Schedule::FixedUpdate => &mut self.fixed_update,
|
||||
Schedule::Update => &mut self.update,
|
||||
Schedule::PostUpdate => &mut self.post_update,
|
||||
Schedule::Render => &mut self.render,
|
||||
Schedule::Last => &mut self.last,
|
||||
}
|
||||
}
|
||||
|
||||
fn phase(&self, phase: Schedule) -> &[SystemEntry] {
|
||||
match phase {
|
||||
Schedule::First => &self.first,
|
||||
Schedule::Input => &self.input,
|
||||
Schedule::PreUpdate => &self.pre_update,
|
||||
Schedule::FixedUpdate => &self.fixed_update,
|
||||
Schedule::Update => &self.update,
|
||||
Schedule::PostUpdate => &self.post_update,
|
||||
Schedule::Render => &self.render,
|
||||
Schedule::Last => &self.last,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn push(&mut self, phase: Schedule, entry: SystemEntry) {
|
||||
self.phase_mut(phase).push(entry);
|
||||
}
|
||||
|
||||
pub(crate) fn total(&self) -> usize {
|
||||
Schedule::PER_FRAME
|
||||
.iter()
|
||||
.chain(std::iter::once(&Schedule::FixedUpdate))
|
||||
.map(|p| self.phase(*p).len())
|
||||
.sum()
|
||||
}
|
||||
|
||||
const ALL_PHASES: [Schedule; 8] = [
|
||||
Schedule::First,
|
||||
Schedule::Input,
|
||||
Schedule::PreUpdate,
|
||||
Schedule::FixedUpdate,
|
||||
Schedule::Update,
|
||||
Schedule::PostUpdate,
|
||||
Schedule::Render,
|
||||
Schedule::Last,
|
||||
];
|
||||
|
||||
/// Drops every system contributed by `module`.
|
||||
pub(crate) fn remove_module(&mut self, module: &str) {
|
||||
for phase in Self::ALL_PHASES {
|
||||
self.phase_mut(phase).retain(|e| e.module != Some(module));
|
||||
}
|
||||
}
|
||||
|
||||
/// Appends all of `other`'s systems (used to fold back systems registered
|
||||
/// while the frame was running).
|
||||
pub(crate) fn merge(&mut self, mut other: Systems) {
|
||||
for phase in Self::ALL_PHASES {
|
||||
let tail = std::mem::take(other.phase_mut(phase));
|
||||
self.phase_mut(phase).extend(tail);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs one phase: every enabled system in registration order.
|
||||
///
|
||||
/// The [`Systems`] are moved out of the [`App`] before phases run (so systems
|
||||
/// get exclusive `&mut App` access), so `systems` and `app` here are disjoint.
|
||||
/// Systems from a disabled module are skipped without being removed.
|
||||
pub(crate) fn run_phase(systems: &mut Systems, app: &mut App, phase: Schedule) {
|
||||
for entry in systems.phase_mut(phase) {
|
||||
if app.is_system_enabled(entry.module) {
|
||||
(entry.run)(app);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user