From 619cd19e91c7a1ae176a706f0e1f47887cd7a1ec Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Thu, 11 Nov 2021 04:26:36 -0500 Subject: avoid unnecessary rerenders and focus changes --- src/action.rs | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/history.rs | 12 ++++---- src/main.rs | 4 ++- src/readline.rs | 8 ++--- src/state.rs | 17 ++++------- 5 files changed, 110 insertions(+), 23 deletions(-) create mode 100644 src/action.rs (limited to 'src') diff --git a/src/action.rs b/src/action.rs new file mode 100644 index 0000000..2cd54b7 --- /dev/null +++ b/src/action.rs @@ -0,0 +1,92 @@ +#[derive(Debug)] +pub enum Action { + Render, + Run(String), + UpdateFocus(crate::state::Focus), +} + +pub struct Debouncer { + pending: async_std::sync::Mutex, + cvar: async_std::sync::Condvar, +} + +impl Debouncer { + pub async fn recv(&self) -> Option { + let mut pending = self + .cvar + .wait_until(self.pending.lock().await, |pending| { + pending.has_event() + }) + .await; + pending.get_event() + } + + async fn send(&self, action: Option) { + let mut pending = self.pending.lock().await; + pending.new_event(&action); + self.cvar.notify_one(); + } +} + +#[derive(Default)] +struct Pending { + render: bool, + run: std::collections::VecDeque, + focus: Option, + done: bool, +} + +impl Pending { + fn new() -> Self { + Self::default() + } + + fn has_event(&self) -> bool { + self.render || !self.run.is_empty() || self.focus.is_some() + } + + fn get_event(&mut self) -> Option { + if !self.run.is_empty() { + return Some(Action::Run(self.run.pop_front().unwrap())); + } + if self.focus.is_some() { + return Some(Action::UpdateFocus(self.focus.take().unwrap())); + } + if self.render { + self.render = false; + return Some(Action::Render); + } + if self.done { + return None; + } + unreachable!() + } + + fn new_event(&mut self, action: &Option) { + match action { + Some(Action::Run(cmd)) => self.run.push_back(cmd.to_string()), + Some(Action::UpdateFocus(focus)) => self.focus = Some(*focus), + Some(Action::Render) => self.render = true, + None => self.done = true, + } + } +} + +pub fn debounce( + input: async_std::channel::Receiver, +) -> async_std::sync::Arc { + let debouncer = std::sync::Arc::new(Debouncer { + pending: async_std::sync::Mutex::new(Pending::new()), + cvar: async_std::sync::Condvar::new(), + }); + { + let debouncer = std::sync::Arc::clone(&debouncer); + async_std::task::spawn(async move { + while let Ok(action) = input.recv().await { + debouncer.send(Some(action)).await; + } + debouncer.send(None).await; + }); + } + debouncer +} diff --git a/src/history.rs b/src/history.rs index 58de34c..e5ad161 100644 --- a/src/history.rs +++ b/src/history.rs @@ -4,12 +4,12 @@ use textmode::Textmode as _; pub struct History { entries: Vec>, - action: async_std::channel::Sender, + action: async_std::channel::Sender, } impl History { pub fn new( - action: async_std::channel::Sender, + action: async_std::channel::Sender, ) -> Self { Self { entries: vec![], @@ -58,7 +58,7 @@ impl History { } task_entry.lock_arc().await.running = false; task_action - .send(crate::state::Action::UpdateFocus( + .send(crate::action::Action::UpdateFocus( crate::state::Focus::Readline, )) .await @@ -67,7 +67,7 @@ impl History { } } task_action - .send(crate::state::Action::Render) + .send(crate::action::Action::Render) .await .unwrap(); } @@ -87,7 +87,7 @@ impl History { }); self.entries.push(entry); self.action - .send(crate::state::Action::UpdateFocus( + .send(crate::action::Action::UpdateFocus( crate::state::Focus::History(self.entries.len() - 1), )) .await @@ -108,7 +108,7 @@ impl History { } textmode::Key::Ctrl(b'z') => { self.action - .send(crate::state::Action::UpdateFocus( + .send(crate::action::Action::UpdateFocus( crate::state::Focus::Readline, )) .await diff --git a/src/main.rs b/src/main.rs index fe259fa..a0e4bd0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ #![allow(clippy::missing_const_for_fn)] #![allow(clippy::unused_self)] +mod action; mod history; mod readline; mod state; @@ -27,7 +28,8 @@ async fn async_main() -> anyhow::Result<()> { { let state = async_std::sync::Arc::clone(&state); async_std::task::spawn(async move { - while let Ok(action) = action_r.recv().await { + let debouncer = crate::action::debounce(action_r); + while let Some(action) = debouncer.recv().await { state.lock_arc().await.handle_action(action).await; } }); diff --git a/src/readline.rs b/src/readline.rs index 8ff001c..cc6c776 100644 --- a/src/readline.rs +++ b/src/readline.rs @@ -3,12 +3,12 @@ use textmode::Textmode as _; pub struct Readline { prompt: String, input_line: String, - action: async_std::channel::Sender, + action: async_std::channel::Sender, } impl Readline { pub fn new( - action: async_std::channel::Sender, + action: async_std::channel::Sender, ) -> Self { Self { prompt: "$ ".into(), @@ -29,7 +29,7 @@ impl Readline { } textmode::Key::Ctrl(b'm') => { self.action - .send(crate::state::Action::Run(self.input())) + .send(crate::action::Action::Run(self.input())) .await .unwrap(); self.clear_input(); @@ -38,7 +38,7 @@ impl Readline { _ => {} } self.action - .send(crate::state::Action::Render) + .send(crate::action::Action::Render) .await .unwrap(); false diff --git a/src/state.rs b/src/state.rs index 6051329..5a1e1bd 100644 --- a/src/state.rs +++ b/src/state.rs @@ -9,7 +9,7 @@ pub struct State { impl State { pub fn new( - actions: async_std::channel::Sender, + actions: async_std::channel::Sender, output: textmode::Output, ) -> Self { let readline = crate::readline::Readline::new(actions.clone()); @@ -37,15 +37,15 @@ impl State { Ok(()) } - pub async fn handle_action(&mut self, action: Action) { + pub async fn handle_action(&mut self, action: crate::action::Action) { match action { - Action::Render => { + crate::action::Action::Render => { self.render().await.unwrap(); } - Action::Run(ref cmd) => { + crate::action::Action::Run(ref cmd) => { self.history.run(cmd).await.unwrap(); } - Action::UpdateFocus(new_focus) => { + crate::action::Action::UpdateFocus(new_focus) => { self.focus = new_focus; self.render().await.unwrap(); } @@ -65,10 +65,3 @@ pub enum Focus { Readline, History(usize), } - -#[derive(Debug)] -pub enum Action { - Render, - Run(String), - UpdateFocus(Focus), -} -- cgit v1.2.3-54-g00ecf