reaction 0.2.0

Universal low-latency input handling for game engines
Documentation
use crate::types::{KeyCode, MouseButton};
use std::collections::VecDeque;

#[derive(Debug, Clone)]
pub struct InputSnapshot {
    pub timestamp: f64,
    // Store minimal state delta or full state? Full state is safer for now.
    // To minimize allocations, we might want bitsets or simplified structures.
    // For MVP, we'll store active keys/buttons.
    pub active_keys: Vec<KeyCode>,
    pub mouse_pos: (f32, f32),
    pub mouse_buttons: Vec<MouseButton>,
    // Gamepad state could be large, store simplified or just what changed?
    // Let's store specific gamepad states if needed, skipping for MVP/focus on Mouse/Keyboard latency first
    // usually Mouse is critical for FPS.
}

pub struct HighFrequencyInputPoller {
    target_hz: u32,
    samples: VecDeque<InputSnapshot>,
    last_poll_time: f64,
}

impl HighFrequencyInputPoller {
    pub fn new(target_hz: u32) -> Self {
        Self {
            target_hz,
            samples: VecDeque::with_capacity((target_hz / 60) as usize * 2),
            last_poll_time: 0.0,
        }
    }

    pub fn target_interval(&self) -> f64 {
        1.0 / self.target_hz as f64
    }

    pub fn should_poll(&self, current_time: f64) -> bool {
        current_time - self.last_poll_time >= self.target_interval()
    }

    pub fn record_snapshot(&mut self, snapshot: InputSnapshot) {
        self.samples.push_back(snapshot);
        self.last_poll_time = self.samples.back().unwrap().timestamp;

        // Keep buffer manageable (e.g. 1 second worth, or just last few frames?)
        // Realistically we only need history for the current frame + maybe some prediction history
        if self.samples.len() > 1000 {
            self.samples.pop_front();
        }
    }

    /// Retrieve inputs relevant for the current frame logic.
    /// This could aggregate sub-frame moves (e.g. mouse delta accumulation).
    pub fn get_aggregated_mouse_delta(&self, from_time: f64) -> (f32, f32) {
        let mut delta_x = 0.0;
        let mut delta_y = 0.0;

        // Iterate relevant samples
        // We assume samples are sorted by time.
        // We find the first sample >= from_time

        let mut previous_pos: Option<(f32, f32)> = None;

        // Efficiently finding start index could be done with binary search if VecDeque supports it well or just linear scan from back
        for sample in self.samples.iter() {
            if sample.timestamp < from_time {
                previous_pos = Some(sample.mouse_pos);
                continue;
            }

            if let Some(prev) = previous_pos {
                delta_x += sample.mouse_pos.0 - prev.0;
                delta_y += sample.mouse_pos.1 - prev.1;
            }
            previous_pos = Some(sample.mouse_pos);
        }

        (delta_x, delta_y)
    }
}