liquidwar7core 0.2.0

Liquidwar7 core logic library, low-level things which are game-engine agnostic.
Documentation
// Copyright (C) 2025 Christian Mauduit <ufoot@ufoot.org>

//! Timing utilities for measuring game logic phases.
//!
//! This module provides [`StepTiming`] to measure individual phases of the game step
//! (gradient spread, fighters animate, other logic) and accumulate the results.

use std::time::Instant;

/// Accumulated timing for game step phases.
///
/// Tracks time spent in each phase of `do_step`:
/// - `spread`: Gradient propagation
/// - `animate`: Fighter movement
/// - `other`: Cursor handling, stats updates, etc.
///
/// Times are accumulated in nanoseconds internally for precision,
/// but returned as microseconds.
/// Call [`reset`](Self::reset) periodically to get per-window averages.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct StepTiming {
    spread_ns: u64,
    animate_ns: u64,
    other_ns: u64,
}

impl StepTiming {
    /// Creates a new timing accumulator with all values at zero.
    pub fn new() -> Self {
        Self::default()
    }

    /// Records elapsed time since `start` for gradient spread phase.
    #[inline]
    pub fn add_spread(&mut self, start: Instant) {
        self.spread_ns += start.elapsed().as_nanos() as u64;
    }

    /// Records elapsed time since `start` for fighters animate phase.
    #[inline]
    pub fn add_animate(&mut self, start: Instant) {
        self.animate_ns += start.elapsed().as_nanos() as u64;
    }

    /// Records elapsed time since `start` for other logic phase.
    #[inline]
    pub fn add_other(&mut self, start: Instant) {
        self.other_ns += start.elapsed().as_nanos() as u64;
    }

    /// Returns accumulated gradient spread time in microseconds.
    pub fn spread_us(&self) -> u64 {
        self.spread_ns / 1000
    }

    /// Returns accumulated fighters animate time in microseconds.
    pub fn animate_us(&self) -> u64 {
        self.animate_ns / 1000
    }

    /// Returns accumulated other logic time in microseconds.
    pub fn other_us(&self) -> u64 {
        self.other_ns / 1000
    }

    /// Returns total accumulated time across all phases in microseconds.
    pub fn total_us(&self) -> u64 {
        (self.spread_ns + self.animate_ns + self.other_ns) / 1000
    }

    /// Resets all accumulators to zero.
    pub fn reset(&mut self) {
        self.spread_ns = 0;
        self.animate_ns = 0;
        self.other_ns = 0;
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::thread;
    use std::time::Duration;

    #[test]
    fn test_new_is_zero() {
        let timing = StepTiming::new();
        assert_eq!(timing.spread_us(), 0);
        assert_eq!(timing.animate_us(), 0);
        assert_eq!(timing.other_us(), 0);
        assert_eq!(timing.total_us(), 0);
    }

    #[test]
    fn test_add_spread_accumulates() {
        let mut timing = StepTiming::new();
        let start = Instant::now();
        thread::sleep(Duration::from_millis(5));
        timing.add_spread(start);
        assert!(timing.spread_us() >= 4000, "Expected >= 4000us, got {}", timing.spread_us());
        assert_eq!(timing.animate_us(), 0);
        assert_eq!(timing.other_us(), 0);
    }

    #[test]
    fn test_add_animate_accumulates() {
        let mut timing = StepTiming::new();
        let start = Instant::now();
        thread::sleep(Duration::from_millis(5));
        timing.add_animate(start);
        assert_eq!(timing.spread_us(), 0);
        assert!(timing.animate_us() >= 4000);
        assert_eq!(timing.other_us(), 0);
    }

    #[test]
    fn test_add_other_accumulates() {
        let mut timing = StepTiming::new();
        let start = Instant::now();
        thread::sleep(Duration::from_millis(5));
        timing.add_other(start);
        assert_eq!(timing.spread_us(), 0);
        assert_eq!(timing.animate_us(), 0);
        assert!(timing.other_us() >= 4000);
    }

    #[test]
    fn test_total_sums_all() {
        let mut timing = StepTiming::new();
        timing.spread_ns = 100_000;  // 100 us in ns
        timing.animate_ns = 200_000; // 200 us in ns
        timing.other_ns = 300_000;   // 300 us in ns
        assert_eq!(timing.total_us(), 600);
    }

    #[test]
    fn test_reset_clears_all() {
        let mut timing = StepTiming::new();
        timing.spread_ns = 100_000;
        timing.animate_ns = 200_000;
        timing.other_ns = 300_000;
        timing.reset();
        assert_eq!(timing.spread_us(), 0);
        assert_eq!(timing.animate_us(), 0);
        assert_eq!(timing.other_us(), 0);
    }
}