ai-agent 0.13.4

Idiomatic agent sdk inspired by the claude code source leak
Documentation
#![allow(dead_code)]

use std::time::Instant;

#[derive(Debug, Clone)]
pub struct FpsMetrics {
    pub average_fps: f64,
    pub low_1pct_fps: f64,
}

pub struct FpsTracker {
    frame_durations: Vec<f64>,
    first_render_time: Option<Instant>,
    last_render_time: Option<Instant>,
}

impl FpsTracker {
    pub fn new() -> Self {
        FpsTracker {
            frame_durations: Vec::new(),
            first_render_time: None,
            last_render_time: None,
        }
    }

    pub fn record(&mut self, duration_ms: f64) {
        let now = Instant::now();
        if self.first_render_time.is_none() {
            self.first_render_time = Some(now);
        }
        self.last_render_time = Some(now);
        self.frame_durations.push(duration_ms);
    }

    pub fn get_metrics(&self) -> Option<FpsMetrics> {
        if self.frame_durations.is_empty() {
            return None;
        }

        let first = self.first_render_time?;
        let last = self.last_render_time?;

        let total_time_ms = last.duration_since(first).as_millis() as f64;
        if total_time_ms <= 0.0 {
            return None;
        }

        let total_frames = self.frame_durations.len() as f64;
        let average_fps = total_frames / (total_time_ms / 1000.0);

        let mut sorted = self.frame_durations.clone();
        sorted.sort_by(|a, b| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal));
        let p99_index = ((sorted.len() as f64 * 0.01).ceil() as usize)
            .saturating_sub(1)
            .max(0);
        let p99_frame_time_ms = sorted.get(p99_index).copied().unwrap_or(0.0);
        let low_1pct_fps = if p99_frame_time_ms > 0.0 {
            1000.0 / p99_frame_time_ms
        } else {
            0.0
        };

        Some(FpsMetrics {
            average_fps: (average_fps * 100.0).round() / 100.0,
            low_1pct_fps: (low_1pct_fps * 100.0).round() / 100.0,
        })
    }
}

impl Default for FpsTracker {
    fn default() -> Self {
        Self::new()
    }
}

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

    #[test]
    fn test_fps_tracker() {
        let mut tracker = FpsTracker::new();
        tracker.record(16.0);
        tracker.record(17.0);
        thread::sleep(Duration::from_millis(50));
        tracker.record(15.0);

        let metrics = tracker.get_metrics();
        assert!(metrics.is_some());
    }
}