0.1.0-0-pre.3
This commit is contained in:
83
crates/engines/engine-v1-common/src/game_loop.rs
Normal file
83
crates/engines/engine-v1-common/src/game_loop.rs
Normal file
@@ -0,0 +1,83 @@
|
||||
// file: crates/engines/engine-v1-common/src/game_loop.rs
|
||||
// version: 1
|
||||
|
||||
/// 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);
|
||||
}
|
||||
|
||||
/// 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<G>(&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)]
|
||||
mod unit_tests {
|
||||
#[derive(Default)]
|
||||
struct CountingGame {
|
||||
updates: u64,
|
||||
last_frame: Option<crate::EngineFrame>,
|
||||
}
|
||||
|
||||
impl crate::EngineGame for CountingGame {
|
||||
fn update(&mut self, frame: crate::EngineFrame, _input: crate::InputState) {
|
||||
self.updates = self.updates.saturating_add(1);
|
||||
self.last_frame = Some(frame);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fixed_step_runner_advances_index_and_elapsed_time() {
|
||||
let mut game = CountingGame::default();
|
||||
let mut runner = crate::FixedStepRunner::new(std::time::Duration::from_millis(16));
|
||||
runner.tick(&mut game, crate::InputState::none());
|
||||
runner.tick(&mut game, crate::InputState::none());
|
||||
assert_eq!(game.updates, 2);
|
||||
assert_eq!(runner.next_frame_index(), 2);
|
||||
assert_eq!(runner.elapsed(), std::time::Duration::from_millis(32));
|
||||
assert_eq!(game.last_frame.map(crate::EngineFrame::index), Some(1));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user