//! The play-mode tick decision (Stage 8.7). //! //! The host runner ([`oxide_editor::main`](crate)) owns the play [`App`] and the //! window loop; this module isolates the one piece of that loop worth testing on //! its own: **how far to advance the simulation this frame** given the current //! [`PlayState`] and whether a single **Step** was requested. //! //! Keeping it a pure function pins the play-mode contract in a unit test instead //! of burying it in the (un-testable) GUI runner: //! //! - [`Playing`](PlayState::Playing) → advance one real frame ([`Tick::Frame`]). //! - [`Paused`](PlayState::Paused) + Step → advance exactly one fixed tick //! ([`Tick::FixedStep`]); a stray Step while *Playing* is ignored (the frame //! already advances). //! - [`Editing`](PlayState::Editing), or Paused with no Step → do nothing //! ([`Tick::Idle`]). //! //! [`App`]: oxide_engine::app::App use crate::state::PlayState; /// How the host runner should advance the play [`App`](oxide_engine::app::App) /// this frame. The runner maps each variant onto an engine call: `Frame` → /// [`App::update`](oxide_engine::app::App::update), `FixedStep` → /// [`App::step`](oxide_engine::app::App::step), `Idle` → no call. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Tick { /// Do not advance the simulation (editing, or paused with no step queued). Idle, /// Advance one normal frame by the real delta (playing). Frame, /// Advance exactly one fixed timestep (a single step while paused). FixedStep, } /// Decides how to advance the simulation this frame. `step_requested` is whether /// the user asked for a single **Step** since the last frame; it is honoured /// only while [`Paused`](PlayState::Paused). See the [module docs](self). pub fn tick_for(play: PlayState, step_requested: bool) -> Tick { match play { PlayState::Playing => Tick::Frame, PlayState::Paused if step_requested => Tick::FixedStep, PlayState::Paused | PlayState::Editing => Tick::Idle, } } #[cfg(test)] mod tests { use super::*; #[test] fn playing_advances_a_frame_regardless_of_step() { assert_eq!(tick_for(PlayState::Playing, false), Tick::Frame); // A stray step while playing is ignored — the frame already advances. assert_eq!(tick_for(PlayState::Playing, true), Tick::Frame); } #[test] fn paused_steps_only_when_requested() { assert_eq!(tick_for(PlayState::Paused, false), Tick::Idle); assert_eq!(tick_for(PlayState::Paused, true), Tick::FixedStep); } #[test] fn editing_never_advances() { assert_eq!(tick_for(PlayState::Editing, false), Tick::Idle); assert_eq!(tick_for(PlayState::Editing, true), Tick::Idle); } }