// file: crates/engines/engine-v1-common/src/game_loop.rs // version: 4 /// Minimal update contract implemented by a game state consumed by engine V1. pub trait EngineGame { /// Advances the game by one engine update using platform-independent input. fn update(&mut self, frame: crate::EngineFrame, input: crate::InputState); /// Handles a platform-independent request to leave the current runtime. /// /// Games may mutate their state and return [`crate::QuitDecision::Continue`] to implement /// pause/confirmation screens, navigation, score submission, saving, or other workflows. fn quit_requested(&mut self, _request: crate::QuitRequest) -> crate::QuitDecision { return crate::QuitDecision::Exit; } /// Produces the platform-independent scene rendered after the update. fn scene(&self) -> crate::EngineScene { return crate::EngineScene::empty(crate::RenderColor::rgb(18, 18, 24)); } } /// Deterministic fixed-step driver used before and underneath platform event loops. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct FixedStepRunner { delta: std::time::Duration, next_frame_index: u64, elapsed: std::time::Duration, } impl FixedStepRunner { /// Creates a fixed-step runner using the supplied update duration. #[must_use] pub const fn new(delta: std::time::Duration) -> Self { return Self { delta, next_frame_index: 0, elapsed: std::time::Duration::ZERO }; } /// Returns the configured fixed update duration. #[must_use] pub const fn delta(self) -> std::time::Duration { return self.delta; } /// Returns the index that will be assigned to the next update. #[must_use] pub const fn next_frame_index(self) -> u64 { return self.next_frame_index; } /// Returns the accumulated simulated time. #[must_use] pub const fn elapsed(self) -> std::time::Duration { return self.elapsed; } /// Advances one game state by one deterministic update. pub fn tick(&mut self, game: &mut G, input: crate::InputState) where G: crate::EngineGame, { let frame = crate::EngineFrame::new(self.next_frame_index, self.delta, self.elapsed); game.update(frame, input); self.next_frame_index = self.next_frame_index.saturating_add(1); self.elapsed = self.elapsed.saturating_add(self.delta); return; } } #[cfg(test)] #[path = "../unit_tests/game_loop.rs"] mod tests;