#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::GameState;
const DEFAULT_STEPS_PER_SECOND: f64 = 25.0;
const MAX_STEPS_PER_PROCESS: u32 = 10;
fn odd_even_from_steps_count(steps_count: u64) -> bool {
let base = steps_count % 2 == 1;
let invert_7 = (steps_count / 7) % 2 == 1;
let invert_13 = (steps_count / 13) % 2 == 1;
let invert_37 = (steps_count / 37) % 2 == 1;
base ^ invert_7 ^ invert_13 ^ invert_37
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct GameHandler<S: GameState> {
state: S,
elapsed_time: f64,
accumulated_time: f64,
steps_per_second: f64,
steps_count: u64,
lagging: bool,
}
impl<S: GameState> GameHandler<S> {
pub fn new(state: S) -> Self {
Self {
state,
elapsed_time: 0.0,
accumulated_time: 0.0,
steps_per_second: DEFAULT_STEPS_PER_SECOND,
steps_count: 0,
lagging: false,
}
}
pub fn state(&self) -> &S {
&self.state
}
pub fn state_mut(&mut self) -> &mut S {
&mut self.state
}
pub fn elapsed_time(&self) -> f64 {
self.elapsed_time
}
pub fn steps_per_second(&self) -> f64 {
self.steps_per_second
}
pub fn set_steps_per_second(&mut self, steps_per_second: f64) {
assert!(steps_per_second > 0.0, "steps_per_second must be positive");
self.steps_per_second = steps_per_second;
}
pub fn steps_count(&self) -> u64 {
self.steps_count
}
pub fn is_lagging(&self) -> bool {
self.lagging
}
pub fn interpolation_factor(&self) -> f64 {
let step_duration = 1.0 / self.steps_per_second;
(self.accumulated_time / step_duration).clamp(0.0, 1.0)
}
pub fn odd_even(&self) -> bool {
odd_even_from_steps_count(self.steps_count)
}
fn step(&mut self) {
self.steps_count += 1;
self.state.do_step(self.odd_even(), self.steps_count);
}
pub fn process(&mut self, delta: f64) {
self.elapsed_time += delta;
self.accumulated_time += delta;
let step_duration = 1.0 / self.steps_per_second;
let mut steps_this_frame = 0u32;
while self.accumulated_time >= step_duration && steps_this_frame < MAX_STEPS_PER_PROCESS {
self.step();
self.accumulated_time -= step_duration;
steps_this_frame += 1;
}
if self.accumulated_time >= step_duration {
self.accumulated_time = self.accumulated_time.rem_euclid(step_duration);
self.lagging = true;
} else {
self.lagging = false;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, Clone, Default)]
struct DummyState {
step_count: u64,
}
impl GameState for DummyState {
fn do_step(&mut self, _odd_even: bool, _steps_count: u64) {
self.step_count += 1;
}
}
#[test]
fn test_new_handler_defaults() {
let handler = GameHandler::new(DummyState::default());
assert_eq!(handler.elapsed_time(), 0.0);
assert_eq!(handler.steps_per_second(), DEFAULT_STEPS_PER_SECOND);
assert!(!handler.odd_even());
assert_eq!(handler.steps_count(), 0);
}
#[test]
fn test_set_steps_per_second() {
let mut handler = GameHandler::new(DummyState::default());
handler.set_steps_per_second(30.0);
assert_eq!(handler.steps_per_second(), 30.0);
handler.set_steps_per_second(120.0);
assert_eq!(handler.steps_per_second(), 120.0);
}
#[test]
#[should_panic(expected = "steps_per_second must be positive")]
fn test_set_steps_per_second_zero_panics() {
let mut handler = GameHandler::new(DummyState::default());
handler.set_steps_per_second(0.0);
}
#[test]
#[should_panic(expected = "steps_per_second must be positive")]
fn test_set_steps_per_second_negative_panics() {
let mut handler = GameHandler::new(DummyState::default());
handler.set_steps_per_second(-1.0);
}
#[test]
fn test_process_no_steps_when_delta_too_small() {
let mut handler = GameHandler::new(DummyState::default());
handler.set_steps_per_second(10.0);
handler.process(0.05);
assert_eq!(handler.steps_count(), 0);
assert!(!handler.odd_even());
}
#[test]
fn test_process_one_step() {
let mut handler = GameHandler::new(DummyState::default());
handler.set_steps_per_second(10.0);
handler.process(0.1);
assert_eq!(handler.steps_count(), 1);
assert!(handler.odd_even());
}
#[test]
fn test_process_multiple_steps() {
let mut handler = GameHandler::new(DummyState::default());
handler.set_steps_per_second(10.0);
handler.process(0.35);
assert_eq!(handler.steps_count(), 3);
assert!(handler.odd_even());
}
#[test]
fn test_odd_even_pattern() {
let mut handler = GameHandler::new(DummyState::default());
handler.set_steps_per_second(1000.0);
let mut pattern = vec![handler.odd_even()];
for _ in 0..29 {
handler.process(0.001);
pattern.push(handler.odd_even());
}
let expected: Vec<bool> = (0..30u64).map(odd_even_from_steps_count).collect();
assert_eq!(pattern, expected, "odd_even pattern mismatch");
let same_consecutive = pattern.windows(2).filter(|w| w[0] == w[1]).count();
assert!(
same_consecutive > 0,
"Pattern should have some consecutive same values due to inversions"
);
}
#[test]
fn test_odd_even_breaks_simple_cycles() {
let mut handler = GameHandler::new(DummyState::default());
handler.set_steps_per_second(1000.0);
let mut pattern = Vec::new();
for _ in 0..100 {
pattern.push(handler.odd_even());
handler.process(0.001);
}
let simple_alternating: Vec<bool> = (0..100).map(|i| i % 2 == 1).collect();
assert_ne!(
pattern, simple_alternating,
"Pattern should not be simple alternation"
);
}
#[test]
fn test_odd_even_balanced_distribution() {
let total: u64 = 100000;
let mut true_count: u64 = 0;
for s in 0..total {
if odd_even_from_steps_count(s) {
true_count += 1;
}
}
let true_ratio = true_count as f32 / total as f32;
let false_ratio = 1.0 - true_ratio;
assert!(
(true_ratio - 0.5).abs() < 0.05,
"true ratio should be ~0.5, got {} ({} true, {} false)",
true_ratio,
true_count,
total - true_count
);
assert!(
(false_ratio - 0.5).abs() < 0.05,
"false ratio should be ~0.5, got {}",
false_ratio
);
}
#[test]
fn test_elapsed_time_accumulates() {
let mut handler = GameHandler::new(DummyState::default());
handler.process(0.5);
assert!((handler.elapsed_time() - 0.5).abs() < 1e-10);
handler.process(0.3);
assert!((handler.elapsed_time() - 0.8).abs() < 1e-10);
handler.process(0.2);
assert!((handler.elapsed_time() - 1.0).abs() < 1e-10);
}
#[test]
fn test_accumulated_time_carries_over() {
let mut handler = GameHandler::new(DummyState::default());
handler.set_steps_per_second(10.0);
handler.process(0.05); assert_eq!(handler.steps_count(), 0);
handler.process(0.05); assert_eq!(handler.steps_count(), 1);
handler.process(0.05); assert_eq!(handler.steps_count(), 1);
handler.process(0.06); assert_eq!(handler.steps_count(), 2);
}
#[test]
fn test_state_mut_access() {
let mut handler = GameHandler::new(DummyState::default());
handler.state_mut().step_count = 100;
assert_eq!(handler.state().step_count, 100);
}
#[test]
fn test_spiral_of_death_protection() {
let mut handler = GameHandler::new(DummyState::default());
handler.set_steps_per_second(10.0);
handler.process(5.0);
assert_eq!(
handler.steps_count(),
MAX_STEPS_PER_PROCESS as u64,
"Should cap at MAX_STEPS_PER_PROCESS"
);
let steps_before = handler.steps_count();
handler.process(0.0);
assert_eq!(
handler.steps_count(),
steps_before,
"No steps should run with 0 delta after time was dropped"
);
}
#[test]
fn test_spiral_of_death_drops_excess_time() {
let mut handler = GameHandler::new(DummyState::default());
handler.set_steps_per_second(10.0);
handler.process(100.0);
assert_eq!(handler.steps_count(), MAX_STEPS_PER_PROCESS as u64);
handler.process(0.05);
assert_eq!(
handler.steps_count(),
MAX_STEPS_PER_PROCESS as u64,
"Excess time should have been dropped, no new steps from small delta"
);
handler.process(0.06); assert_eq!(
handler.steps_count(),
MAX_STEPS_PER_PROCESS as u64 + 1,
"Should get one more step after accumulating enough time"
);
}
#[test]
fn test_is_lagging_reports_correctly() {
let mut handler = GameHandler::new(DummyState::default());
handler.set_steps_per_second(10.0);
assert!(!handler.is_lagging());
handler.process(0.1);
assert!(!handler.is_lagging(), "Should not lag with normal delta");
handler.process(100.0);
assert!(handler.is_lagging(), "Should lag after huge delta");
handler.process(0.1);
assert!(!handler.is_lagging(), "Should recover after normal delta");
}
#[test]
fn test_interpolation_factor() {
let mut handler = GameHandler::new(DummyState::default());
handler.set_steps_per_second(10.0);
assert!((handler.interpolation_factor() - 0.0).abs() < 1e-10);
handler.process(0.05);
assert!(
(handler.interpolation_factor() - 0.5).abs() < 1e-10,
"Expected 0.5, got {}",
handler.interpolation_factor()
);
handler.process(0.03);
assert!(
(handler.interpolation_factor() - 0.8).abs() < 1e-10,
"Expected 0.8, got {}",
handler.interpolation_factor()
);
handler.process(0.02);
assert_eq!(handler.steps_count(), 1);
assert!(
handler.interpolation_factor() < 0.01,
"Expected ~0.0 after step, got {}",
handler.interpolation_factor()
);
}
#[test]
fn test_default_steps_per_second_is_25() {
assert_eq!(DEFAULT_STEPS_PER_SECOND, 25.0);
}
}