Refactor input system

* Moves from chan to crossbeam-channel
* Moves from chan_signal to signal-hook
This commit is contained in:
Alexandre Bury 2018-07-08 12:48:37 -07:00
parent 4c70b79663
commit 6135b0df79
10 changed files with 316 additions and 210 deletions

View File

@ -27,8 +27,8 @@ unicode-segmentation = "1.0"
unicode-width = "0.1" unicode-width = "0.1"
xi-unicode = "0.1.0" xi-unicode = "0.1.0"
libc = "0.2" libc = "0.2"
chan = "0.1"
term_size = { version = "0.3.1", optional = true } term_size = { version = "0.3.1", optional = true }
crossbeam-channel = "0.2.1"
[dependencies.num] [dependencies.num]
default-features = false default-features = false
@ -61,8 +61,8 @@ version = "0.1.0"
optional = true optional = true
version = "1.5.0" version = "1.5.0"
[target.'cfg(unix)'.dependencies.chan-signal] [target.'cfg(unix)'.dependencies.signal-hook]
version = "0.3" version = "0.1"
[dev-dependencies] [dev-dependencies]
rand = "0.5" rand = "0.5"

View File

@ -5,19 +5,22 @@
extern crate bear_lib_terminal; extern crate bear_lib_terminal;
use std::collections::HashSet;
use std::time::{Duration, Instant};
use self::bear_lib_terminal::geometry::Size; use self::bear_lib_terminal::geometry::Size;
use self::bear_lib_terminal::terminal::{ use self::bear_lib_terminal::terminal::{
self, state, Event as BltEvent, KeyCode, self, state, Event as BltEvent, KeyCode,
}; };
use self::bear_lib_terminal::Color as BltColor; use self::bear_lib_terminal::Color as BltColor;
use crossbeam_channel::{Receiver, Sender};
use backend; use backend;
use chan;
use event::{Event, Key, MouseButton, MouseEvent}; use event::{Event, Key, MouseButton, MouseEvent};
use std::collections::HashSet;
use std::time::{Duration, Instant};
use theme::{BaseColor, Color, ColorPair, Effect}; use theme::{BaseColor, Color, ColorPair, Effect};
use vec::Vec2; use vec::Vec2;
enum ColorRole { enum ColorRole {
Foreground, Foreground,
Background, Background,
@ -306,17 +309,25 @@ impl backend::Backend for Backend {
} }
fn prepare_input( fn prepare_input(
&mut self, event_sink: &chan::Sender<Event>, timeout: Duration, &mut self, event_sink: &Sender<Option<Event>>, input_request: backend::InputRequest,
) { ) {
// Wait for up to `timeout_ms`. match input_request {
let start = Instant::now(); backend::InputRequest::Peek => event_sink.send(self.parse_next()),
while start.elapsed() < timeout { backend::InputRequest::Block => {
if let Some(event) = self.parse_next() { let timeout = ::std::time::Duration::from_millis(30);
event_sink.send(event); // Wait for up to `timeout_ms`.
return; let start = Instant::now();
while start.elapsed() < timeout {
if let Some(event) = self.parse_next() {
event_sink.send(Some(event));
return;
}
}
event_sink.send(Some(Event::Refresh));
} }
} }
event_sink.send(Event::Refresh);
} }
} }

View File

@ -10,9 +10,9 @@ use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc; use std::sync::Arc;
use std::thread; use std::thread;
use chan; use crossbeam_channel::{Receiver, Sender};
use chan_signal;
use libc; use libc;
use signal_hook::iterator::Signals;
use backend; use backend;
use event::{Event, Key, MouseButton, MouseEvent}; use event::{Event, Key, MouseButton, MouseEvent};
@ -32,16 +32,19 @@ pub struct Backend {
// This is set by the SIGWINCH-triggered thread. // This is set by the SIGWINCH-triggered thread.
// When TRUE, we should tell ncurses about the new terminal size. // When TRUE, we should tell ncurses about the new terminal size.
needs_resize: Arc<AtomicBool>, needs_resize: Arc<AtomicBool>,
// The signal hook to receive SIGWINCH (window resize)
signals: Option<Signals>,
} }
struct InputParser { struct InputParser {
key_codes: HashMap<i32, Event>, key_codes: HashMap<i32, Event>,
last_mouse_button: Option<MouseButton>, last_mouse_button: Option<MouseButton>,
event_sink: chan::Sender<Event>, event_sink: Sender<Option<Event>>,
} }
impl InputParser { impl InputParser {
fn new(event_sink: chan::Sender<Event>) -> Self { fn new(event_sink: Sender<Option<Event>>) -> Self {
InputParser { InputParser {
key_codes: initialize_keymap(), key_codes: initialize_keymap(),
last_mouse_button: None, last_mouse_button: None,
@ -52,6 +55,17 @@ impl InputParser {
fn parse_next(&mut self) { fn parse_next(&mut self) {
let ch: i32 = ncurses::getch(); let ch: i32 = ncurses::getch();
if ch == 410 {
// Ignore resize events.
self.parse_next();
return;
}
if ch == -1 {
self.event_sink.send(None);
return;
}
// Is it a UTF-8 starting point? // Is it a UTF-8 starting point?
let event = if 32 <= ch && ch <= 255 && ch != 127 { let event = if 32 <= ch && ch <= 255 && ch != 127 {
utf8::read_char(ch as u8, || Some(ncurses::getch() as u8)) utf8::read_char(ch as u8, || Some(ncurses::getch() as u8))
@ -63,7 +77,7 @@ impl InputParser {
} else { } else {
self.parse_ncurses_char(ch) self.parse_ncurses_char(ch)
}; };
self.event_sink.send(event); self.event_sink.send(Some(event));
} }
fn parse_ncurses_char(&mut self, ch: i32) -> Event { fn parse_ncurses_char(&mut self, ch: i32) -> Event {
@ -132,7 +146,7 @@ impl InputParser {
if event.is_none() { if event.is_none() {
event = Some(e); event = Some(e);
} else { } else {
self.event_sink.send(make_event(e)); self.event_sink.send(Some(make_event(e)));
} }
}); });
} }
@ -171,6 +185,9 @@ fn write_to_tty(bytes: &[u8]) -> io::Result<()> {
impl Backend { impl Backend {
pub fn init() -> Box<backend::Backend> { pub fn init() -> Box<backend::Backend> {
let signals = Some(Signals::new(&[libc::SIGWINCH]).unwrap());
// Change the locale. // Change the locale.
// For some reasons it's mandatory to get some UTF-8 support. // For some reasons it's mandatory to get some UTF-8 support.
ncurses::setlocale(ncurses::LcCategory::all, ""); ncurses::setlocale(ncurses::LcCategory::all, "");
@ -212,6 +229,7 @@ impl Backend {
current_style: Cell::new(ColorPair::from_256colors(0, 0)), current_style: Cell::new(ColorPair::from_256colors(0, 0)),
pairs: RefCell::new(HashMap::new()), pairs: RefCell::new(HashMap::new()),
needs_resize: Arc::new(AtomicBool::new(false)), needs_resize: Arc::new(AtomicBool::new(false)),
signals,
}; };
Box::new(c) Box::new(c)
@ -285,48 +303,47 @@ impl backend::Backend for Backend {
} }
fn start_input_thread( fn start_input_thread(
&mut self, event_sink: chan::Sender<Event>, &mut self, event_sink: Sender<Option<Event>>,
stops: chan::Receiver<bool>, input_request: Receiver<backend::InputRequest>,
) { ) {
let resize = chan_signal::notify(&[chan_signal::Signal::WINCH]); let running = Arc::new(AtomicBool::new(true));
let (sender, receiver) = chan::async();
let needs_resize = Arc::clone(&self.needs_resize); let needs_resize = Arc::clone(&self.needs_resize);
// This thread will merge resize and input into event_sink let resize_running = Arc::clone(&running);
let resize_sender = event_sink.clone();
let signals = self.signals.take().unwrap();
thread::spawn(move || { thread::spawn(move || {
loop { // This thread will listen to SIGWINCH events and report them.
chan_select! { while resize_running.load(Ordering::Relaxed) {
resize.recv() => { // We know it will only contain SIGWINCH signals, so no need to check.
// Tell ncurses about the new terminal size. for _ in signals.pending() {
// Well, do the actual resizing later on, in the main thread. // Tell ncurses about the new terminal size.
// Ncurses isn't really thread-safe so calling resize_term() can crash // Well, do the actual resizing later on, in the main thread.
// other calls like clear() or refresh(). // Ncurses isn't really thread-safe so calling resize_term() can crash
needs_resize.store(true, Ordering::Relaxed); // other calls like clear() or refresh().
event_sink.send(Event::WindowResize); needs_resize.store(true, Ordering::Relaxed);
}, resize_sender.send(Some(Event::WindowResize));
receiver.recv() -> event => {
match event {
Some(event) => event_sink.send(event),
None => return,
}
}
} }
} }
}); });
let mut parser = InputParser::new(sender); let mut parser = InputParser::new(event_sink);
// This thread will just fill the input channel
thread::spawn(move || {
loop {
// This sends events to the event sender.
parser.parse_next();
if stops.recv() != Some(false) { // This thread will take input from ncurses for each request.
// If the channel was closed or if `true` was sent, abort. thread::spawn(move || {
break; for req in input_request {
match req {
backend::InputRequest::Peek => {
ncurses::timeout(0);
}
backend::InputRequest::Block => {
ncurses::timeout(-1);
}
} }
parser.parse_next();
} }
running.store(false, Ordering::Relaxed);
}); });
} }

View File

@ -7,16 +7,19 @@ use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc; use std::sync::Arc;
use std::thread; use std::thread;
use chan; use crossbeam_channel::{Sender, Receiver};
#[cfg(unix)] #[cfg(unix)]
use chan_signal; use signal_hook::iterator::Signals;
#[cfg(unix)]
use libc;
use backend; use backend;
use event::{Event, Key, MouseButton, MouseEvent}; use event::{Event, Key, MouseButton, MouseEvent};
use theme::{Color, ColorPair, Effect}; use theme::{Color, ColorPair, Effect};
use vec::Vec2; use vec::Vec2;
use self::super::split_i32; use super::split_i32;
use self::pancurses::mmask_t; use self::pancurses::mmask_t;
pub struct Backend { pub struct Backend {
@ -30,12 +33,16 @@ pub struct Backend {
// This is set by the SIGWINCH-triggered thread. // This is set by the SIGWINCH-triggered thread.
// When TRUE, we should tell ncurses about the new terminal size. // When TRUE, we should tell ncurses about the new terminal size.
needs_resize: Arc<AtomicBool>, needs_resize: Arc<AtomicBool>,
// The signal hook to receive SIGWINCH (window resize)
#[cfg(unix)]
signals: Option<Signals>,
} }
struct InputParser { struct InputParser {
key_codes: HashMap<i32, Event>, key_codes: HashMap<i32, Event>,
last_mouse_button: Option<MouseButton>, last_mouse_button: Option<MouseButton>,
event_sink: chan::Sender<Event>, event_sink: Sender<Option<Event>>,
window: Arc<pancurses::Window>, window: Arc<pancurses::Window>,
} }
@ -47,7 +54,7 @@ unsafe impl Send for InputParser {}
impl InputParser { impl InputParser {
fn new( fn new(
event_sink: chan::Sender<Event>, window: Arc<pancurses::Window>, event_sink: Sender<Option<Event>>, window: Arc<pancurses::Window>,
) -> Self { ) -> Self {
InputParser { InputParser {
key_codes: initialize_keymap(), key_codes: initialize_keymap(),
@ -59,7 +66,8 @@ impl InputParser {
fn parse_next(&mut self) { fn parse_next(&mut self) {
let event = if let Some(ev) = self.window.getch() { let event = if let Some(ev) = self.window.getch() {
match ev {
Some(match ev {
pancurses::Input::Character('\n') => Event::Key(Key::Enter), pancurses::Input::Character('\n') => Event::Key(Key::Enter),
// TODO: wait for a very short delay. If more keys are // TODO: wait for a very short delay. If more keys are
// pipelined, it may be an escape sequence. // pipelined, it may be an escape sequence.
@ -204,9 +212,9 @@ impl InputParser {
pancurses::Input::KeyB2 => Event::Key(Key::NumpadCenter), pancurses::Input::KeyB2 => Event::Key(Key::NumpadCenter),
pancurses::Input::KeyC1 => Event::Refresh, pancurses::Input::KeyC1 => Event::Refresh,
pancurses::Input::KeyC3 => Event::Refresh, pancurses::Input::KeyC3 => Event::Refresh,
} })
} else { } else {
Event::Refresh None
}; };
self.event_sink.send(event); self.event_sink.send(event);
} }
@ -255,7 +263,7 @@ impl InputParser {
if event.is_none() { if event.is_none() {
event = Some(e); event = Some(e);
} else { } else {
self.event_sink.send(make_event(e)); self.event_sink.send(Some(make_event(e)));
} }
}); });
} }
@ -276,8 +284,13 @@ fn find_closest_pair(pair: ColorPair) -> (i16, i16) {
super::find_closest_pair(pair, pancurses::COLORS() as i16) super::find_closest_pair(pair, pancurses::COLORS() as i16)
} }
impl Backend { impl Backend {
pub fn init() -> Box<backend::Backend> { pub fn init() -> Box<backend::Backend> {
#[cfg(unix)]
let signals = Some(Signals::new(&[libc::SIGWINCH]).unwrap());
::std::env::set_var("ESCDELAY", "25"); ::std::env::set_var("ESCDELAY", "25");
let window = pancurses::initscr(); let window = pancurses::initscr();
@ -304,6 +317,7 @@ impl Backend {
pairs: RefCell::new(HashMap::new()), pairs: RefCell::new(HashMap::new()),
window: Arc::new(window), window: Arc::new(window),
needs_resize: Arc::new(AtomicBool::new(false)), needs_resize: Arc::new(AtomicBool::new(false)),
signals,
}; };
Box::new(c) Box::new(c)
@ -365,19 +379,6 @@ fn on_resize() {
pancurses::resize_term(size.y as i32, size.x as i32); pancurses::resize_term(size.y as i32, size.x as i32);
} }
#[cfg(unix)]
fn resize_channel() -> chan::Receiver<chan_signal::Signal> {
chan_signal::notify(&[chan_signal::Signal::WINCH])
}
#[cfg(not(unix))]
fn resize_channel() -> chan::Receiver<()> {
let (sender, receiver) = chan::async();
// Forget the sender, so the channel doesn't close, but never completes.
::std::mem::forget(sender);
receiver
}
impl backend::Backend for Backend { impl backend::Backend for Backend {
fn screen_size(&self) -> Vec2 { fn screen_size(&self) -> Vec2 {
// Coordinates are reversed here // Coordinates are reversed here
@ -449,47 +450,47 @@ impl backend::Backend for Backend {
} }
fn start_input_thread( fn start_input_thread(
&mut self, event_sink: chan::Sender<Event>, &mut self, event_sink: Sender<Option<Event>>,
stops: chan::Receiver<bool>, input_request: Receiver<backend::InputRequest>,
) { ) {
let resize = resize_channel(); let running = Arc::new(AtomicBool::new(true));
let (sender, receiver) = chan::async();
let needs_resize = Arc::clone(&self.needs_resize); let needs_resize = Arc::clone(&self.needs_resize);
// This thread will merge resize and input into event_sink let resize_running = Arc::clone(&running);
let resize_sender = event_sink.clone();
let signals = self.signals.take().unwrap();
thread::spawn(move || { thread::spawn(move || {
loop { // This thread will listen to SIGWINCH events and report them.
chan_select! { while resize_running.load(Ordering::Relaxed) {
resize.recv() => { // We know it will only contain SIGWINCH signals, so no need to check.
// Tell ncurses about the new terminal size. for _ in signals.pending() {
// Well, do the actual resizing later on, in the main thread. // Tell ncurses about the new terminal size.
// Ncurses isn't really thread-safe so calling resize_term() can crash // Well, do the actual resizing later on, in the main thread.
// other calls like clear() or refresh(). // Ncurses isn't really thread-safe so calling resize_term() can crash
needs_resize.store(true, Ordering::Relaxed); // other calls like clear() or refresh().
event_sink.send(Event::WindowResize); needs_resize.store(true, Ordering::Relaxed);
}, resize_sender.send(Some(Event::WindowResize));
receiver.recv() -> event => {
match event {
Some(event) => event_sink.send(event),
None => return,
}
}
} }
} }
}); });
let mut input_parser = let mut input_parser =
InputParser::new(sender, Arc::clone(&self.window)); InputParser::new(event_sink, Arc::clone(&self.window));
thread::spawn(move || { thread::spawn(move || {
loop { for req in input_request {
input_parser.parse_next(); match req {
backend::InputRequest::Peek => {
if stops.recv() != Some(false) { input_parser.window.timeout(0);
// If the channel was closed or if `true` was sent, abort. }
break; backend::InputRequest::Block => {
input_parser.window.timeout(-1);
}
} }
input_parser.parse_next();
} }
running.store(false, Ordering::Relaxed);
}); });
} }
} }

View File

@ -4,9 +4,7 @@ use event;
use theme; use theme;
use vec::Vec2; use vec::Vec2;
use std::time::Duration; use crossbeam_channel::{Sender};
use chan;
pub struct Backend; pub struct Backend;
@ -33,9 +31,10 @@ impl backend::Backend for Backend {
} }
fn prepare_input( fn prepare_input(
&mut self, event_sink: &chan::Sender<event::Event>, _timeout: Duration, &mut self, event_sink: &Sender<Option<event::Event>>,
_input_request: backend::InputRequest,
) { ) {
event_sink.send(event::Event::Exit) event_sink.send(Some(event::Event::Exit))
} }
fn print_at(&self, _: Vec2, _: &str) {} fn print_at(&self, _: Vec2, _: &str) {}

View File

@ -10,18 +10,24 @@
use event; use event;
use theme; use theme;
use chan::{Receiver, Sender}; use crossbeam_channel::{Receiver, Sender};
use vec::Vec2; use vec::Vec2;
use std::time::Duration;
pub mod dummy; pub mod dummy;
pub mod blt; pub mod blt;
pub mod curses; pub mod curses;
pub mod termion; pub mod termion;
/// A request for input, sent to the backend.
pub enum InputRequest {
/// The backend should respond immediately with an answer, possibly empty.
Peek,
/// The backend should block until input is available.
Block,
}
/// Trait defining the required methods to be a backend. /// Trait defining the required methods to be a backend.
pub trait Backend { pub trait Backend {
// TODO: take `self` by value? // TODO: take `self` by value?
@ -32,12 +38,15 @@ pub trait Backend {
fn finish(&mut self); fn finish(&mut self);
/// Starts a thread to collect input and send it to the given channel. /// Starts a thread to collect input and send it to the given channel.
///
/// `event_trigger` will receive a value before any event is needed.
fn start_input_thread( fn start_input_thread(
&mut self, event_sink: Sender<event::Event>, running: Receiver<bool>, &mut self, event_sink: Sender<Option<event::Event>>,
input_request: Receiver<InputRequest>,
) { ) {
// Dummy implementation for some backends. // Dummy implementation for some backends.
let _ = event_sink; let _ = event_sink;
let _ = running; let _ = input_request;
} }
/// Prepares the backend to collect input. /// Prepares the backend to collect input.
@ -45,12 +54,13 @@ pub trait Backend {
/// This is only required for non-thread-safe backends like BearLibTerminal /// This is only required for non-thread-safe backends like BearLibTerminal
/// where we cannot collect input in a separate thread. /// where we cannot collect input in a separate thread.
fn prepare_input( fn prepare_input(
&mut self, event_sink: &Sender<event::Event>, timeout: Duration, &mut self, event_sink: &Sender<Option<event::Event>>,
input_request: InputRequest,
) { ) {
// Dummy implementation for most backends. // Dummy implementation for most backends.
// Little trick to avoid unused variables. // Little trick to avoid unused variables.
let _ = event_sink; let _ = event_sink;
let _ = timeout; let _ = input_request;
} }
/// Refresh the screen. /// Refresh the screen.

View File

@ -5,8 +5,7 @@
extern crate termion; extern crate termion;
extern crate chan_signal; use crossbeam_channel::{self, Sender, Receiver};
use self::termion::color as tcolor; use self::termion::color as tcolor;
use self::termion::event::Event as TEvent; use self::termion::event::Event as TEvent;
use self::termion::event::Key as TKey; use self::termion::event::Key as TKey;
@ -16,14 +15,19 @@ use self::termion::input::{MouseTerminal, TermRead};
use self::termion::raw::{IntoRawMode, RawTerminal}; use self::termion::raw::{IntoRawMode, RawTerminal};
use self::termion::screen::AlternateScreen; use self::termion::screen::AlternateScreen;
use self::termion::style as tstyle; use self::termion::style as tstyle;
use signal_hook::iterator::Signals;
use libc;
use backend; use backend;
use chan;
use event::{Event, Key, MouseButton, MouseEvent}; use event::{Event, Key, MouseButton, MouseEvent};
use theme;
use vec::Vec2;
use std::cell::Cell; use std::cell::Cell;
use std::io::{Stdout, Write}; use std::io::{Stdout, Write};
use std::thread; use std::thread;
use theme; use std::sync::Arc;
use vec::Vec2; use std::sync::atomic::{AtomicBool,Ordering};
pub struct Backend { pub struct Backend {
terminal: AlternateScreen<MouseTerminal<RawTerminal<Stdout>>>, terminal: AlternateScreen<MouseTerminal<RawTerminal<Stdout>>>,
@ -31,53 +35,73 @@ pub struct Backend {
} }
struct InputParser { struct InputParser {
input: chan::Receiver<TEvent>, // Inner state required to parse input
resize: chan::Receiver<chan_signal::Signal>,
event_sink: chan::Sender<Event>,
last_button: Option<MouseButton>, last_button: Option<MouseButton>,
event_due: bool,
requests: Sender<()>,
input: Receiver<TEvent>,
} }
impl InputParser { impl InputParser {
fn new(event_sink: chan::Sender<Event>) -> Self { // Creates a non-blocking abstraction over the usual blocking input
let (sender, receiver) = chan::async(); fn new() -> Self {
let (input_sender, input_receiver) = crossbeam_channel::bounded(0);
let (request_sender, request_receiver) = crossbeam_channel::bounded(0);
let resize = chan_signal::notify(&[chan_signal::Signal::WINCH]); // This thread will stop after an event when `InputParser` is dropped.
// Fill the input channel
thread::spawn(move || { thread::spawn(move || {
for key in ::std::io::stdin().events() { let stdin = ::std::io::stdin();
if let Ok(key) = key { let stdin = stdin.lock();
sender.send(key) let mut events = stdin.events();
}
for _ in request_receiver {
let event: Result<TEvent, ::std::io::Error> = events.next().unwrap();
input_sender.send(event.unwrap());
} }
}); });
InputParser { InputParser {
resize,
event_sink,
last_button: None, last_button: None,
input: receiver, input: input_receiver,
requests: request_sender,
event_due: false,
} }
} }
/// We pledge to want input.
///
/// If we were already expecting input, this is a NO-OP.
fn request(&mut self) {
if !self.event_due {
self.requests.send(());
self.event_due = true;
}
}
fn peek(&mut self) -> Option<Event> {
self.request();
let timeout = ::std::time::Duration::from_millis(10);
let input = select! {
recv(self.input, input) => {
input
}
recv(crossbeam_channel::after(timeout)) => return None,
};
// We got what we came for.
self.event_due = false;
Some(self.map_key(input.unwrap()))
}
fn next_event(&mut self) -> Event { fn next_event(&mut self) -> Event {
let result; self.request();
{
let input = &self.input;
let resize = &self.resize;
chan_select!{ let input = self.input.recv().unwrap();
resize.recv() => return Event::WindowResize, self.event_due = false;
input.recv() -> input => result = Some(input.unwrap()), self.map_key(input)
}
}
self.map_key(result.unwrap())
}
fn parse_next(&mut self) {
let event = self.next_event();
self.event_sink.send(event);
} }
fn map_key(&mut self, event: TEvent) -> Event { fn map_key(&mut self, event: TEvent) -> Event {
@ -266,21 +290,41 @@ impl backend::Backend for Backend {
} }
fn start_input_thread( fn start_input_thread(
&mut self, event_sink: chan::Sender<Event>, &mut self, event_sink: Sender<Option<Event>>,
stops: chan::Receiver<bool>, input_request: Receiver<backend::InputRequest>,
) { ) {
let mut parser = InputParser::new(event_sink); let running = Arc::new(AtomicBool::new(true));
thread::spawn(move || { let resize_sender = event_sink.clone();
loop { let signals = Signals::new(&[libc::SIGWINCH]).unwrap();
// This sends events to the event sender.
parser.parse_next();
if stops.recv() != Some(false) { let resize_running = Arc::clone(&running);
// If the channel was closed or if `true` was sent, abort. thread::spawn(move || {
break; while resize_running.load(Ordering::Relaxed) {
// We know it will only contain SIGWINCH signals, so no need to check.
for _ in signals.pending() {
// Tell ncurses about the new terminal size.
// Well, do the actual resizing later on, in the main thread.
// Ncurses isn't really thread-safe so calling resize_term() can crash
// other calls like clear() or refresh().
resize_sender.send(Some(Event::WindowResize));
} }
} }
}); });
let mut parser = InputParser::new();
thread::spawn(move || {
for req in input_request {
match req {
backend::InputRequest::Peek => {
event_sink.send(parser.peek());
}
backend::InputRequest::Block => {
event_sink.send(Some(parser.next_event()));
}
}
}
running.store(false, Ordering::Relaxed);
});
} }
} }

View File

@ -3,7 +3,7 @@ use std::collections::HashMap;
use std::path::Path; use std::path::Path;
use std::time::Duration; use std::time::Duration;
use chan; use crossbeam_channel::{self, Receiver, Sender};
use backend; use backend;
use direction; use direction;
@ -39,15 +39,15 @@ pub struct Cursive {
backend: Box<backend::Backend>, backend: Box<backend::Backend>,
cb_source: chan::Receiver<Box<CbFunc>>, cb_source: Receiver<Box<CbFunc>>,
cb_sink: chan::Sender<Box<CbFunc>>, cb_sink: Sender<Box<CbFunc>>,
event_source: chan::Receiver<Event>, event_source: Receiver<Option<Event>>,
event_sink: chan::Sender<Event>, event_sink: Sender<Option<Event>>,
// Sends true or false after each event. // Sends true or false after each event.
stop_sink: chan::Sender<bool>, input_trigger: Sender<backend::InputRequest>,
received_event: bool, expecting_event: bool,
} }
/// Describes one of the possible interruptions we should handle. /// Describes one of the possible interruptions we should handle.
@ -130,13 +130,13 @@ impl Cursive {
{ {
let theme = theme::load_default(); let theme = theme::load_default();
let (cb_sink, cb_source) = chan::async(); let (cb_sink, cb_source) = crossbeam_channel::unbounded();
let (event_sink, event_source) = chan::async(); let (event_sink, event_source) = crossbeam_channel::unbounded();
let (stop_sink, stop_source) = chan::async(); let (input_sink, input_source) = crossbeam_channel::unbounded();
let mut backend = backend_init(); let mut backend = backend_init();
backend.start_input_thread(event_sink.clone(), stop_source); backend.start_input_thread(event_sink.clone(), input_source);
Cursive { Cursive {
fps: 0, fps: 0,
@ -152,8 +152,8 @@ impl Cursive {
event_source, event_source,
event_sink, event_sink,
backend, backend,
stop_sink, input_trigger: input_sink,
received_event: false, expecting_event: false,
} }
} }
@ -214,7 +214,7 @@ impl Cursive {
/// ``` /// ```
/// ///
/// [`set_fps`]: #method.set_fps /// [`set_fps`]: #method.set_fps
pub fn cb_sink(&self) -> &chan::Sender<Box<CbFunc>> { pub fn cb_sink(&self) -> &Sender<Box<CbFunc>> {
&self.cb_sink &self.cb_sink
} }
@ -585,36 +585,51 @@ impl Cursive {
self.screen_mut().reposition_layer(layer, position); self.screen_mut().reposition_layer(layer, position);
} }
// Wait until something happens. fn peek(&mut self) -> Option<Interruption> {
fn poll(&mut self) -> Interruption { // First, try a callback
let input_channel = &self.event_source; select! {
let cb_channel = &self.cb_source; // Skip to input if nothing is ready
default => (),
recv(self.cb_source, cb) => return Some(Interruption::Callback(cb.unwrap())),
}
self.backend // No callback? Check input then
.prepare_input(&self.event_sink, Duration::from_millis(30)); if self.expecting_event {
// We're already blocking.
return None;
}
if self.fps > 0 { self.input_trigger.send(backend::InputRequest::Peek);
let timeout = 1000 / self.fps; self.backend.prepare_input(&self.event_sink, backend::InputRequest::Peek);
let timeout = chan::after_ms(timeout); self.event_source.recv().unwrap().map(Interruption::Event)
chan_select! { }
input_channel.recv() -> input => {
return Interruption::Event(input.unwrap()) /// Wait until something happens.
}, ///
cb_channel.recv() -> cb => { /// If `peek` is `true`, return `None` immediately if nothing is ready.
return Interruption::Callback(cb.unwrap()) fn poll(&mut self) -> Option<Interruption> {
}, if !self.expecting_event {
timeout.recv() => { self.input_trigger.send(backend::InputRequest::Block);
return Interruption::Timeout; self.backend.prepare_input(&self.event_sink, backend::InputRequest::Block);
}, self.expecting_event = true;
} }
let timeout = if self.fps > 0 {
Duration::from_millis(1000 / self.fps as u64)
} else { } else {
chan_select! { // Defaults to 1 refresh per hour.
input_channel.recv() -> input => { Duration::from_secs(3600)
return Interruption::Event(input.unwrap()) };
},
cb_channel.recv() -> cb => { select! {
return Interruption::Callback(cb.unwrap()) recv(self.event_source, event) => {
}, event.unwrap().map(Interruption::Event)
},
recv(self.cb_source, cb) => {
cb.map(Interruption::Callback)
},
recv(crossbeam_channel::after(timeout)) => {
Some(Interruption::Timeout)
} }
} }
} }
@ -720,13 +735,21 @@ impl Cursive {
self.draw(); self.draw();
self.backend.refresh(); self.backend.refresh();
// Wait for next event. // First, read all events available while peeking.
if self.received_event { while let Some(interruption) = self.peek() {
// Now tell the backend whether he sould keep receiving. self.handle_interruption(interruption);
self.stop_sink.send(!self.running); if !self.running {
self.received_event = false; return;
}
} }
match self.poll() {
if let Some(interruption) = self.poll() {
self.handle_interruption(interruption);
}
}
fn handle_interruption(&mut self, interruption: Interruption) {
match interruption {
Interruption::Event(event) => { Interruption::Event(event) => {
// eprintln!("{:?}, {:?}", event, self.screen_size()); // eprintln!("{:?}, {:?}", event, self.screen_size());
if event == Event::Exit { if event == Event::Exit {
@ -735,6 +758,7 @@ impl Cursive {
if event == Event::WindowResize { if event == Event::WindowResize {
self.clear(); self.clear();
return;
} }
if let Event::Mouse { if let Event::Mouse {
@ -772,7 +796,7 @@ impl Cursive {
} }
// Ok, we processed the event. // Ok, we processed the event.
self.received_event = true; self.expecting_event = false;
} }
Interruption::Callback(cb) => { Interruption::Callback(cb) => {
cb.call_box(self); cb.call_box(self);

View File

@ -113,7 +113,7 @@ impl EventResult {
} }
} }
/// Returns `self` if it is not `Event::Ignored`, otherwise returns `f()`. /// Returns `self` if it is not `EventResult::Ignored`, otherwise returns `f()`.
pub fn or_else<F>(self, f: F) -> Self pub fn or_else<F>(self, f: F) -> Self
where where
F: FnOnce() -> EventResult, F: FnOnce() -> EventResult,

View File

@ -68,7 +68,7 @@ extern crate enumset;
#[macro_use] #[macro_use]
extern crate log; extern crate log;
#[macro_use] #[macro_use]
extern crate chan; extern crate crossbeam_channel;
#[cfg(any(feature = "ncurses", feature = "pancurses"))] #[cfg(any(feature = "ncurses", feature = "pancurses"))]
#[macro_use] #[macro_use]
@ -77,7 +77,7 @@ extern crate maplit;
// We use chan_signal to detect SIGWINCH. // We use chan_signal to detect SIGWINCH.
// It's not how windows work, so no need to use that. // It's not how windows work, so no need to use that.
#[cfg(unix)] #[cfg(unix)]
extern crate chan_signal; extern crate signal_hook;
extern crate libc; extern crate libc;
extern crate num; extern crate num;