use condvar/mutex to not busywait when suspending input polling (closes #153)

This commit is contained in:
Stephan Dilly 2020-06-30 09:33:22 +02:00
parent a4de701415
commit 923bed9abf
3 changed files with 77 additions and 30 deletions

View file

@ -1,3 +1,4 @@
use crate::notify_mutex::NotifyableMutex;
use crossbeam_channel::{unbounded, Receiver};
use crossterm::event::{self, Event};
use std::{
@ -27,7 +28,7 @@ pub enum InputEvent {
///
pub struct Input {
desired_state: Arc<AtomicBool>,
desired_state: Arc<NotifyableMutex<bool>>,
current_state: Arc<AtomicBool>,
receiver: Receiver<InputEvent>,
}
@ -37,40 +38,39 @@ impl Input {
pub fn new() -> Self {
let (tx, rx) = unbounded();
let desired_state = Arc::new(AtomicBool::new(true));
let desired_state = Arc::new(NotifyableMutex::new(true));
let current_state = Arc::new(AtomicBool::new(true));
let arc_desired = Arc::clone(&desired_state);
let arc_current = Arc::clone(&current_state);
thread::spawn(move || {
loop {
//TODO: use condvar to not busy wait
if arc_desired.load(Ordering::Relaxed) {
if !arc_current.load(Ordering::Relaxed) {
tx.send(InputEvent::State(
InputState::Polling,
))
.expect("send failed");
}
arc_current.store(true, Ordering::Relaxed);
thread::spawn(move || loop {
if arc_desired.get() {
if !arc_current.load(Ordering::Relaxed) {
log::info!("input polling resumed");
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 arc_current.load(Ordering::Relaxed) {
tx.send(InputEvent::State(
InputState::Paused,
))
.expect("send failed");
}
arc_current.store(false, Ordering::Relaxed);
tx.send(InputEvent::State(InputState::Polling))
.expect("send state failed");
}
arc_current.store(true, Ordering::Relaxed);
if let Some(e) = Self::poll(POLL_DURATION)
.expect("failed to pull events.")
{
tx.send(InputEvent::Input(e))
.expect("send input failed");
}
} else {
if arc_current.load(Ordering::Relaxed) {
log::info!("input polling suspended");
tx.send(InputEvent::State(InputState::Paused))
.expect("send state failed");
}
arc_current.store(false, Ordering::Relaxed);
arc_desired.wait(true);
}
});
@ -88,12 +88,16 @@ impl Input {
///
pub fn set_polling(&mut self, enabled: bool) {
self.desired_state.store(enabled, Ordering::Relaxed);
self.desired_state.set_and_notify(enabled)
}
fn shall_poll(&self) -> bool {
self.desired_state.get()
}
///
pub fn is_state_changing(&self) -> bool {
self.desired_state.load(Ordering::Relaxed)
self.shall_poll()
!= self.current_state.load(Ordering::Relaxed)
}

View file

@ -14,6 +14,7 @@ mod cmdbar;
mod components;
mod input;
mod keys;
mod notify_mutex;
mod queue;
mod spinner;
mod strings;

42
src/notify_mutex.rs Normal file
View file

@ -0,0 +1,42 @@
use std::sync::{Arc, Condvar, Mutex};
///
#[derive(Clone, Debug)]
pub struct NotifyableMutex<T> {
data: Arc<(Mutex<T>, Condvar)>,
}
impl<T> NotifyableMutex<T> {
///
pub fn new(start_value: T) -> Self {
Self {
data: Arc::new((Mutex::new(start_value), Condvar::new())),
}
}
///
#[allow(clippy::needless_pass_by_value)]
pub fn wait(&self, condition: T)
where
T: PartialEq,
{
let mut data = self.data.0.lock().expect("lock err");
while *data != condition {
data = self.data.1.wait(data).expect("wait err");
}
}
///
pub fn set_and_notify(&self, value: T) {
*self.data.0.lock().expect("set err") = value;
self.data.1.notify_one();
}
///
pub fn get(&self) -> T
where
T: Copy,
{
*self.data.0.lock().expect("get err")
}
}