use std::time::{Duration, Instant};
use makcu::{Button, Device, LockTarget, Result, VERSION};
const WARM_UP: usize = 10;
fn main() -> Result<()> {
println!("=== makcu v{} — Benchmark ===\n", VERSION);
let device = Device::connect()?;
println!("Connected to: {}", device.port_name());
println!("Mode: confirmed (default) — measuring real round-trip latency\n");
for _ in 0..WARM_UP {
device.move_xy(0, 0)?;
}
println!("=== PART 1: Confirmed Round-Trip Latency ===\n");
println!("Each measurement = time from send to receiving `>>> ` prompt back.\n");
let (avg, min, max) = bench_n(100, || device.move_xy(1, 0))?;
println!(
" move_xy (100x): avg={:>6}us min={:>6}us max={:>6}us",
avg, min, max
);
device.move_xy(-100, 0)?;
let (avg, min, max) = bench_n(100, || device.silent_move(1, 0))?;
println!(
" silent_move (100x): avg={:>6}us min={:>6}us max={:>6}us",
avg, min, max
);
device.silent_move(-100, 0)?;
let (avg, min, max) = bench_n(50, || device.wheel(1))?;
println!(
" wheel (50x): avg={:>6}us min={:>6}us max={:>6}us",
avg, min, max
);
device.wheel(-50)?;
let (avg, min, max) = bench_n(50, || {
device.button_down(Button::Left)?;
device.button_up(Button::Left)
})?;
println!(
" button down+up (50x): avg={:>6}us min={:>6}us max={:>6}us (2 cmds per iter)",
avg, min, max
);
let (avg, min, max) = bench_n(50, || device.button_up_force(Button::Left))?;
println!(
" button_up_force (50x): avg={:>6}us min={:>6}us max={:>6}us",
avg, min, max
);
let (avg, min, max) = bench_n(50, || {
device.set_lock(LockTarget::X, true)?;
device.set_lock(LockTarget::X, false)
})?;
println!(
" set_lock on+off (50x): avg={:>6}us min={:>6}us max={:>6}us (2 cmds per iter)",
avg, min, max
);
let (avg, min, max) = bench_n(50, || {
device.button_state(Button::Left)?;
Ok(())
})?;
println!(
" button_state (50x): avg={:>6}us min={:>6}us max={:>6}us",
avg, min, max
);
let (avg, min, max) = bench_n(50, || {
device.lock_state(LockTarget::X)?;
Ok(())
})?;
println!(
" lock_state (50x): avg={:>6}us min={:>6}us max={:>6}us",
avg, min, max
);
let (avg, min, max) = bench_n(20, || {
device.version()?;
Ok(())
})?;
println!(
" version (20x): avg={:>6}us min={:>6}us max={:>6}us",
avg, min, max
);
let (avg, min, max) = bench_n(50, || {
device.send_raw(b"km.version()\r\n")?;
Ok(())
})?;
println!(
" send_raw (50x): avg={:>6}us min={:>6}us max={:>6}us",
avg, min, max
);
println!("\n=== PART 2: Fire-and-Forget Throughput (fenced) ===\n");
println!("Sends N fire-and-forget commands, then 1 confirmed command as a fence.");
println!("Total time includes actual serial I/O — comparable to makcu-cpp.\n");
let ff = device.ff();
let (total, per, cps) = bench_ff_fenced(100, || ff.move_xy(1, 0), &device)?;
println!(
" ff move_xy (100x): total={:>6}us per_cmd={:>4}us ({:.0} cmd/s)",
total, per, cps
);
device.move_xy(-100, 0)?;
let (total, per, cps) = bench_ff_fenced(1000, || ff.move_xy(1, 0), &device)?;
println!(
" ff move_xy (1000x): total={:>6}us per_cmd={:>4}us ({:.0} cmd/s)",
total, per, cps
);
std::thread::sleep(Duration::from_millis(500));
device.move_xy(-1000, 0)?;
let (total, per, cps) = bench_ff_fenced(100, || ff.wheel(1), &device)?;
println!(
" ff wheel (100x): total={:>6}us per_cmd={:>4}us ({:.0} cmd/s)",
total, per, cps
);
device.wheel(-100)?;
let (total, per, cps) = bench_ff_fenced(100, || ff.button_down(Button::Left), &device)?;
println!(
" ff button_down (100x): total={:>6}us per_cmd={:>4}us ({:.0} cmd/s)",
total, per, cps
);
device.button_up_force(Button::Left)?;
let (total, per, cps) = bench_ff_fenced(100, || ff.silent_move(1, 0), &device)?;
println!(
" ff silent_move (100x): total={:>6}us per_cmd={:>4}us ({:.0} cmd/s)",
total, per, cps
);
device.silent_move(-100, 0)?;
let start = Instant::now();
for i in 0u64..100 {
ff.move_xy((i % 10) as i32, (i % 10) as i32)?;
}
device.move_xy(0, 0)?;
let elapsed = start.elapsed();
let per_cmd = elapsed.as_micros() as u64 / 101;
println!(
" ff 100 moves (cpp-style): total={:>6}us per_cmd={:>4}us ({:.0} cmd/s)",
elapsed.as_micros(),
per_cmd,
101.0 / elapsed.as_secs_f64()
);
std::thread::sleep(Duration::from_millis(200));
#[cfg(feature = "batch")]
{
println!("\n=== PART 3: Batch Coalesced Writes ===\n");
println!("Multiple commands sent as a single write_all() call.\n");
let start = Instant::now();
device
.batch()
.move_xy(10, 0)
.move_xy(0, 10)
.move_xy(-10, 0)
.move_xy(0, -10)
.wheel(1)
.wheel(-1)
.button_down(Button::Left)
.button_up(Button::Left)
.set_lock(LockTarget::X, true)
.set_lock(LockTarget::X, false)
.execute()?;
let elapsed = start.elapsed();
println!(
" batch 10 native cmds: {:>6}us total ({:>4}us per cmd equivalent)",
elapsed.as_micros(),
elapsed.as_micros() / 10
);
let start = Instant::now();
let mut batch = device.batch();
for i in 0..50 {
batch = batch.move_xy(if i % 2 == 0 { 2 } else { -2 }, 0);
}
batch.execute()?;
let elapsed = start.elapsed();
println!(
" batch 50 move_xy cmds: {:>6}us total ({:>4}us per cmd equivalent)",
elapsed.as_micros(),
elapsed.as_micros() / 50
);
}
#[cfg(feature = "extras")]
{
println!("\n=== PART 4: Extras (Software-Timed) ===\n");
println!("These include intentional sleeps, so measure overhead vs expected time.\n");
let expected_ms = 50;
let start = Instant::now();
device.click(Button::Middle, Duration::from_millis(expected_ms))?;
let elapsed = start.elapsed();
let overhead_us = elapsed.as_micros() as i64 - (expected_ms as i64 * 1000);
println!(
" click(50ms hold): {:>6}us elapsed ({:>+6}us overhead vs {}ms expected)",
elapsed.as_micros(),
overhead_us,
expected_ms
);
let steps = 10u32;
let interval_ms = 10u64;
let expected_ms = steps as u64 * interval_ms;
let start = Instant::now();
device.move_smooth(50, 0, steps, Duration::from_millis(interval_ms))?;
let elapsed = start.elapsed();
let overhead_us = elapsed.as_micros() as i64 - (expected_ms as i64 * 1000);
println!(
" move_smooth(10x10ms): {:>6}us elapsed ({:>+6}us overhead vs {}ms expected)",
elapsed.as_micros(),
overhead_us,
expected_ms
);
device.move_xy(-50, 0)?;
let steps = 5u32;
let interval_ms = 10u64;
let expected_ms = steps as u64 * interval_ms;
let start = Instant::now();
device.drag(
Button::Left,
30,
0,
steps,
Duration::from_millis(interval_ms),
)?;
let elapsed = start.elapsed();
let overhead_us = elapsed.as_micros() as i64 - (expected_ms as i64 * 1000);
println!(
" drag(5x10ms): {:>6}us elapsed ({:>+6}us overhead vs {}ms expected)",
elapsed.as_micros(),
overhead_us,
expected_ms
);
device.move_xy(-30, 0)?;
}
println!("\n=== COMPARISON NOTES ===\n");
println!(" makcu (ours): Confirmed mode measures REAL round-trip latency (~700-2000us)");
println!(" makcu-cpp: Claims ~40us/cmd — measures serial write only (fire-and-forget)");
println!(" makcu-rs: Claims ~0.0us/cmd — measures channel enqueue only (meaningless)");
println!();
println!(" Our F&F throughput is comparable to makcu-cpp's claimed numbers.");
println!(" Our confirmed latency is the ACTUAL time your code waits per command.");
device.disconnect();
println!("\nDisconnected");
println!("\n=== Benchmark complete! ===");
Ok(())
}
fn bench_n<F>(n: usize, mut f: F) -> Result<(u64, u64, u64)>
where
F: FnMut() -> Result<()>,
{
let mut total = Duration::ZERO;
let mut min = Duration::MAX;
let mut max = Duration::ZERO;
for _ in 0..n {
let start = Instant::now();
f()?;
let elapsed = start.elapsed();
total += elapsed;
min = min.min(elapsed);
max = max.max(elapsed);
}
let avg = total.as_micros() as u64 / n as u64;
let min = min.as_micros() as u64;
let max = max.as_micros() as u64;
Ok((avg, min, max))
}
fn bench_ff_fenced<F>(n: u64, mut ff_cmd: F, device: &Device) -> Result<(u64, u64, f64)>
where
F: FnMut() -> Result<()>,
{
let start = Instant::now();
for _ in 0..n {
ff_cmd()?;
}
device.move_xy(0, 0)?;
let elapsed = start.elapsed();
let total_cmds = n + 1;
let total_us = elapsed.as_micros() as u64;
let per_cmd = total_us / total_cmds;
let cps = total_cmds as f64 / elapsed.as_secs_f64();
Ok((total_us, per_cmd, cps))
}