Remove reader thread in AsyncReader (#309)

This commit is contained in:
John-John Tedro 2019-11-08 06:53:10 +01:00 committed by Timon
parent 053a027ee6
commit 9690e5bc38
6 changed files with 56 additions and 106 deletions

View File

@ -1,3 +1,7 @@
# Unreleased
- Remove thread from AsyncReader on Windows ([PR #309](https://github.com/crossterm-rs/crossterm/pull/309))
# Version 0.13.2 # Version 0.13.2
- New `input::stop_reading_thread()` function - New `input::stop_reading_thread()` function

View File

@ -26,7 +26,7 @@ serde = { version = "1.0.0", features = ["derive"], optional = true }
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
winapi = "0.3.8" winapi = "0.3.8"
crossterm_winapi = "0.3.0" crossterm_winapi = { version = "0.4.0" }
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
libc = "0.2.51" libc = "0.2.51"

View File

@ -7,7 +7,6 @@ use crossterm_winapi::{is_true, Coord, Handle, HandleType, ScreenBuffer};
use winapi::{ use winapi::{
shared::minwindef::{FALSE, TRUE}, shared::minwindef::{FALSE, TRUE},
um::wincon::{SetConsoleCursorInfo, SetConsoleCursorPosition, CONSOLE_CURSOR_INFO, COORD}, um::wincon::{SetConsoleCursorInfo, SetConsoleCursorPosition, CONSOLE_CURSOR_INFO, COORD},
um::winnt::HANDLE,
}; };
use lazy_static::lazy_static; use lazy_static::lazy_static;
@ -22,7 +21,7 @@ lazy_static! {
/// ///
/// The top left cell is represented `0,0`. /// The top left cell is represented `0,0`.
pub fn position() -> Result<(u16, u16)> { pub fn position() -> Result<(u16, u16)> {
let cursor = ScreenBufferCursor::new()?; let cursor = ScreenBufferCursor::output()?;
Ok(cursor.position()?.into()) Ok(cursor.position()?.into())
} }
@ -31,7 +30,7 @@ pub(crate) fn show_cursor(show_cursor: bool) -> Result<()> {
} }
pub(crate) fn move_to(column: u16, row: u16) -> Result<()> { pub(crate) fn move_to(column: u16, row: u16) -> Result<()> {
let cursor = ScreenBufferCursor::new()?; let cursor = ScreenBufferCursor::output()?;
cursor.move_to(column as i16, row as i16)?; cursor.move_to(column as i16, row as i16)?;
Ok(()) Ok(())
} }
@ -61,12 +60,12 @@ pub(crate) fn move_left(count: u16) -> Result<()> {
} }
pub(crate) fn save_position() -> Result<()> { pub(crate) fn save_position() -> Result<()> {
ScreenBufferCursor::new()?.save_position()?; ScreenBufferCursor::output()?.save_position()?;
Ok(()) Ok(())
} }
pub(crate) fn restore_position() -> Result<()> { pub(crate) fn restore_position() -> Result<()> {
ScreenBufferCursor::new()?.restore_position()?; ScreenBufferCursor::output()?.restore_position()?;
Ok(()) Ok(())
} }
@ -76,7 +75,7 @@ struct ScreenBufferCursor {
} }
impl ScreenBufferCursor { impl ScreenBufferCursor {
fn new() -> Result<ScreenBufferCursor> { fn output() -> Result<ScreenBufferCursor> {
Ok(ScreenBufferCursor { Ok(ScreenBufferCursor {
screen_buffer: ScreenBuffer::from(Handle::new(HandleType::CurrentOutputHandle)?), screen_buffer: ScreenBuffer::from(Handle::new(HandleType::CurrentOutputHandle)?),
}) })
@ -163,14 +162,6 @@ impl From<Handle> for ScreenBufferCursor {
} }
} }
impl From<HANDLE> for ScreenBufferCursor {
fn from(handle: HANDLE) -> Self {
ScreenBufferCursor {
screen_buffer: ScreenBuffer::from(handle),
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{ use super::{

View File

@ -1,15 +1,6 @@
//! This is a WINDOWS specific implementation for input related action. //! This is a WINDOWS specific implementation for input related action.
use std::{ use std::{char, collections::VecDeque, io, sync::Mutex};
char, io,
sync::{
atomic::{AtomicBool, Ordering},
mpsc::{self, Receiver, Sender},
Arc, Mutex,
},
thread,
time::Duration,
};
use crossterm_winapi::{ use crossterm_winapi::{
ButtonState, Console, ConsoleMode, EventFlags, Handle, InputEventType, KeyEventRecord, ButtonState, Console, ConsoleMode, EventFlags, Handle, InputEventType, KeyEventRecord,
@ -85,41 +76,15 @@ impl Input for WindowsInput {
} }
fn read_async(&self) -> AsyncReader { fn read_async(&self) -> AsyncReader {
AsyncReader::new(Box::new(move |event_tx, cancellation_token| loop { let handle = Handle::current_in_handle().expect("failed to create console input handle");
for i in read_input_events().unwrap().1 { let console = Console::from(handle);
if event_tx.send(i).is_err() { AsyncReader::new(console, None)
return;
}
}
if cancellation_token.load(Ordering::SeqCst) {
return;
}
thread::sleep(Duration::from_millis(1));
}))
} }
fn read_until_async(&self, delimiter: u8) -> AsyncReader { fn read_until_async(&self, delimiter: u8) -> AsyncReader {
AsyncReader::new(Box::new(move |event_tx, cancellation_token| loop { let handle = Handle::current_in_handle().expect("failed to create console input handle");
for event in read_input_events().unwrap().1 { let console = Console::from(handle);
if let InputEvent::Keyboard(KeyEvent::Char(key)) = event { AsyncReader::new(console, Some(delimiter))
if (key as u8) == delimiter {
return;
}
}
if cancellation_token.load(Ordering::SeqCst) {
return;
} else {
if event_tx.send(event).is_err() {
return;
}
}
thread::sleep(Duration::from_millis(1));
}
}))
} }
fn read_sync(&self) -> SyncReader { fn read_sync(&self) -> SyncReader {
@ -271,9 +236,9 @@ impl Iterator for SyncReader {
/// } // `reader` dropped <- thread cleaned up, `_raw` dropped <- raw mode disabled /// } // `reader` dropped <- thread cleaned up, `_raw` dropped <- raw mode disabled
/// ``` /// ```
pub struct AsyncReader { pub struct AsyncReader {
event_rx: Receiver<InputEvent>, console: Console,
shutdown: Arc<AtomicBool>, buffer: VecDeque<InputEvent>,
thread: Option<thread::JoinHandle<()>>, delimiter: Option<u8>,
} }
impl AsyncReader { impl AsyncReader {
@ -284,44 +249,15 @@ impl AsyncReader {
/// ///
/// * A thread is spawned to read the input. /// * A thread is spawned to read the input.
/// * The reading thread is cleaned up when you drop the `AsyncReader`. /// * The reading thread is cleaned up when you drop the `AsyncReader`.
pub fn new(function: Box<dyn Fn(&Sender<InputEvent>, &Arc<AtomicBool>) + Send>) -> AsyncReader { pub fn new(console: Console, delimiter: Option<u8>) -> AsyncReader {
let shutdown_handle = Arc::new(AtomicBool::new(false));
let (event_tx, event_rx) = mpsc::channel();
let thread_shutdown = shutdown_handle.clone();
let thread = thread::spawn(move || {
function(&event_tx, &thread_shutdown);
});
AsyncReader { AsyncReader {
event_rx, console,
shutdown: shutdown_handle, buffer: VecDeque::new(),
thread: Some(thread), delimiter,
} }
} }
// TODO If we we keep the Drop semantics, do we really need this in the public API? It's useless as pub fn stop(&mut self) {}
// there's no `start`, etc.
/// Stops the input reader.
///
/// # Notes
///
/// * The reading thread is cleaned up.
/// * You don't need to call this method, because it will be automatically called when the
/// `AsyncReader` is dropped.
pub fn stop(&mut self) {
if let Some(thread) = self.thread.take() {
self.shutdown.store(true, Ordering::SeqCst);
thread.join().expect("failed to join background thread");
}
}
}
impl Drop for AsyncReader {
fn drop(&mut self) {
self.stop();
}
} }
impl Iterator for AsyncReader { impl Iterator for AsyncReader {
@ -332,8 +268,31 @@ impl Iterator for AsyncReader {
/// `None` doesn't mean that the iteration is finished. See the /// `None` doesn't mean that the iteration is finished. See the
/// [`AsyncReader`](struct.AsyncReader.html) documentation for more information. /// [`AsyncReader`](struct.AsyncReader.html) documentation for more information.
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
let mut iterator = self.event_rx.try_iter(); loop {
iterator.next() if self.buffer.is_empty() {
let (_, events) = read_input_events(&self.console).expect("read failed");
if events.is_empty() {
return None;
}
self.buffer.extend(events);
}
if let Some(delimiter) = self.delimiter {
while let Some(e) = self.buffer.pop_front() {
if let InputEvent::Keyboard(KeyEvent::Char(key)) = e {
if (key as u8) == delimiter {
return Some(e);
}
}
}
continue;
}
return self.buffer.pop_front();
}
} }
} }
@ -361,9 +320,7 @@ fn read_single_event() -> Result<Option<InputEvent>> {
} }
/// partially inspired by: https://github.com/retep998/wio-rs/blob/master/src/console.rs#L130 /// partially inspired by: https://github.com/retep998/wio-rs/blob/master/src/console.rs#L130
fn read_input_events() -> Result<(u32, Vec<InputEvent>)> { fn read_input_events(console: &Console) -> Result<(u32, Vec<InputEvent>)> {
let console = Console::from(Handle::current_in_handle()?);
let result = console.read_console_input()?; let result = console.read_console_input()?;
let mut input_events = Vec::with_capacity(result.0 as usize); let mut input_events = Vec::with_capacity(result.0 as usize);

View File

@ -40,8 +40,7 @@ pub(crate) fn set_foreground_color(fg_color: Color) -> Result<()> {
color = color | wincon::BACKGROUND_INTENSITY as u16; color = color | wincon::BACKGROUND_INTENSITY as u16;
} }
Console::from(**screen_buffer.handle()).set_text_attribute(color)?; Console::from(screen_buffer.handle().clone()).set_text_attribute(color)?;
Ok(()) Ok(())
} }
@ -66,8 +65,7 @@ pub(crate) fn set_background_color(bg_color: Color) -> Result<()> {
color = color | wincon::FOREGROUND_INTENSITY as u16; color = color | wincon::FOREGROUND_INTENSITY as u16;
} }
Console::from(**screen_buffer.handle()).set_text_attribute(color)?; Console::from(screen_buffer.handle().clone()).set_text_attribute(color)?;
Ok(()) Ok(())
} }

View File

@ -53,7 +53,7 @@ pub(crate) fn scroll_up(row_count: u16) -> Result<()> {
window.top -= count; // move top down window.top -= count; // move top down
window.bottom -= count; // move bottom down window.bottom -= count; // move bottom down
Console::new()?.set_console_info(true, window)?; Console::output()?.set_console_info(true, window)?;
} }
Ok(()) Ok(())
} }
@ -70,7 +70,7 @@ pub(crate) fn scroll_down(row_count: u16) -> Result<()> {
window.top += count; // move top down window.top += count; // move top down
window.bottom += count; // move bottom down window.bottom += count; // move bottom down
Console::new()?.set_console_info(true, window)?; Console::output()?.set_console_info(true, window)?;
} }
Ok(()) Ok(())
} }
@ -91,7 +91,7 @@ pub(crate) fn set_size(width: u16, height: u16) -> Result<()> {
// get the position of the current console window // get the position of the current console window
let screen_buffer = ScreenBuffer::current()?; let screen_buffer = ScreenBuffer::current()?;
let console = Console::from(**screen_buffer.handle()); let console = Console::from(screen_buffer.handle().clone());
let csbi = screen_buffer.info()?; let csbi = screen_buffer.info()?;
let current_size = csbi.buffer_size(); let current_size = csbi.buffer_size();