diff --git a/Cargo.lock b/Cargo.lock index 5a92595..a4ca0ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,6 +43,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "cancellation" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7a879c84c21f354f13535f87ad119ac3be22ebb9097b552a0af6a78f86628c4" + [[package]] name = "cfg-if" version = "1.0.0" @@ -51,26 +57,24 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "3.2.16" +version = "4.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3dbbb6653e7c55cc8595ad3e1f7be8f32aba4eb7ff7f0fd1163d4f3d137c0a9" +checksum = "2148adefda54e14492fb9bddcc600b4344c5d1a3123bd666dcb939c6f0e0e57e" dependencies = [ "atty", "bitflags", "clap_derive", "clap_lex", - "indexmap", "once_cell", "strsim", "termcolor", - "textwrap", ] [[package]] name = "clap_derive" -version = "3.2.15" +version = "4.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ba52acd3b0a5c33aeada5cdaa3267cdc7c594a98731d4268cdc1532f4264cb4" +checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014" dependencies = [ "heck", "proc-macro-error", @@ -81,9 +85,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.2.4" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" dependencies = [ "os_str_bytes", ] @@ -96,9 +100,9 @@ checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" [[package]] name = "crossterm" -version = "0.24.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab9f7409c70a38a56216480fba371ee460207dd8926ccf5b4160591759559170" +checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" dependencies = [ "bitflags", "crossterm_winapi", @@ -119,6 +123,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "ctrlc" +version = "3.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d91974fbbe88ec1df0c24a4f00f99583667a7e2e6272b2b92d294d81e462173" +dependencies = [ + "nix", + "winapi", +] + [[package]] name = "derive_more" version = "0.99.17" @@ -176,12 +190,6 @@ dependencies = [ "wasi", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "heck" version = "0.4.0" @@ -197,16 +205,6 @@ dependencies = [ "libc", ] -[[package]] -name = "indexmap" -version = "1.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" -dependencies = [ - "autocfg", - "hashbrown", -] - [[package]] name = "itertools" version = "0.10.3" @@ -224,9 +222,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.126" +version = "0.2.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" +checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" [[package]] name = "lock_api" @@ -292,6 +290,18 @@ dependencies = [ "syn", ] +[[package]] +name = "nix" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e322c04a9e3440c327fca7b6c8a63e6890a32fa2ad689db972425f07e0d22abb" +dependencies = [ + "autocfg", + "bitflags", + "cfg-if", + "libc", +] + [[package]] name = "normalize-line-endings" version = "0.3.0" @@ -565,12 +575,6 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b" -[[package]] -name = "textwrap" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" - [[package]] name = "unicode-ident" version = "1.0.3" @@ -668,8 +672,10 @@ name = "wipe" version = "2.0.0" dependencies = [ "approx", + "cancellation", "clap", "crossterm", + "ctrlc", "derive_more", "mockall", "rand", diff --git a/Cargo.toml b/Cargo.toml index c5d446a..e160b61 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,8 +8,10 @@ repository = "https://github.com/ricoriedel/wipe" authors = ["Rico Riedel "] [dependencies] -clap = { version = "3.2", features = ["derive"] } -crossterm = "0.24" +clap = { version = "4.0.26", features = ["derive"] } +crossterm = "0.25.0" +ctrlc = "3.2.3" +cancellation = "0.1.0" derive_more = "0.99" rand = "0.8" diff --git a/src/error.rs b/src/error.rs index f4048b9..41e2458 100644 --- a/src/error.rs +++ b/src/error.rs @@ -21,6 +21,12 @@ impl From<&str> for Error { } } +impl From for Error { + fn from(err: ctrlc::Error) -> Self { + Error(err.to_string()) + } +} + #[cfg(test)] mod test { use super::*; diff --git a/src/timer.rs b/src/exec.rs similarity index 71% rename from src/timer.rs rename to src/exec.rs index 3881253..f70a975 100644 --- a/src/timer.rs +++ b/src/exec.rs @@ -1,5 +1,6 @@ use crate::Error; use crate::Renderer; +use cancellation::CancellationToken; use std::thread; use std::time::{Duration, Instant}; @@ -29,23 +30,23 @@ impl Clock for ClockImpl { /// A timer for rendering. #[derive(derive_more::Constructor)] -pub struct Timer { +pub struct Executor { clock: T, duration: Duration, delay: Duration, } -impl Timer { +impl Executor { /// Runs the animation main loop. - pub fn run(&self, mut renderer: impl Renderer) -> Result<(), Error> { + pub fn run(&self, mut renderer: impl Renderer, token: &CancellationToken) -> Result<(), Error> { let start = self.clock.now(); - let mut now = start; + let mut tick = start; - while now.duration_since(start) < self.duration { - let step = now.duration_since(start).as_secs_f32() / self.duration.as_secs_f32(); + while !token.is_canceled() && tick.duration_since(start) < self.duration { + let step = tick.duration_since(start).as_secs_f32() / self.duration.as_secs_f32(); renderer.render(step)?; - now = self.delay(now); + tick = self.delay(tick); } Ok(()) } @@ -66,6 +67,7 @@ impl Timer { mod test { use super::*; use crate::MockRenderer; + use cancellation::CancellationTokenSource; use mockall::predicate::eq; use mockall::Sequence; @@ -91,7 +93,7 @@ mod test { .return_const(begin + Duration::from_secs(20)) .in_sequence(clock_seq); - let timer = Timer::new(clock, Duration::from_secs(20), Duration::from_secs(10)); + let timer = Executor::new(clock, Duration::from_secs(20), Duration::from_secs(10)); let mut renderer = MockRenderer::new(); let renderer_seq = &mut Sequence::new(); @@ -109,7 +111,7 @@ mod test { .returning(|_| Ok(())) .in_sequence(renderer_seq); - timer.run(renderer).unwrap(); + timer.run(renderer, CancellationToken::none()).unwrap(); } #[test] @@ -140,12 +142,12 @@ mod test { .return_const(begin + Duration::from_secs(10)) .in_sequence(clock_seq); - let timer = Timer::new(clock, Duration::from_secs(10), Duration::from_secs(10)); + let timer = Executor::new(clock, Duration::from_secs(10), Duration::from_secs(10)); let mut renderer = MockRenderer::new(); renderer.expect_render().returning(|_| Ok(())); - timer.run(renderer).unwrap(); + timer.run(renderer, CancellationToken::none()).unwrap(); } #[test] @@ -165,11 +167,30 @@ mod test { .return_const(begin + Duration::from_secs(12)) .in_sequence(clock_seq); - let timer = Timer::new(clock, Duration::from_secs(10), Duration::from_secs(10)); + let timer = Executor::new(clock, Duration::from_secs(10), Duration::from_secs(10)); let mut renderer = MockRenderer::new(); renderer.expect_render().returning(|_| Ok(())); - timer.run(renderer).unwrap(); + timer.run(renderer, CancellationToken::none()).unwrap(); + } + + #[test] + fn run_canceled_aborts() { + let mut clock = MockClock::new(); + let mut renderer = MockRenderer::new(); + let src = CancellationTokenSource::new(); + let token = src.token().clone(); + + clock.expect_now().return_const(Instant::now()); + clock.expect_sleep().once().return_const(()); + renderer.expect_render().once().returning(move |_| { + src.cancel(); + + Ok(()) + }); + let timer = Executor::new(clock, Duration::from_secs(10), Duration::from_secs(1)); + + timer.run(renderer, &token).unwrap(); } } diff --git a/src/main.rs b/src/main.rs index bc40287..942e5ef 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,22 +3,23 @@ pub mod pattern; pub mod transform; mod error; +mod exec; mod printer; mod renderer; mod term; -mod timer; mod vec; pub use error::*; +pub use exec::*; pub use printer::*; pub use renderer::*; pub use term::*; -pub use timer::*; pub use vec::*; use crate::convert::*; use crate::pattern::*; use crate::transform::*; +use cancellation::CancellationTokenSource; use clap::builder::NonEmptyStringValueParser; use clap::{value_parser, Parser, ValueEnum}; use crossterm::style::Color; @@ -267,9 +268,15 @@ fn main() -> Result<(), Error> { let renderer = RendererImpl::new(sampler, converter, printer)?; let clock = ClockImpl::new(); - let timer = Timer::new(clock, duration, delay); + let executor = Executor::new(clock, duration, delay); - timer.run(renderer) + let src = CancellationTokenSource::new(); + let token = src.token().clone(); + + ctrlc::set_handler(move || { + src.cancel(); + })?; + executor.run(renderer, &token) } #[cfg(test)]