use std::io::{self, Write};
use std::time::{Duration, Instant};
use tokio::time::sleep;
#[derive(Clone)]
pub struct KnightRiderAnimator {
led_count: usize,
show_metrics: bool,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum AnimationPattern {
Sine,
Sawtooth,
Square,
Pulse,
}
impl KnightRiderAnimator {
pub fn new() -> Self {
Self {
led_count: 50,
show_metrics: true,
}
}
pub fn with_leds(led_count: usize) -> Self {
Self {
led_count,
show_metrics: true,
}
}
pub fn show_metrics(mut self, show: bool) -> Self {
self.show_metrics = show;
self
}
pub fn draw_frame(
&self,
position: usize,
direction: i32,
rate: f64,
min_rate: f64,
max_rate: f64,
) {
const BRIGHT_RED: &str = "\x1b[38;5;196m"; const RED: &str = "\x1b[38;5;160m"; const DIM_RED_1: &str = "\x1b[38;5;124m"; const DIM_RED_2: &str = "\x1b[38;5;88m"; const DIM_RED_3: &str = "\x1b[38;5;52m"; const RESET: &str = "\x1b[0m";
let mut display = vec![" ".to_string(); self.led_count];
if position < self.led_count {
display[position] = format!("{}█{}", BRIGHT_RED, RESET);
}
for i in 1..12 {
let trail_pos = if direction > 0 {
if position >= i {
position - i
} else {
0
}
} else {
if position + i < self.led_count {
position + i
} else {
self.led_count - 1
}
};
if trail_pos < self.led_count {
let color = match i {
1 => BRIGHT_RED,
2 => BRIGHT_RED,
3 => RED,
4 => RED,
5 => DIM_RED_1,
6 => DIM_RED_1,
7 => DIM_RED_2,
8 => DIM_RED_2,
9 => DIM_RED_3,
10 => DIM_RED_3,
_ => "\x1b[38;5;52;2m", };
display[trail_pos] = format!("{}█{}", color, RESET);
}
}
if position < self.led_count && rate < max_rate * 0.7 {
let dim_factor = rate / max_rate;
let main_color = if dim_factor < 0.3 {
DIM_RED_1
} else if dim_factor < 0.5 {
RED
} else {
BRIGHT_RED
};
display[position] = format!("{}█{}", main_color, RESET);
}
let pattern: String = display.join("");
if self.show_metrics {
print!(
"\r[{}] {:.0} msg/s (min: {:.0}, max: {:.0}) ",
pattern,
rate,
if min_rate > 1e9 { 0.0 } else { min_rate },
max_rate
);
} else {
print!("\r[{}]", pattern);
}
io::stdout().flush().unwrap();
}
pub async fn run_demo(&self, duration_secs: u64, base_speed_ms: u64, max_rate_value: f64) {
println!("Running Knight Rider animation demo...");
println!(
"Duration: {}s, Base speed: {}ms, Max rate: {}",
duration_secs, base_speed_ms, max_rate_value
);
println!("Press Ctrl+C to exit");
let duration = Duration::from_secs(duration_secs);
let start = Instant::now();
let mut position = 0;
let mut direction = 1;
let mut rate: f64;
let mut min_rate: f64 = f64::MAX;
let mut max_rate: f64 = 0.0;
let half_max_rate = max_rate_value / 2.0;
let patterns = [
AnimationPattern::Sine,
AnimationPattern::Sawtooth,
AnimationPattern::Square,
AnimationPattern::Pulse,
];
let mut current_pattern = 0;
let pattern_duration = Duration::from_secs(5);
let mut pattern_start = start;
while start.elapsed() < duration {
if pattern_start.elapsed() >= pattern_duration {
current_pattern = (current_pattern + 1) % patterns.len();
pattern_start = Instant::now();
println!(
"\nSwitching to {:?} wave pattern",
patterns[current_pattern]
);
}
let elapsed = start.elapsed().as_secs_f64();
let pattern_progress =
pattern_start.elapsed().as_secs_f64() / pattern_duration.as_secs_f64();
rate = match patterns[current_pattern] {
AnimationPattern::Sine => half_max_rate + half_max_rate * (elapsed * 0.2).sin(),
AnimationPattern::Sawtooth => {
let saw_val = pattern_progress % 1.0;
saw_val * max_rate_value
}
AnimationPattern::Square => {
if (elapsed * 0.2).sin() >= 0.0 {
max_rate_value
} else {
max_rate_value * 0.1
}
}
AnimationPattern::Pulse => {
let pulse_val = (elapsed * 2.0).sin();
if pulse_val > 0.9 {
max_rate_value
} else {
max_rate_value * 0.2
}
}
};
min_rate = min_rate.min(rate);
max_rate = max_rate.max(rate);
self.draw_frame(position, direction, rate, min_rate, max_rate);
position = (position as i32 + direction) as usize;
if position >= self.led_count - 1 {
direction = -1;
} else if position == 0 {
direction = 1;
}
let speed_factor = 1.0 - (rate / max_rate_value).min(1.0).max(0.0);
let delay_ms = base_speed_ms + (speed_factor * 80.0) as u64;
sleep(Duration::from_millis(delay_ms)).await;
}
println!("\nAnimation demo completed!");
}
}
impl Default for KnightRiderAnimator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_animator() {
let animator = KnightRiderAnimator::new();
assert_eq!(animator.led_count, 50);
assert_eq!(animator.show_metrics, true);
let custom_animator = KnightRiderAnimator::with_leds(30);
assert_eq!(custom_animator.led_count, 30);
let no_metrics = KnightRiderAnimator::new().show_metrics(false);
assert_eq!(no_metrics.show_metrics, false);
}
}