From fd752496332fb4506431aee8deec31d6967a5849 Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Wed, 18 Jul 2018 18:14:25 -0700 Subject: [PATCH] Move resize-specific behaviour to backend --- src/backend/curses/n.rs | 59 ++++++++++++++++++++++++++++------------- src/cursive.rs | 27 +++++++++++-------- 2 files changed, 56 insertions(+), 30 deletions(-) diff --git a/src/backend/curses/n.rs b/src/backend/curses/n.rs index a468ccc..6e86ec6 100644 --- a/src/backend/curses/n.rs +++ b/src/backend/curses/n.rs @@ -6,7 +6,7 @@ use std::ffi::CString; use std::fs::File; use std::io; use std::io::Write; -use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::Arc; use std::thread; @@ -40,30 +40,27 @@ pub struct Backend { struct InputParser { key_codes: HashMap, last_mouse_button: Option, - event_sink: Sender>, + input_buffer: Option, } impl InputParser { - fn new(event_sink: Sender>) -> Self { + fn new() -> Self { InputParser { key_codes: initialize_keymap(), last_mouse_button: None, - event_sink, + input_buffer: None, } } - fn parse_next(&mut self) { - let ch: i32 = ncurses::getch(); - - if ch == 410 { - // Ignore resize events. - self.parse_next(); - return; + fn parse_next(&mut self) -> Option { + if let Some(event) = self.input_buffer.take() { + return Some(event); } + let ch: i32 = ncurses::getch(); + if ch == -1 { - self.event_sink.send(None); - return; + return None; } // Is it a UTF-8 starting point? @@ -77,7 +74,8 @@ impl InputParser { } else { self.parse_ncurses_char(ch) }; - self.event_sink.send(Some(event)); + + Some(event) } fn parse_ncurses_char(&mut self, ch: i32) -> Event { @@ -146,7 +144,7 @@ impl InputParser { if event.is_none() { event = Some(e); } else { - self.event_sink.send(Some(make_event(e))); + self.input_buffer = Some(make_event(e)); } }); } @@ -185,7 +183,6 @@ fn write_to_tty(bytes: &[u8]) -> io::Result<()> { impl Backend { pub fn init() -> Box { - let signals = Some(Signals::new(&[libc::SIGWINCH]).unwrap()); // Change the locale. @@ -312,37 +309,61 @@ impl backend::Backend for Backend { let resize_running = Arc::clone(&running); let resize_sender = event_sink.clone(); let signals = self.signals.take().unwrap(); + let resize_requests = input_request.clone(); thread::spawn(move || { // This thread will listen to SIGWINCH events and report them. while resize_running.load(Ordering::Relaxed) { // We know it will only contain SIGWINCH signals, so no need to check. - for _ in signals.pending() { + if signals.wait().count() > 0 { // 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(). needs_resize.store(true, Ordering::Relaxed); + resize_sender.send(Some(Event::WindowResize)); + // We've sent the message. + // This means Cursive was listening, and will now soon be sending a new request. + // This means the input thread accepted a request, but hasn't sent a message yet. + // So we KNOW the input thread is not waiting for a new request. + + // We sent an event for free, so pay for it now by consuming a request + while let Some(backend::InputRequest::Peek) = + resize_requests.recv() + { + // At this point Cursive will now listen for input. + // There is a chance the input thread will send his event before us. + // But without some extra atomic flag, it'd be hard to know. + // So instead, keep sending `None` + + // Repeat until we receive a blocking call + resize_sender.send(None); + } } } }); - let mut parser = InputParser::new(event_sink); + let mut parser = InputParser::new(); // This thread will take input from ncurses for each request. thread::spawn(move || { for req in input_request { match req { backend::InputRequest::Peek => { + // When peeking, we want an answer instantly from ncurses ncurses::timeout(0); } backend::InputRequest::Block => { ncurses::timeout(-1); } } - parser.parse_next(); + + // Do the actual polling & parsing. + event_sink.send(parser.parse_next()); } + // The request channel is closed, which means Cursive has been + // dropped, so stop the resize-detection thread as well. running.store(false, Ordering::Relaxed); }); } diff --git a/src/cursive.rs b/src/cursive.rs index af5eca1..9f658a1 100644 --- a/src/cursive.rs +++ b/src/cursive.rs @@ -131,9 +131,9 @@ impl Cursive { let theme = theme::load_default(); let (cb_sink, cb_source) = crossbeam_channel::unbounded(); - let (event_sink, event_source) = crossbeam_channel::unbounded(); + let (event_sink, event_source) = crossbeam_channel::bounded(0); - let (input_sink, input_source) = crossbeam_channel::unbounded(); + let (input_sink, input_source) = crossbeam_channel::bounded(0); let mut backend = backend_init(); backend.start_input_thread(event_sink.clone(), input_source); @@ -600,7 +600,8 @@ impl Cursive { } self.input_trigger.send(backend::InputRequest::Peek); - self.backend.prepare_input(&self.event_sink, backend::InputRequest::Peek); + self.backend + .prepare_input(&self.event_sink, backend::InputRequest::Peek); self.event_source.recv().unwrap().map(Interruption::Event) } @@ -610,7 +611,8 @@ impl Cursive { fn poll(&mut self) -> Option { if !self.expecting_event { self.input_trigger.send(backend::InputRequest::Block); - self.backend.prepare_input(&self.event_sink, backend::InputRequest::Block); + self.backend + .prepare_input(&self.event_sink, backend::InputRequest::Block); self.expecting_event = true; } @@ -623,6 +625,9 @@ impl Cursive { select! { recv(self.event_source, event) => { + // Ok, we processed the event. + self.expecting_event = false; + event.unwrap().map(Interruption::Event) }, recv(self.cb_source, cb) => { @@ -735,16 +740,20 @@ impl Cursive { self.draw(); self.backend.refresh(); - // First, read all events available while peeking. - while let Some(interruption) = self.peek() { + if let Some(interruption) = self.poll() { self.handle_interruption(interruption); if !self.running { return; } } - if let Some(interruption) = self.poll() { + // Don't block, but try to read any other pending event. + // This lets us batch-process chunks of events, like big copy-paste or mouse drags. + while let Some(interruption) = self.peek() { self.handle_interruption(interruption); + if !self.running { + return; + } } } @@ -758,7 +767,6 @@ impl Cursive { if event == Event::WindowResize { self.clear(); - return; } if let Event::Mouse { @@ -794,9 +802,6 @@ impl Cursive { EventResult::Consumed(Some(cb)) => cb(self), } } - - // Ok, we processed the event. - self.expecting_event = false; } Interruption::Callback(cb) => { cb.call_box(self);