From ed0b1cfff04903fe26f586340e036c38bbf30b33 Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Sat, 10 Dec 2016 22:44:13 -0800 Subject: [PATCH] Display manages window, renderer, rasterizer This is part of an ongoing decoupling effort across the codebase and tidying effort in main.rs. Everything to do with showing the window with a grid of characters is now managed by the `Display` type. It owns the window, the font rasterizer, and the renderer. The only info needed from it are dimensions of characters and the window itself for sizing the terminal properly. Additionally, the I/O loop has access to wake it up when new data arrives. --- src/cli.rs | 2 +- src/display.rs | 246 +++++++++++++++++++++++++++++++++++++++++ src/event.rs | 1 + src/event_loop.rs | 23 +--- src/lib.rs | 23 +--- src/main.rs | 274 ++++++++++------------------------------------ src/term/mod.rs | 14 +++ src/tty.rs | 33 +++--- src/window.rs | 79 +++++++++++-- 9 files changed, 418 insertions(+), 277 deletions(-) create mode 100644 src/display.rs diff --git a/src/cli.rs b/src/cli.rs index b61d1054..15bd57c5 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. use std::env; -use alacritty::index::{Line, Column}; +use index::{Line, Column}; /// Options specified on the command line pub struct Options { diff --git a/src/display.rs b/src/display.rs new file mode 100644 index 00000000..cfe684c0 --- /dev/null +++ b/src/display.rs @@ -0,0 +1,246 @@ +// Copyright 2016 Joe Wilm, The Alacritty Project Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! The display subsystem including window management, font rasterization, and +//! GPU drawing. +use std::sync::mpsc; + +use parking_lot::{MutexGuard}; + +use font; +use Rgb; +use ansi::Color; +use cli; +use config::Config; +use meter::Meter; +use renderer::{GlyphCache, QuadRenderer}; +use term::{Term, SizeInfo}; + +use window::{self, Size, Pixels, Window, SetInnerSize}; + +/// The display wraps a window, font rasterizer, and GPU renderer +pub struct Display { + window: Window, + renderer: QuadRenderer, + glyph_cache: GlyphCache, + render_timer: bool, + rx: mpsc::Receiver<(u32, u32)>, + tx: mpsc::Sender<(u32, u32)>, + meter: Meter, + resize_callback: Option, + size_info: SizeInfo, +} + +/// Can wakeup the render loop from other threads +pub struct Notifier(window::Proxy); + +impl Notifier { + pub fn notify(&self) { + self.0.wakeup_event_loop(); + } +} + +impl Display + where F: Fn(&SizeInfo) +{ + pub fn notifier(&self) -> Notifier { + Notifier(self.window.create_window_proxy()) + } + + pub fn update_config(&mut self, config: &Config) { + self.renderer.update_config(config); + self.render_timer = config.render_timer(); + } + + /// Provide a callback to be invoked then the display changes size. + pub fn set_resize_callback(&mut self, callback: F) { + self.resize_callback = Some(callback); + } + + /// Get size info about the display + pub fn size(&self) -> &SizeInfo { + &self.size_info + } + + pub fn new( + config: &Config, + options: &cli::Options, + ) -> Result, window::Error> { + // Extract some properties from config + let font = config.font(); + let dpi = config.dpi(); + let render_timer = config.render_timer(); + + // Create the window where Alacritty will be displayed + let mut window = match Window::new() { + Ok(window) => window, + Err(err) => die!("{}", err) + }; + + // get window properties for initializing the other subsytems + let size = window.inner_size_pixels().unwrap(); + let dpr = window.hidpi_factor(); + + println!("device_pixel_ratio: {}", dpr); + + let rasterizer = font::Rasterizer::new(dpi.x(), dpi.y(), dpr); + + // Create renderer + let mut renderer = QuadRenderer::new(&config, size); + + // Initialize glyph cache + let glyph_cache = { + println!("Initializing glyph cache"); + let init_start = ::std::time::Instant::now(); + + let cache = renderer.with_loader(|mut api| { + GlyphCache::new(rasterizer, &config, &mut api) + }); + + let stop = init_start.elapsed(); + let stop_f = stop.as_secs() as f64 + stop.subsec_nanos() as f64 / 1_000_000_000f64; + println!("Finished initializing glyph cache in {}", stop_f); + + cache + }; + + // Need font metrics to resize the window properly. This suggests to me the + // font metrics should be computed before creating the window in the first + // place so that a resize is not needed. + let metrics = glyph_cache.font_metrics(); + let cell_width = (metrics.average_advance + font.offset().x() as f64) as u32; + let cell_height = (metrics.line_height + font.offset().y() as f64) as u32; + + // Resize window to specified dimensions + let width = cell_width * options.columns_u32() + 4; + let height = cell_height * options.lines_u32() + 4; + let size = Size { width: Pixels(width), height: Pixels(height) }; + println!("set_inner_size: {}", size); + + window.set_inner_size(size); + renderer.resize(*size.width as _, *size.height as _); + println!("Cell Size: ({} x {})", cell_width, cell_height); + + let size_info = SizeInfo { + width: *size.width as f32, + height: *size.height as f32, + cell_width: cell_width as f32, + cell_height: cell_height as f32 + }; + + // Channel for resize events + // + // macOS has a callback for getting resize events, the channel is used + // to queue resize events until the next draw call. Unfortunately, it + // seems that the event loop is blocked until the window is done + // resizing. If any drawing were to happen during a resize, it would + // need to be in the callback. + let (tx, rx) = mpsc::channel(); + + let mut display = Display { + window: window, + renderer: renderer, + glyph_cache: glyph_cache, + render_timer: render_timer, + tx: tx, + rx: rx, + meter: Meter::new(), + resize_callback: None, + size_info: size_info, + }; + + let resize_tx = display.resize_channel(); + let proxy = display.window.create_window_proxy(); + display.window.set_resize_callback(move |width, height| { + let _ = resize_tx.send((width, height)); + proxy.wakeup_event_loop(); + }); + + Ok(display) + } + + #[inline] + pub fn resize_channel(&self) -> mpsc::Sender<(u32, u32)> { + self.tx.clone() + } + + pub fn window(&self) -> &Window { + &self.window + } + + /// Draw the screen + /// + /// A reference to Term whose state is being drawn must be provided. + /// + /// This call may block if vsync is enabled + pub fn draw(&mut self, mut terminal: MutexGuard, config: &Config) { + // This is a hack since sometimes we get stuck waiting for events + // in the main loop otherwise. + // + // TODO figure out why this is necessary + self.window.clear_wakeup_flag(); + + // Clear dirty flag + terminal.dirty = false; + + // Resize events new_size and are handled outside the poll_events + // iterator. This has the effect of coalescing multiple resize + // events into one. + let mut new_size = None; + + // Check for any out-of-band resize events (mac only) + while let Ok(sz) = self.rx.try_recv() { + new_size = Some(sz); + } + + // Receive any resize events; only call gl::Viewport on last + // available + if let Some((w, h)) = new_size.take() { + terminal.resize(w as f32, h as f32); + let size = terminal.size_info(); + self.resize_callback.as_ref() + .map(|func| func(&size)); + self.renderer.resize(w as i32, h as i32); + } + + { + let glyph_cache = &mut self.glyph_cache; + // Draw grid + { + let _sampler = self.meter.sampler(); + + let size_info = terminal.size_info().clone(); + self.renderer.with_api(config, &size_info, |mut api| { + api.clear(); + + // Draw the grid + api.render_cells(terminal.renderable_cells(), glyph_cache); + }); + } + + // Draw render timer + if self.render_timer { + let timing = format!("{:.3} usec", self.meter.average()); + let color = Color::Spec(Rgb { r: 0xd5, g: 0x4e, b: 0x53 }); + self.renderer.with_api(config, terminal.size_info(), |mut api| { + api.render_string(&timing[..], glyph_cache, &color); + }); + } + } + + // Unlock the terminal mutex + drop(terminal); + self.window.swap_buffers().unwrap(); + } +} diff --git a/src/event.rs b/src/event.rs index 900a0fb1..2a27dd61 100644 --- a/src/event.rs +++ b/src/event.rs @@ -5,6 +5,7 @@ use std::sync::{Arc, mpsc}; use serde_json as json; use glutin; + use window::Window; use input; diff --git a/src/event_loop.rs b/src/event_loop.rs index c2c5ce69..70aa4acb 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -10,14 +10,11 @@ use mio::{self, Events, PollOpt, Ready}; use mio::unix::EventedFd; use ansi; +use display; use term::Term; use util::thread; use sync::FairMutex; -use window; - -use super::Flag; - /// Messages that may be sent to the `EventLoop` #[derive(Debug)] pub enum Msg { @@ -35,8 +32,7 @@ pub struct EventLoop { rx: mio::channel::Receiver, tx: mio::channel::Sender, terminal: Arc>, - proxy: window::Proxy, - signal_flag: Flag, + display: display::Notifier, ref_test: bool, } @@ -131,8 +127,7 @@ impl EventLoop /// Create a new event loop pub fn new( terminal: Arc>, - proxy: window::Proxy, - signal_flag: Flag, + display: display::Notifier, pty: Io, ref_test: bool, ) -> EventLoop { @@ -143,8 +138,7 @@ impl EventLoop tx: tx, rx: rx, terminal: terminal, - proxy: proxy, - signal_flag: signal_flag, + display: display, ref_test: ref_test, } } @@ -196,14 +190,7 @@ impl EventLoop terminal.dirty = true; - // Only wake up the event loop if it hasn't already been - // signaled. This is a really important optimization because - // waking up the event loop redundantly burns *a lot* of - // cycles. - if !self.signal_flag.get() { - self.proxy.wakeup_event_loop(); - self.signal_flag.set(true); - } + self.display.notify(); }, Err(err) => { match err.kind() { diff --git a/src/lib.rs b/src/lib.rs index cd4f382e..9903f591 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,7 +49,9 @@ extern crate bitflags; pub mod macros; pub mod ansi; +pub mod cli; pub mod config; +pub mod display; pub mod event; pub mod event_loop; pub mod grid; @@ -63,9 +65,6 @@ pub mod tty; pub mod util; pub mod window; -use std::sync::Arc; -use std::sync::atomic::{AtomicBool, Ordering}; - pub use grid::Grid; pub use term::Term; @@ -80,21 +79,3 @@ pub mod gl { #![allow(non_upper_case_globals)] include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs")); } - -#[derive(Clone)] -pub struct Flag(pub Arc); -impl Flag { - pub fn new(initial_value: bool) -> Flag { - Flag(Arc::new(AtomicBool::new(initial_value))) - } - - #[inline] - pub fn get(&self) -> bool { - self.0.load(Ordering::Acquire) - } - - #[inline] - pub fn set(&self, value: bool) { - self.0.store(value, Ordering::Release) - } -} diff --git a/src/main.rs b/src/main.rs index 1c1397d0..0fba05a6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -42,25 +42,20 @@ extern crate vte; #[macro_use] extern crate bitflags; +use std::error::Error; use std::sync::{mpsc, Arc}; -use std::sync::atomic::Ordering; +use std::rc::Rc; -use parking_lot::{MutexGuard}; - -use alacritty::Flag; -use alacritty::Rgb; +use alacritty::cli; use alacritty::config::{self, Config}; +use alacritty::display::{self, Display}; use alacritty::event; use alacritty::event_loop::EventLoop; use alacritty::input; -use alacritty::meter::Meter; -use alacritty::renderer::{QuadRenderer, GlyphCache}; use alacritty::sync::FairMutex; -use alacritty::term::{self, Term}; -use alacritty::tty::{self, Pty, process_should_exit}; -use alacritty::window::{self, Window, SetInnerSize, Pixels, Size}; +use alacritty::term::{Term}; +use alacritty::tty::{self, process_should_exit}; -mod cli; fn main() { // Load configuration @@ -86,7 +81,11 @@ fn main() { let options = cli::Options::load(); // Run alacritty - run(config, options); + if let Err(err) = run(config, options) { + die!("{}", err); + } + + println!("Goodbye"); } /// Run Alacritty @@ -156,124 +155,59 @@ fn main() { /// /// instead of the 200 line monster it currently is. /// -fn run(config: Config, options: cli::Options) { +fn run(config: Config, options: cli::Options) -> Result<(), Box> { + // Create a display. + // + // The display manages a window and can draw the terminal + let mut display = Display::new(&config, &options)?; + // Create the terminal + // + // This object contains all of the state about what's being displayed. It's + // wrapped in a clonable mutex since both the I/O loop and display need to + // access it. + let terminal = Arc::new(FairMutex::new(Term::new(display.size().to_owned()))); - // Extract some properties from config - let font = config.font(); - let dpi = config.dpi(); - let render_timer = config.render_timer(); + // Create the pty + // + // The pty forks a process to run the shell on the slave side of the + // pseudoterminal. A file descriptor for the master side is retained for + // reading/writing to the shell. + let pty = Rc::new(tty::new(display.size())); - // Create the window where Alacritty will be displayed - let mut window = match Window::new() { - Ok(window) => window, - Err(err) => die!("{}", err) - }; - - // get window properties for initializing the other subsytems - let size = window.inner_size_pixels().unwrap(); - let dpr = window.hidpi_factor(); - - println!("device_pixel_ratio: {}", dpr); - - let rasterizer = font::Rasterizer::new(dpi.x(), dpi.y(), dpr); - - // Create renderer - let mut renderer = QuadRenderer::new(&config, size); - - // Initialize glyph cache - let glyph_cache = { - println!("Initializing glyph cache"); - let init_start = ::std::time::Instant::now(); - - let cache = renderer.with_loader(|mut api| { - GlyphCache::new(rasterizer, &config, &mut api) - }); - - let stop = init_start.elapsed(); - let stop_f = stop.as_secs() as f64 + stop.subsec_nanos() as f64 / 1_000_000_000f64; - println!("Finished initializing glyph cache in {}", stop_f); - - cache - }; - - // Need font metrics to resize the window properly. This suggests to me the - // font metrics should be computed before creating the window in the first - // place so that a resize is not needed. - let metrics = glyph_cache.font_metrics(); - let cell_width = (metrics.average_advance + font.offset().x() as f64) as u32; - let cell_height = (metrics.line_height + font.offset().y() as f64) as u32; - - // Resize window to specified dimensions - let width = cell_width * options.columns_u32() + 4; - let height = cell_height * options.lines_u32() + 4; - let size = Size { width: Pixels(width), height: Pixels(height) }; - println!("set_inner_size: {}", size); - - window.set_inner_size(size); - renderer.resize(*size.width as _, *size.height as _); - - println!("Cell Size: ({} x {})", cell_width, cell_height); - - let size = term::SizeInfo { - width: *size.width as f32, - height: *size.height as f32, - cell_width: cell_width as f32, - cell_height: cell_height as f32 - }; - - let terminal = Term::new(size); - let pty = tty::new(size.lines(), size.cols()); - pty.resize(size.lines(), size.cols(), size.width as usize, size.height as usize); - let pty_io = pty.reader(); - - let (tx, rx) = mpsc::channel(); - - let signal_flag = Flag::new(false); - - let terminal = Arc::new(FairMutex::new(terminal)); - - // Setup the rsize callback for osx - let terminal_ref = terminal.clone(); - let signal_flag_ref = signal_flag.clone(); - let proxy = window.create_window_proxy(); - let tx2 = tx.clone(); - window.set_resize_callback(move |width, height| { - let _ = tx2.send((width, height)); - if !signal_flag_ref.0.swap(true, Ordering::AcqRel) { - // We raised the signal flag - let mut terminal = terminal_ref.lock(); - terminal.dirty = true; - proxy.wakeup_event_loop(); - } + // When the display is resized, inform the kernel of changes to pty + // dimensions. + // + // TODO: The Rc on pty is needed due to a borrowck error here. The borrow + // checker says that `pty` is still borrowed when it is dropped at the end + // of the `run` function. + let pty_ref = pty.clone(); + display.set_resize_callback(move |size| { + pty_ref.resize(size); }); + // Create the pseudoterminal I/O loop + // + // pty I/O is ran on another thread as to not occupy cycles used by the + // renderer and input processing. Note that access to the terminal state is + // synchronized since the I/O loop updates the state, and the display + // consumes it periodically. let event_loop = EventLoop::new( terminal.clone(), - window.create_window_proxy(), - signal_flag.clone(), - pty_io, + display.notifier(), + pty.reader(), options.ref_test, ); let loop_tx = event_loop.channel(); let event_loop_handle = event_loop.spawn(None); - // Wraps a renderer and gives simple draw() api. - let mut display = Display::new( - &window, - renderer, - glyph_cache, - render_timer, - rx, - pty - ); - // Event processor + let resize_tx = display.resize_channel(); let mut processor = event::Processor::new( input::LoopNotifier(loop_tx), terminal.clone(), - tx, + resize_tx, &config, options.ref_test, ); @@ -284,28 +218,26 @@ fn run(config: Config, options: cli::Options) { let _config_reloader = config.path().map(|path| { config::Watcher::new(path, ConfigHandler { tx: config_tx, - window: window.create_window_proxy(), + loop_kicker: display.notifier(), }) }); // Main loop - let mut force_draw; + let mut config_updated = false; loop { - force_draw = false; // Wait for something to happen - processor.process_events(&window); + processor.process_events(display.window()); // Handle config reloads if let Ok(config) = config_rx.try_recv() { - force_draw = true; + config_updated = 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 { + if terminal.dirty || config_updated { display.draw(terminal, &config); } @@ -317,16 +249,19 @@ fn run(config: Config, options: cli::Options) { // 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 // config_reloader.join().ok(); // shutdown event_loop_handle.join().ok(); - println!("Goodbye"); + + Ok(()) } struct ConfigHandler { tx: mpsc::Sender, - window: window::Proxy, + loop_kicker: display::Notifier, } impl config::OnConfigReload for ConfigHandler { @@ -336,100 +271,7 @@ impl config::OnConfigReload for ConfigHandler { return; } - self.window.wakeup_event_loop(); + self.loop_kicker.notify(); } } -struct Display<'a> { - window: &'a Window, - renderer: QuadRenderer, - glyph_cache: GlyphCache, - render_timer: bool, - rx: mpsc::Receiver<(u32, u32)>, - meter: Meter, - pty: Pty, -} - -impl<'a> Display<'a> { - pub fn update_config(&mut self, config: &Config) { - self.renderer.update_config(config); - self.render_timer = config.render_timer(); - } - - pub fn new( - window: &Window, - renderer: QuadRenderer, - glyph_cache: GlyphCache, - render_timer: bool, - rx: mpsc::Receiver<(u32, u32)>, - pty: Pty - ) -> Display { - Display { - window: window, - renderer: renderer, - glyph_cache: glyph_cache, - render_timer: render_timer, - rx: rx, - meter: Meter::new(), - pty: pty, - } - } - - /// Draw the screen - /// - /// A reference to Term whose state is being drawn must be provided. - /// - /// This call may block if vsync is enabled - pub fn draw(&mut self, mut terminal: MutexGuard, config: &Config) { - terminal.dirty = false; - - // Resize events new_size and are handled outside the poll_events - // iterator. This has the effect of coalescing multiple resize - // events into one. - let mut new_size = None; - - - // Check for any out-of-band resize events (mac only) - while let Ok(sz) = self.rx.try_recv() { - new_size = Some(sz); - } - - // Receive any resize events; only call gl::Viewport on last - // available - if let Some((w, h)) = new_size.take() { - terminal.resize(w as f32, h as f32); - let size = terminal.size_info(); - self.pty.resize(size.lines(), size.cols(), w as _, h as _); - self.renderer.resize(w as i32, h as i32); - } - - { - let glyph_cache = &mut self.glyph_cache; - // Draw grid - { - let _sampler = self.meter.sampler(); - - let size_info = terminal.size_info().clone(); - self.renderer.with_api(config, &size_info, |mut api| { - api.clear(); - - // Draw the grid - api.render_cells(terminal.renderable_cells(), glyph_cache); - }); - } - - // Draw render timer - if self.render_timer { - let timing = format!("{:.3} usec", self.meter.average()); - let color = alacritty::ansi::Color::Spec(Rgb { r: 0xd5, g: 0x4e, b: 0x53 }); - self.renderer.with_api(config, terminal.size_info(), |mut api| { - api.render_string(&timing[..], glyph_cache, &color); - }); - } - } - - // Unlock the terminal mutex - drop(terminal); - self.window.swap_buffers().unwrap(); - } -} diff --git a/src/term/mod.rs b/src/term/mod.rs index 979999a9..da5683e0 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -23,6 +23,20 @@ use grid::{Grid, ClearRegion}; use index::{Cursor, Column, Line}; use ansi::{Color, NamedColor}; +use tty::ToWinsize; +use libc::{self, winsize}; + +impl<'a> ToWinsize for &'a SizeInfo { + fn to_winsize(&self) -> winsize { + winsize { + ws_row: self.lines().0 as libc::c_ushort, + ws_col: self.cols().0 as libc::c_ushort, + ws_xpixel: self.width as libc::c_ushort, + ws_ypixel: self.height as libc::c_ushort, + } + } +} + pub mod cell; pub use self::cell::Cell; diff --git a/src/tty.rs b/src/tty.rs index fddb4d99..7071bad3 100644 --- a/src/tty.rs +++ b/src/tty.rs @@ -23,8 +23,6 @@ use std::ptr; use libc::{self, winsize, c_int, pid_t, WNOHANG, WIFEXITED, WEXITSTATUS, SIGCHLD}; -use index::{Line, Column}; - /// Process ID of child process /// /// Necessary to put this in static storage for `sigchld` to have access @@ -238,8 +236,10 @@ fn execsh() -> ! { } /// Create a new tty and return a handle to interact with it. -pub fn new(lines: Line, cols: Column) -> Pty { - let (master, slave) = openpty(lines.0 as _, cols.0 as _); +pub fn new(size: T) -> Pty { + let win = size.to_winsize(); + + let (master, slave) = openpty(win.ws_row as _, win.ws_col as _); match fork() { Relation::Child => { @@ -282,7 +282,9 @@ pub fn new(lines: Line, cols: Column) -> Pty { set_nonblocking(master); } - Pty { fd: master } + let pty = Pty { fd: master }; + pty.resize(size); + pty } } } @@ -301,15 +303,12 @@ impl Pty { } } - pub fn resize(&self, lines: Line, cols: Column, px_x: usize, px_y: usize) { - let lines = lines.0; - let cols = cols.0; - let win = winsize { - ws_row: lines as libc::c_ushort, - ws_col: cols as libc::c_ushort, - ws_xpixel: px_x as libc::c_ushort, - ws_ypixel: px_y as libc::c_ushort, - }; + /// Resize the pty + /// + /// Tells the kernel that the window size changed with the new pixel + /// dimensions and line/column counts. + pub fn resize(&self, size: T) { + let win = size.to_winsize(); let res = unsafe { libc::ioctl(self.fd, libc::TIOCSWINSZ, &win as *const _) @@ -321,6 +320,12 @@ impl Pty { } } +/// Types that can produce a `libc::winsize` +pub trait ToWinsize { + /// Get a `libc::winsize` + fn to_winsize(&self) -> winsize; +} + unsafe fn set_nonblocking(fd: c_int) { use libc::{fcntl, F_SETFL, F_GETFL, O_NONBLOCK}; diff --git a/src/window.rs b/src/window.rs index 4d8a0c02..f9f2050c 100644 --- a/src/window.rs +++ b/src/window.rs @@ -11,6 +11,8 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; use std::convert::From; use std::fmt::{self, Display}; use std::ops::Deref; @@ -55,10 +57,17 @@ type Result = ::std::result::Result; /// Wraps the underlying windowing library to provide a stable API in Alacritty pub struct Window { glutin_window: glutin::Window, + + /// This flag allows calls to wakeup_event_loop to be coalesced. Waking the + /// event loop is potentially very slow (and indeed is on X11). + flag: Flag } /// Threadsafe APIs for the window -pub struct Proxy(glutin::WindowProxy); +pub struct Proxy { + inner: glutin::WindowProxy, + flag: Flag, +} /// Information about where the window is being displayed /// @@ -89,6 +98,46 @@ pub struct Pixels(pub T); #[derive(Debug, Copy, Clone)] pub struct Points(pub T); +/// A wrapper around glutin's WaitEventsIterator that clears the wakeup +/// optimization flag on drop. +pub struct WaitEventsIterator<'a> { + inner: glutin::WaitEventsIterator<'a>, + flag: Flag +} + +impl<'a> Iterator for WaitEventsIterator<'a> { + type Item = glutin::Event; + + #[inline] + fn next(&mut self) -> Option { + self.inner.next() + } +} + +impl<'a> Drop for WaitEventsIterator<'a> { + fn drop(&mut self) { + self.flag.set(false); + } +} + +#[derive(Clone)] +pub struct Flag(pub Arc); +impl Flag { + pub fn new(initial_value: bool) -> Flag { + Flag(Arc::new(AtomicBool::new(initial_value))) + } + + #[inline] + pub fn get(&self) -> bool { + self.0.load(Ordering::Acquire) + } + + #[inline] + pub fn set(&self, value: bool) { + self.0.store(value, Ordering::Release) + } +} + pub trait ToPoints { fn to_points(&self, scale: f32) -> Size>; } @@ -216,6 +265,7 @@ impl Window { Ok(Window { glutin_window: window, + flag: Flag::new(false), }) } @@ -256,7 +306,10 @@ impl Window { #[inline] pub fn create_window_proxy(&self) -> Proxy { - Proxy(self.glutin_window.create_window_proxy()) + Proxy { + inner: self.glutin_window.create_window_proxy(), + flag: self.flag.clone(), + } } #[inline] @@ -267,11 +320,17 @@ impl Window { } /// Block waiting for events - /// - /// FIXME should return our own type #[inline] - pub fn wait_events(&self) -> glutin::WaitEventsIterator { - self.glutin_window.wait_events() + pub fn wait_events(&self) -> WaitEventsIterator { + WaitEventsIterator { + inner: self.glutin_window.wait_events(), + flag: self.flag.clone() + } + } + + /// Clear the wakeup optimization flag + pub fn clear_wakeup_flag(&self) { + self.flag.set(false); } /// Block waiting for events @@ -289,7 +348,13 @@ impl Proxy { /// This is useful for triggering a draw when the renderer would otherwise /// be waiting on user input. pub fn wakeup_event_loop(&self) { - self.0.wakeup_event_loop(); + // Only wake up the window event loop if it hasn't already been + // signaled. This is a really important optimization because waking up + // the event loop redundantly burns *a lot* of cycles. + if !self.flag.get() { + self.inner.wakeup_event_loop(); + self.flag.set(true); + } } }