- Introduced `SessionId` newtype for validated session identifiers, ensuring safety against path traversal attacks. - Updated session repository methods to accept `SessionId` instead of raw strings, enhancing type safety. - Removed redundant error handling in repository methods by leveraging the new `Error` type from `zesdex_utils`. - Simplified atomic JSON write operations by eliminating unnecessary error conversions. - Enhanced integer casting with a new `CastOr` trait for safer narrowing conversions. - Removed deprecated error handling code and consolidated error types across the codebase. - Updated HTTP handlers to utilize the new session ID validation, improving overall robustness.
97 lines
3.2 KiB
Rust
97 lines
3.2 KiB
Rust
//! Adaptive poll-rate event loop: polls faster for IDLE_THRESHOLD_MS
|
|
//! after any activity, then slows down to conserve CPU.
|
|
//!
|
|
//! Flow: `mark_active()` sets a fast-poll deadline; `poll_interval()`
|
|
//! checks if the deadline is still in the future and returns either
|
|
//! `FAST_POLL_MS` (8 ms) or `SLOW_POLL_MS` (100 ms). `is_idle()` reports
|
|
//! whether the deadline has expired.
|
|
use std::collections::VecDeque;
|
|
use std::time::{Duration, Instant};
|
|
use zesdex_utils::CastOr;
|
|
|
|
use crate::app::state::runtime::TurnEvent;
|
|
use tracing;
|
|
|
|
const FAST_POLL_MS: u64 = 8;
|
|
const SLOW_POLL_MS: u64 = 100;
|
|
const IDLE_THRESHOLD_MS: u64 = 500;
|
|
|
|
/// Tracks whether the app has been active vs idle to adjust the TUI poll
|
|
/// rate, balancing responsiveness against CPU usage.
|
|
pub struct EventLoop {
|
|
last_activity: Instant,
|
|
fast_poll_until: Option<Instant>,
|
|
}
|
|
|
|
impl EventLoop {
|
|
/// Create an `EventLoop` with the current instant as the last activity.
|
|
pub fn new() -> Self {
|
|
EventLoop {
|
|
last_activity: Instant::now(),
|
|
fast_poll_until: None,
|
|
}
|
|
}
|
|
|
|
/// Return the appropriate polling delay based on activity state.
|
|
///
|
|
/// Flow: if `fast_poll_until` is set and the deadline hasn't expired,
|
|
/// return `FAST_POLL_MS`; otherwise return `SLOW_POLL_MS`.
|
|
pub fn poll_interval(&self) -> Duration {
|
|
if let Some(fast_until) = self.fast_poll_until {
|
|
if Instant::now() < fast_until {
|
|
return Duration::from_millis(FAST_POLL_MS);
|
|
}
|
|
}
|
|
Duration::from_millis(SLOW_POLL_MS)
|
|
}
|
|
|
|
/// Mark the current time as the last activity and arm the fast-poll
|
|
/// window for the next `IDLE_THRESHOLD_MS`.
|
|
///
|
|
/// Called by the event loop whenever a TurnEvent arrives, keeping the
|
|
/// UI responsive during bursts of activity.
|
|
pub fn mark_active(&mut self) {
|
|
self.last_activity = Instant::now();
|
|
let deadline = Duration::from_millis(IDLE_THRESHOLD_MS);
|
|
self.fast_poll_until = Some(Instant::now() + deadline);
|
|
tracing::debug!(
|
|
"[event-loop] marked active — fast-poll armed for next {}ms",
|
|
IDLE_THRESHOLD_MS,
|
|
);
|
|
}
|
|
|
|
/// Return `true` if the app has been idle for more than `IDLE_THRESHOLD_MS`.
|
|
pub fn is_idle(&self) -> bool {
|
|
let elapsed: u64 = self.last_activity.elapsed().as_millis().cast_or(u64::MAX);
|
|
elapsed > IDLE_THRESHOLD_MS
|
|
}
|
|
|
|
/// Drain all pending `TurnEvent`s from the shared mutex queue.
|
|
///
|
|
/// Flow: acquire the mutex lock → drain the VecDeque into a Vec → release.
|
|
/// Returns an empty Vec if the lock is poisoned.
|
|
///
|
|
/// Return: a `Vec` of all events that were in the queue (may be empty).
|
|
pub fn drain_events(
|
|
events: &std::sync::Mutex<VecDeque<TurnEvent>>,
|
|
) -> Vec<TurnEvent> {
|
|
let drained: Vec<TurnEvent> = events
|
|
.lock()
|
|
.map(|mut q| q.drain(..).collect())
|
|
.unwrap_or_default();
|
|
if !drained.is_empty() {
|
|
tracing::debug!(
|
|
"[event-loop] drained {} event(s)",
|
|
drained.len(),
|
|
);
|
|
}
|
|
drained
|
|
}
|
|
}
|
|
|
|
impl Default for EventLoop {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|