diff --git a/Cargo.toml b/Cargo.toml index 156bb23..2ccbd60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "slowcat" version = "1.0.0" -edition = "2021" +edition = "2024" [dependencies] diff --git a/src/main.rs b/src/main.rs index 8f569c4..fe89a12 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,20 +1,70 @@ use std::{ env, - io::{stdin, stdout, Read, StdoutLock, Write}, - time::Duration, + io::{Read, StdoutLock, Write, stdin, stdout}, + time::{Duration, Instant}, }; static DEFAULT_HZ: u32 = 100; fn output_slow(data: &[u8], hz: u32, out: &mut StdoutLock) { - // write 1 byte at a time - data.iter().for_each(|&b| { - out.write_all(&[b]).unwrap(); - out.flush().unwrap(); + let interval = Duration::from_secs_f64(1.0 / hz as f64); - // sleep according to given frequency - std::thread::sleep(Duration::from_secs_f64(1.0 / hz as f64)); - }); + // For very high frequencies, use adaptive batching + if hz > 1000 { + adaptive_batch_output(data, interval, out); + } else { + // Original behavior for lower frequencies + let mut next_time = Instant::now(); + for &b in data { + out.write_all(&[b]).unwrap(); + out.flush().unwrap(); + + next_time += interval; + let now = Instant::now(); + if next_time > now { + std::thread::sleep(next_time - now); + } + } + } +} + +fn adaptive_batch_output(data: &[u8], interval: Duration, out: &mut StdoutLock) { + let frame_duration = Duration::from_millis(20); // 20ms target window + let mut batch_size = 1usize; + let mut i = 0; + let mut last_batch_start = Instant::now(); + + while i < data.len() { + let batch_start = Instant::now(); + let end = (i + batch_size).min(data.len()); + let actual_bytes = end - i; + + // Output the batch + out.write_all(&data[i..end]).unwrap(); + out.flush().unwrap(); + + let batch_duration = batch_start.elapsed(); + i = end; + + // Calculate how many bytes we should output per 20ms window + let target_bytes_per_frame = (frame_duration.as_secs_f64() / interval.as_secs_f64()) as usize; + + // Calculate optimal batch size based on measured throughput + if batch_duration.as_secs_f64() > 0.0 { + let measured_bytes_per_sec = actual_bytes as f64 / batch_duration.as_secs_f64(); + let optimal_batch_size = (measured_bytes_per_sec * frame_duration.as_secs_f64()) as usize; + batch_size = optimal_batch_size.max(1).min(target_bytes_per_frame); + } + + // Sleep for the expected time for this batch + let expected_sleep = interval * actual_bytes as u32; + let elapsed_since_batch_start = last_batch_start.elapsed(); + if expected_sleep > elapsed_since_batch_start { + std::thread::sleep(expected_sleep - elapsed_since_batch_start); + } + + last_batch_start = batch_start; + } } fn main() {