allow input polling to be paused (#114)

This commit is contained in:
Stephan Dilly 2020-06-22 09:13:37 +02:00
parent 598b98b2bd
commit 4bcfa61087
3 changed files with 149 additions and 92 deletions

View file

@ -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(())

View file

@ -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<bool> {
}
fn select_event(
rx_input: &Receiver<Vec<QueueEvent>>,
rx_input: &Receiver<InputEvent>,
rx_git: &Receiver<AsyncNotification>,
rx_ticker: &Receiver<Instant>,
rx_spinner: &Receiver<Instant>,
@ -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))),

View file

@ -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<Vec<QueueEvent>> {
let (tx, rx) = unbounded();
pub struct Input {
desired_state: Arc<AtomicBool>,
receiver: Receiver<InputEvent>,
}
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<InputEvent> {
self.receiver.clone()
}
///
fn poll(dur: Duration) -> anyhow::Result<Option<Event>> {
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<Option<Event>> {
if event::poll(dur)? {
Ok(Some(event::read()?))
} else {
Ok(None)
}
}
}