From 517a64156a36cada086a11779ae4dd5bd9f1e7a7 Mon Sep 17 00:00:00 2001 From: tali Date: Fri, 14 Apr 2023 13:25:29 -0400 Subject: [PATCH] add fancy progress bar :D --- tidepool/src/main.rs | 63 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 54 insertions(+), 9 deletions(-) diff --git a/tidepool/src/main.rs b/tidepool/src/main.rs index a5c35f1..794c67d 100644 --- a/tidepool/src/main.rs +++ b/tidepool/src/main.rs @@ -3,7 +3,7 @@ use rand::RngCore as _; use std::io::{Read, Write}; use std::path::Path; use std::sync::{atomic, mpsc, Arc}; -use std::time::Duration; +use std::time::{Duration, Instant}; use fish::bot; use tidepool::{cli, config, output, sim}; @@ -75,14 +75,21 @@ fn single(args: cli::SingleRun) -> Result<()> { tracing::debug!("heartbeat"); } Msg::Status(_id, status) => { - if !args.io.quiet { - let ps = status.pieces; - let ll = status.lines_left; - let pps = ps as f64 / status.time.as_secs_f64(); - eprintln!("#{ps}, {ll} lines left, {pps:.2} pps"); + if exit_early.load(atomic::Ordering::Relaxed) { + continue; } + write_progress_bar( + &args.io, + status.pieces, + status.lines_left, + config.game.goal, + status.time, + )?; } Msg::Done(_id, output) => { + // dropping rx early does two things here: 1) removing any of the 'break's + // in this block become a compile error, 2) interrupts will not be able to + // be sent so the ctrl-c handler will abort the program std::mem::drop(rx); if output.cleared < config.game.goal { @@ -144,6 +151,44 @@ fn write_output(io_args: &cli::IoArgs, path: Option<&Path>, output: &output::Out } } +fn write_progress_bar( + io_args: &cli::IoArgs, + pieces: usize, + lines_left: usize, + goal: usize, + time: Duration, +) -> std::io::Result<()> { + if io_args.quiet { + return Ok(()); + } + + let width = 40; // TODO: get terminal size? + let cleared = goal - lines_left; + let pps = pieces as f64 / time.as_secs_f64().max(0.001); + let pace = pieces * 100 / cleared.max(1); + + let writer = std::io::stderr().lock(); + let mut writer = std::io::BufWriter::new(writer); + write!(writer, "\r[")?; + for i in 0..width { + if goal * i <= cleared * (width - 1) { + write!(writer, "#") + } else { + write!(writer, " ") + }?; + } + write!( + writer, + "] {cleared}/{goal} #{pieces}, {pps:.2} pps, {pace} pace" + )?; + + if cleared == goal { + writeln!(writer) + } else { + writer.flush() + } +} + fn prompt_yn(io_args: &cli::IoArgs, msg: &str) -> bool { if io_args.noninteractive { return true; @@ -174,7 +219,7 @@ fn run_simulation( }; let mut sim = sim::Simul::new(seed, sim_opts); - let time_start = std::time::Instant::now(); + let time_start = Instant::now(); while sim.lines_left() > 0 { if exit_early.load(atomic::Ordering::Relaxed) { @@ -213,14 +258,14 @@ fn run_simulation( let status = Status { lines_left: sim.lines_left(), pieces: sim.pieces(), - time: std::time::Instant::now() - time_start, + time: Instant::now() - time_start, }; if tx.send(Msg::Status(id, status.into())).is_err() { break; } } - let time_end = std::time::Instant::now(); + let time_end = Instant::now(); let profile = data_args.profile.then(|| output::Profile { time: time_end - time_start,