//! 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, } /// The collection of systems, grouped by phase, in registration order. #[derive(Default)] pub(crate) struct Systems { first: Vec, input: Vec, pre_update: Vec, fixed_update: Vec, update: Vec, post_update: Vec, render: Vec, last: Vec, } impl Systems { fn phase_mut(&mut self, phase: Schedule) -> &mut Vec { 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); } } }