From 4df29bb37700ee9f2b8e09374c6db513daae89b3 Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Sat, 10 Dec 2016 23:32:12 -0800 Subject: [PATCH] Finish main.rs cleanup * config::Monitor is more ergonomic and self-contained * Fixed an issue with macOS resize. Previously, the terminal was marked as dirty in the window resize handler, but the display can't do that. Instead, the event processor returns a flag whether it was requested to wakeup. --- src/config.rs | 96 ++++++++++++++++++----------- src/event.rs | 14 +++-- src/main.rs | 165 +++++++++----------------------------------------- 3 files changed, 100 insertions(+), 175 deletions(-) diff --git a/src/config.rs b/src/config.rs index 2c21428c..23010350 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1074,57 +1074,83 @@ impl Default for Font { } } -pub struct Watcher(::std::thread::JoinHandle<()>); - -pub trait OnConfigReload { - fn on_config_reload(&mut self, Config); +pub struct Monitor { + _thread: ::std::thread::JoinHandle<()>, + rx: mpsc::Receiver, } -impl Watcher { - pub fn new(path: P, mut handler: H) -> Watcher +pub trait OnConfigReload { + fn on_config_reload(&mut self); +} + +impl OnConfigReload for ::display::Notifier { + fn on_config_reload(&mut self) { + self.notify(); + } +} + +impl Monitor { + /// Get pending config changes + pub fn pending_config(&self) -> Option { + let mut config = None; + while let Ok(new) = self.rx.try_recv() { + config = Some(new); + } + + config + } + pub fn new(path: P, mut handler: H) -> Monitor where H: OnConfigReload + Send + 'static, P: Into { let path = path.into(); - Watcher(::util::thread::spawn_named("config watcher", move || { - let (tx, rx) = mpsc::channel(); - let mut watcher = FileWatcher::new(tx).unwrap(); - watcher.watch(&path).expect("watch alacritty yml"); + let (config_tx, config_rx) = mpsc::channel(); - let config_path = path.as_path(); + Monitor { + _thread: ::util::thread::spawn_named("config watcher", move || { + let (tx, rx) = mpsc::channel(); + let mut watcher = FileWatcher::new(tx).unwrap(); + watcher.watch(&path).expect("watch alacritty yml"); - loop { - let event = rx.recv().expect("watcher event"); - let ::notify::Event { path, op } = event; + let config_path = path.as_path(); - if let Ok(op) = op { - // Skip events that are just a rename - if op.contains(op::RENAME) && !op.contains(op::WRITE) { - continue; - } + loop { + let event = rx.recv().expect("watcher event"); + let ::notify::Event { path, op } = event; - // Need to handle ignore for linux - if op.contains(op::IGNORED) { - if let Some(path) = path.as_ref() { - if let Err(err) = watcher.watch(&path) { - err_println!("failed to establish watch on {:?}: {:?}", path, err); + if let Ok(op) = op { + // Skip events that are just a rename + if op.contains(op::RENAME) && !op.contains(op::WRITE) { + continue; + } + + // Need to handle ignore for linux + if op.contains(op::IGNORED) { + if let Some(path) = path.as_ref() { + if let Err(err) = watcher.watch(&path) { + err_println!("failed to establish watch on {:?}: {:?}", path, err); + } } } - } - // Reload file - path.map(|path| { - if path == config_path { - match Config::load() { - Ok(config) => handler.on_config_reload(config), - Err(err) => err_println!("Ignoring invalid config: {}", err), + // Reload file + path.map(|path| { + if path == config_path { + match Config::load() { + Ok(config) => { + let _ = config_tx.send(config); + handler.on_config_reload(); + }, + Err(err) => err_println!("Ignoring invalid config: {}", err), + } } - } - }); + }); + } } - } - })) + }), + rx: config_rx, + } } } diff --git a/src/event.rs b/src/event.rs index 2a27dd61..d8fec37e 100644 --- a/src/event.rs +++ b/src/event.rs @@ -43,7 +43,7 @@ impl Processor { } } - fn handle_event(&mut self, event: glutin::Event) { + fn handle_event(&mut self, event: glutin::Event, wakeup_request: &mut bool) { match event { glutin::Event::Closed => { if self.ref_test { @@ -111,20 +111,26 @@ impl Processor { &terminal ); }, + glutin::Event::Awakened => { + *wakeup_request = true; + }, _ => (), } } /// Process at least one event and handle any additional queued events. - pub fn process_events(&mut self, window: &Window) { + pub fn process_events(&mut self, window: &Window) -> bool { + let mut wakeup_request = false; for event in window.wait_events() { - self.handle_event(event); + self.handle_event(event, &mut wakeup_request); break; } for event in window.poll_events() { - self.handle_event(event); + self.handle_event(event, &mut wakeup_request); } + + wakeup_request } pub fn update_config(&mut self, config: &Config) { diff --git a/src/main.rs b/src/main.rs index 0fba05a6..d4194c28 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,42 +13,18 @@ // limitations under the License. // //! Alacritty - The GPU Enhanced Terminal -#![feature(question_mark)] -#![feature(inclusive_range_syntax)] -#![feature(drop_types_in_const)] #![allow(stable_features)] // lying about question_mark because 1.14.0 isn't released! -#![feature(proc_macro)] - -#[macro_use] -extern crate serde_derive; - #[macro_use] extern crate alacritty; -extern crate cgmath; -extern crate copypasta; -extern crate errno; -extern crate font; -extern crate glutin; -extern crate libc; -extern crate mio; -extern crate notify; -extern crate parking_lot; -extern crate serde; -extern crate serde_json; -extern crate serde_yaml; -extern crate vte; - -#[macro_use] -extern crate bitflags; use std::error::Error; -use std::sync::{mpsc, Arc}; +use std::sync::Arc; use std::rc::Rc; use alacritty::cli; use alacritty::config::{self, Config}; -use alacritty::display::{self, Display}; +use alacritty::display::Display; use alacritty::event; use alacritty::event_loop::EventLoop; use alacritty::input; @@ -56,7 +32,6 @@ use alacritty::sync::FairMutex; use alacritty::term::{Term}; use alacritty::tty::{self, process_should_exit}; - fn main() { // Load configuration let config = match Config::load() { @@ -90,71 +65,8 @@ fn main() { /// Run Alacritty /// -/// Currently, these operations take place in this order. -/// 1. create a window -/// 2. create font rasterizer -/// 3. create quad renderer -/// 4. create glyph cache -/// 5. resize window/renderer using info from glyph cache / rasterizer -/// 6. create a pty -/// 7. create the terminal -/// 8. set resize callback on the window -/// 9. create event loop -/// 10. create display -/// 11. create input processor -/// 12. create config reloader -/// 13. enter main loop -/// -/// Observations: -/// * The window + quad renderer + glyph cache and display are closely -/// related Actually, probably include the input processor as well. -/// The resize callback can be lumped in there and that resize step. -/// Rasterizer as well. Maybe we can lump *all* of this into the -/// `Display`. -/// * the pty and event loop closely related -/// * The term bridges the display and pty -/// * Main loop currently manages input, config reload events, drawing, and -/// exiting -/// -/// It would be *really* great if this could read more like -/// -/// ```ignore -/// let display = Display::new(args..); -/// let pty = Pty::new(display.size()); -/// let term = Arc::new(Term::new(display.size()); -/// let io_loop = Loop::new(Pty::new(display.size()), term.clone()); -/// let config_reloader = config::Monitor::new(&config); -/// -/// loop { -/// force_draw = false; -/// // Wait for something to happen -/// processor.process_events(&display); -/// -/// // Handle config reloads -/// if let Ok(config) = config_rx.try_recv() { -/// force_draw = true; -/// display.update_config(&config); -/// processor.update_config(&config); -/// } -/// -/// // Maybe draw the terminal -/// let terminal = terminal.lock(); -/// signal_flag.set(false); -/// if force_draw || terminal.dirty { -/// display.draw(terminal, &config); -/// drop(terminal); -/// display.swap_buffers(); -/// } -/// -/// // Begin shutdown if the flag was raised. -/// if process_should_exit() { -/// break; -/// } -/// } -/// ``` -/// -/// instead of the 200 line monster it currently is. -/// +/// Creates a window, the terminal state, pty, I/O event loop, input processor, +/// config change monitor, and runs the main display loop. fn run(config: Config, options: cli::Options) -> Result<(), Box> { // Create a display. // @@ -199,45 +111,46 @@ fn run(config: Config, options: cli::Options) -> Result<(), Box> { options.ref_test, ); + // The event loop channel allows write requests from the event processor + // to be sent to the loop and ultimately written to the pty. let loop_tx = event_loop.channel(); - let event_loop_handle = event_loop.spawn(None); // Event processor - let resize_tx = display.resize_channel(); let mut processor = event::Processor::new( input::LoopNotifier(loop_tx), terminal.clone(), - resize_tx, + display.resize_channel(), &config, options.ref_test, ); - let (config_tx, config_rx) = mpsc::channel(); + // Create a config monitor when config was loaded from path + // + // The monitor watches the config file for changes and reloads it. Pending + // config changes are processed in the main loop. + let config_monitor = config.path() + .map(|path| config::Monitor::new(path, display.notifier())); - // create a config watcher when config is loaded from disk - let _config_reloader = config.path().map(|path| { - config::Watcher::new(path, ConfigHandler { - tx: config_tx, - loop_kicker: display.notifier(), - }) - }); + // Kick off the I/O thread + let io_thread = event_loop.spawn(None); - // Main loop - let mut config_updated = false; + // Main display loop loop { - // Wait for something to happen - processor.process_events(display.window()); + // Process input and window events + let wakeup_request = processor.process_events(display.window()); // Handle config reloads - if let Ok(config) = config_rx.try_recv() { - config_updated = true; - display.update_config(&config); - processor.update_config(&config); - } + let config_updated = config_monitor.as_ref() + .and_then(|monitor| monitor.pending_config()) + .map(|config| { + display.update_config(&config); + processor.update_config(&config); + true + }).unwrap_or(false); // Maybe draw the terminal let terminal = terminal.lock(); - if terminal.dirty || config_updated { + if wakeup_request || config_updated { display.draw(terminal, &config); } @@ -247,31 +160,11 @@ fn run(config: Config, options: cli::Options) -> Result<(), Box> { } } - // FIXME need file watcher to work with custom delegates before - // joining config reloader is possible - // - // HELP I don't know what I meant in the above fixme + // FIXME patch notify library to have a shutdown method // config_reloader.join().ok(); - // shutdown - event_loop_handle.join().ok(); + // Wait for the I/O thread thread to finish + let _ = io_thread.join(); Ok(()) } - -struct ConfigHandler { - tx: mpsc::Sender, - loop_kicker: display::Notifier, -} - -impl config::OnConfigReload for ConfigHandler { - fn on_config_reload(&mut self, config: Config) { - if let Err(..) = self.tx.send(config) { - err_println!("Failed to notify of new config"); - return; - } - - self.loop_kicker.notify(); - } -} -