From 4bcfa610870ec6dd966fc7018452ecd0204fc3aa Mon Sep 17 00:00:00 2001 From: Stephan Dilly Date: Mon, 22 Jun 2020 09:13:37 +0200 Subject: [PATCH] allow input polling to be paused (#114) --- src/app.rs | 95 ++++++++++++++++++++-------------------- src/main.rs | 24 +++++++++-- src/poll.rs | 122 ++++++++++++++++++++++++++++++++++------------------ 3 files changed, 149 insertions(+), 92 deletions(-) diff --git a/src/app.rs b/src/app.rs index 15f6ee07..2988466d 100644 --- a/src/app.rs +++ b/src/app.rs @@ -8,6 +8,7 @@ use crate::{ StashMsgComponent, }, keys, + poll::InputEvent, queue::{Action, InternalEvent, NeedsUpdate, Queue}, strings, tabs::{Revlog, StashList, Stashing, Status}, @@ -117,61 +118,63 @@ impl App { } /// - pub fn event(&mut self, ev: Event) -> Result<()> { + pub fn event(&mut self, ev: InputEvent) -> Result<()> { log::trace!("event: {:?}", ev); - if self.check_quit_key(ev) { - return Ok(()); - } + if let InputEvent::Input(ev) = ev { + if self.check_quit_key(ev) { + return Ok(()); + } - let mut flags = NeedsUpdate::empty(); + let mut flags = NeedsUpdate::empty(); - if event_pump(ev, self.components_mut().as_mut_slice())? { - flags.insert(NeedsUpdate::COMMANDS); - } else if let Event::Key(k) = ev { - let new_flags = match k { - keys::TAB_TOGGLE => { - self.toggle_tabs(false)?; - NeedsUpdate::COMMANDS - } - keys::TAB_TOGGLE_REVERSE - | keys::TAB_TOGGLE_REVERSE_WINDOWS => { - self.toggle_tabs(true)?; - NeedsUpdate::COMMANDS - } - keys::TAB_1 - | keys::TAB_2 - | keys::TAB_3 - | keys::TAB_4 => { - self.switch_tab(k)?; - NeedsUpdate::COMMANDS - } + if event_pump(ev, self.components_mut().as_mut_slice())? { + flags.insert(NeedsUpdate::COMMANDS); + } else if let Event::Key(k) = ev { + let new_flags = match k { + keys::TAB_TOGGLE => { + self.toggle_tabs(false)?; + NeedsUpdate::COMMANDS + } + keys::TAB_TOGGLE_REVERSE + | keys::TAB_TOGGLE_REVERSE_WINDOWS => { + self.toggle_tabs(true)?; + NeedsUpdate::COMMANDS + } + keys::TAB_1 + | keys::TAB_2 + | keys::TAB_3 + | keys::TAB_4 => { + self.switch_tab(k)?; + NeedsUpdate::COMMANDS + } - keys::CMD_BAR_TOGGLE => { - self.cmdbar.toggle_more(); - NeedsUpdate::empty() - } + keys::CMD_BAR_TOGGLE => { + self.cmdbar.toggle_more(); + NeedsUpdate::empty() + } - _ => NeedsUpdate::empty(), - }; + _ => NeedsUpdate::empty(), + }; + flags.insert(new_flags); + } + + let new_flags = self.process_queue()?; flags.insert(new_flags); - } - let new_flags = self.process_queue()?; - flags.insert(new_flags); - - if flags.contains(NeedsUpdate::ALL) { - self.update()?; - } - //TODO: make this a queue event? - //NOTE: set when any tree component changed selection - if flags.contains(NeedsUpdate::DIFF) { - self.status_tab.update_diff()?; - self.inspect_commit_popup.update_diff()?; - } - if flags.contains(NeedsUpdate::COMMANDS) { - self.update_commands(); + if flags.contains(NeedsUpdate::ALL) { + self.update()?; + } + //TODO: make this a queue event? + //NOTE: set when any tree component changed selection + if flags.contains(NeedsUpdate::DIFF) { + self.status_tab.update_diff()?; + self.inspect_commit_popup.update_diff()?; + } + if flags.contains(NeedsUpdate::COMMANDS) { + self.update_commands(); + } } Ok(()) diff --git a/src/main.rs b/src/main.rs index c4467194..b0480cec 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,7 +21,7 @@ mod tabs; mod ui; mod version; -use crate::{app::App, poll::QueueEvent}; +use crate::app::App; use anyhow::{anyhow, Result}; use asyncgit::AsyncNotification; use backtrace::Backtrace; @@ -37,6 +37,7 @@ use crossterm::{ }, ExecutableCommand, }; +use poll::{Input, InputEvent}; use scopeguard::defer; use scopetime::scope_time; use simplelog::{Config, LevelFilter, WriteLogger}; @@ -58,6 +59,15 @@ use tui::{ static TICK_INTERVAL: Duration = Duration::from_secs(5); static SPINNER_INTERVAL: Duration = Duration::from_millis(50); +/// +#[derive(Clone, Copy)] +pub enum QueueEvent { + Tick, + SpinnerUpdate, + GitEvent(AsyncNotification), + InputEvent(InputEvent), +} + fn main() -> Result<()> { process_cmdline()?; @@ -82,7 +92,8 @@ fn main() -> Result<()> { let mut app = App::new(&tx_git); - let rx_input = poll::start_polling_thread(); + let mut input = Input::new(); + let rx_input = input.receiver(); let ticker = tick(TICK_INTERVAL); let spinner_ticker = tick(SPINNER_INTERVAL); @@ -116,6 +127,9 @@ fn main() -> Result<()> { } } + //TODO: disable input polling while external editor open + input.set_polling(!app.any_work_pending()); + if needs_draw { draw(&mut terminal, &mut app)?; } @@ -160,7 +174,7 @@ fn valid_path() -> Result { } fn select_event( - rx_input: &Receiver>, + rx_input: &Receiver, rx_git: &Receiver, rx_ticker: &Receiver, rx_spinner: &Receiver, @@ -178,7 +192,9 @@ fn select_event( let index = oper.index(); match index { - 0 => oper.recv(rx_input).map(|inputs| events.extend(inputs)), + 0 => oper + .recv(rx_input) + .map(|input| events.push(QueueEvent::InputEvent(input))), 1 => oper .recv(rx_git) .map(|ev| events.push(QueueEvent::GitEvent(ev))), diff --git a/src/poll.rs b/src/poll.rs index 9956c0ae..79e65673 100644 --- a/src/poll.rs +++ b/src/poll.rs @@ -1,59 +1,97 @@ -use asyncgit::AsyncNotification; use crossbeam_channel::{unbounded, Receiver}; use crossterm::event::{self, Event}; -use std::time::{Duration, Instant}; +use std::{ + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, + thread, + time::Duration, +}; + +static POLL_DURATION: Duration = Duration::from_millis(1000); /// -#[derive(Clone, Copy)] -pub enum QueueEvent { - Tick, - SpinnerUpdate, - GitEvent(AsyncNotification), - InputEvent(Event), +#[derive(Clone, Copy, Debug)] +pub enum InputState { + Paused, + Polling, } -static MAX_POLL_DURATION: Duration = Duration::from_secs(2); -static MIN_POLL_DURATION: Duration = Duration::from_millis(5); -static MAX_BATCHING_DURATION: Duration = Duration::from_millis(25); +/// +#[derive(Clone, Copy, Debug)] +pub enum InputEvent { + Input(Event), + State(InputState), +} /// -pub fn start_polling_thread() -> Receiver> { - let (tx, rx) = unbounded(); +pub struct Input { + desired_state: Arc, + receiver: Receiver, +} - rayon_core::spawn(move || { - let mut last_send = Instant::now(); - let mut batch = Vec::new(); +impl Input { + /// + pub fn new() -> Self { + let (tx, rx) = unbounded(); - loop { - let timeout = if batch.is_empty() { - MAX_POLL_DURATION - } else { - MIN_POLL_DURATION - }; - if let Some(e) = - poll(timeout).expect("failed to pull events.") - { - batch.push(QueueEvent::InputEvent(e)); + let desired_state = Arc::new(AtomicBool::new(true)); + + let arc_desired = Arc::clone(&desired_state); + + thread::spawn(move || { + let mut current_state = true; + loop { + //TODO: use condvar to not busy wait + if arc_desired.load(Ordering::Relaxed) { + if !current_state { + tx.send(InputEvent::State( + InputState::Polling, + )) + .expect("send failed"); + } + current_state = true; + + if let Some(e) = Self::poll(POLL_DURATION) + .expect("failed to pull events.") + { + tx.send(InputEvent::Input(e)) + .expect("send input event failed"); + } + } else { + if current_state { + tx.send(InputEvent::State( + InputState::Paused, + )) + .expect("send failed"); + } + current_state = false; + } } + }); - if !batch.is_empty() - && last_send.elapsed() > MAX_BATCHING_DURATION - { - tx.send(batch).expect("send input event failed"); - batch = Vec::new(); - last_send = Instant::now(); - } + Self { + receiver: rx, + desired_state, } - }); + } - rx -} + /// + pub fn receiver(&self) -> Receiver { + self.receiver.clone() + } -/// -fn poll(dur: Duration) -> anyhow::Result> { - if event::poll(dur)? { - Ok(Some(event::read()?)) - } else { - Ok(None) + /// + pub fn set_polling(&mut self, enabled: bool) { + self.desired_state.store(enabled, Ordering::Relaxed); + } + + fn poll(dur: Duration) -> anyhow::Result> { + if event::poll(dur)? { + Ok(Some(event::read()?)) + } else { + Ok(None) + } } }