From fd755831f2cae4e36206f28b06b205a7a53063e1 Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Mon, 29 Aug 2016 19:23:04 -0700 Subject: [PATCH] Separate input handling from rendering To minimize rendering, the input must be handled in a separate thread. To see, why, consider the optimal rendering solution: renders are only necessary when the pty has data that changes the terminal state, OR there is a window event which changes the graphics state. When not drawing, the render thread is to remain parked at a condition variable, and it's not possible to handle input while parked! Thus, we need a separate thread. In addition to adding the separate thread, each subsystem thread is now spawned in a separate function to (hopefully) improve readability. --- Cargo.lock | 13 ++- Cargo.toml | 4 +- src/main.rs | 242 +++++++++++++++++++++++++++++----------------------- 3 files changed, 147 insertions(+), 112 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6469fafc..29e6d19e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,7 +10,7 @@ dependencies = [ "glutin 0.6.1 (git+https://github.com/jwilm/glutin?rev=22cc23997389bedd948a3ddc3833279ed308a673)", "libc 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", "notify 2.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "parking_lot 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)", "serde_macros 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)", "serde_yaml 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -562,11 +562,20 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.2.6" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "parking_lot_core 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "parking_lot_core" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/Cargo.toml b/Cargo.toml index 1748c99d..a20d4199 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ notify = "2.6" bitflags = "*" font = { path = "./font" } errno = "0.1.6" -parking_lot = "0.2.6" +parking_lot = "0.3.1" serde = "0.7" serde_yaml = "0.2" serde_macros = "0.7" @@ -23,4 +23,4 @@ gl_generator = "0.5" [dependencies.glutin] git = "https://github.com/jwilm/glutin" -rev = "22cc23997389bedd948a3ddc3833279ed308a673" +rev = "45092a8e2f6706a7b2c613ff212341754e346c9c" diff --git a/src/main.rs b/src/main.rs index ef02af51..29201adf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -51,7 +51,6 @@ mod util; mod io; mod sync; -use std::io::{Write, BufWriter, Read}; use std::sync::{mpsc, Arc}; use sync::PriorityMutex; @@ -131,7 +130,7 @@ fn main() { let mut renderer = QuadRenderer::new(width, height); // Initialize glyph cache - let mut glyph_cache = { + let glyph_cache = { println!("Initializing glyph cache"); let init_start = ::std::time::Instant::now(); @@ -154,9 +153,8 @@ fn main() { let terminal = Term::new(width as f32, height as f32, cell_width as f32, cell_height as f32); - let mut reader = terminal.tty().reader(); - let writer = terminal.tty().writer(); - + let reader = terminal.tty().reader(); + let mut writer = terminal.tty().writer(); let (tx, rx) = mpsc::channel(); unsafe { @@ -164,24 +162,83 @@ fn main() { } let terminal = Arc::new(PriorityMutex::new(terminal)); - let term_ref = terminal.clone(); - let term_updater = terminal.clone(); - // Rendering thread - // - // Holds lock only when updating terminal - let reader_thread = thread::spawn_named("TTY Reader", move || { + let pty_reader_thread = spawn_pty_reader(terminal.clone(), reader); + + let window = Arc::new(window); + + let _ = window.clear_current(); + let render_thread = spawn_renderer(window.clone(), + terminal.clone(), + renderer, + glyph_cache, + render_timer, + rx); + + handle_window_events(&mut writer, terminal, window); + + pty_reader_thread.join().ok(); + render_thread.join().ok(); + println!("Goodbye"); +} + +/// Handle window events until the application should close +fn handle_window_events(writer: &mut W, + terminal: Arc>, + window: Arc) + where W: std::io::Write, +{ + let mut input_processor = input::Processor::new(); + let resize_tx = unsafe { resize_sender.as_ref().cloned().unwrap() }; + + for event in window.wait_events() { + match event { + glutin::Event::Closed => break, + glutin::Event::ReceivedCharacter(c) => { + match c { + // Ignore BACKSPACE and DEL. These are handled specially. + '\u{8}' | '\u{7f}' => (), + // OSX arrow keys send invalid characters; ignore. + '\u{f700}' | '\u{f701}' | '\u{f702}' | '\u{f703}' => (), + _ => { + let encoded = c.encode_utf8(); + writer.write(encoded.as_slice()).unwrap(); + } + } + }, + glutin::Event::Resized(w, h) => { + resize_tx.send((w, h)).expect("send new size"); + }, + glutin::Event::KeyboardInput(state, _code, key, mods) => { + // Acquire term lock + let terminal = terminal.lock_high(); + + input_processor.process(state, + key, + mods, + &mut input::WriteNotifier(writer), + *terminal.mode()) + }, + _ => (), + } + } +} + +fn spawn_pty_reader(terminal: Arc>, mut pty: R) -> std::thread::JoinHandle<()> + where R: std::io::Read + Send + 'static, +{ + thread::spawn_named("pty reader", move || { let mut buf = [0u8; 4096]; let mut start = 0; let mut pty_parser = ansi::Parser::new(); - 'reader: loop { - let got = reader.read(&mut buf[start..]).expect("pty fd active"); + loop { + let got = pty.read(&mut buf[start..]).expect("pty fd active"); let mut remain = 0; // if `start` is nonzero, then actual bytes in buffer is > `got` by `start` bytes. let end = start + got; - let mut terminal = term_updater.lock_low(); + let mut terminal = terminal.lock_low(); for c in Utf8Chars::new(&buf[..end]) { match c { Ok(c) => pty_parser.advance(&mut *terminal, c), @@ -201,108 +258,77 @@ fn main() { } start = remain; } - }); + }) +} - let mut meter = Meter::new(); +fn spawn_renderer(window: Arc, + terminal: Arc>, + mut renderer: QuadRenderer, + mut glyph_cache: GlyphCache, + render_timer: bool, + rx: mpsc::Receiver<(u32, u32)>) -> std::thread::JoinHandle<()> { + thread::spawn_named("render", move || { + unsafe { + let _ = window.make_current(); + } + let mut meter = Meter::new(); - let window = Arc::new(window); - let window_ref = window.clone(); - - let mut input_processor = input::Processor::new(); - - 'main_loop: loop { - - // Scope ensures terminal lock isn't held when calling swap_buffers - { - // Acquire term lock - let mut terminal = term_ref.lock_high(); - - // 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; - - // Process input events + 'render_loop: loop { + // Scope ensures terminal lock isn't held when calling swap_buffers { - let mut writer = BufWriter::new(&writer); - for event in window_ref.poll_events() { - match event { - glutin::Event::Closed => break 'main_loop, - glutin::Event::ReceivedCharacter(c) => { - match c { - // Ignore BACKSPACE and DEL. These are handled specially. - '\u{8}' | '\u{7f}' => (), - // OSX arrow keys send invalid characters; ignore. - '\u{f700}' | '\u{f701}' | '\u{f702}' | '\u{f703}' => (), - _ => { - let encoded = c.encode_utf8(); - writer.write(encoded.as_slice()).unwrap(); - } - } - }, - glutin::Event::Resized(w, h) => { - new_size = Some((w, h)); - }, - glutin::Event::KeyboardInput(state, _code, key, mods) => { - input_processor.process(state, - key, - mods, - &mut input::WriteNotifier(&mut writer), - *terminal.mode()) - }, - _ => (), + // Acquire term lock + let mut terminal = terminal.lock_high(); + + // 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; + + unsafe { + gl::ClearColor(0.0, 0.0, 0.00, 1.0); + gl::Clear(gl::COLOR_BUFFER_BIT); + } + + // Check for any out-of-band resize events (mac only) + while let Ok(sz) = 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); + renderer.resize(w as i32, h as i32); + } + + { + // Draw grid + { + let _sampler = meter.sampler(); + + let size_info = terminal.size_info().clone(); + renderer.with_api(&size_info, |mut api| { + // Draw the grid + api.render_grid(&terminal.render_grid(), &mut glyph_cache); + }); + } + + // Draw render timer + if render_timer { + let timing = format!("{:.3} usec", meter.average()); + let color = Rgb { r: 0xd5, g: 0x4e, b: 0x53 }; + renderer.with_api(terminal.size_info(), |mut api| { + api.render_string(&timing[..], &mut glyph_cache, &color); + }); } } } - unsafe { - gl::ClearColor(0.0, 0.0, 0.00, 1.0); - gl::Clear(gl::COLOR_BUFFER_BIT); - } + window.swap_buffers().unwrap(); - // Check for any out-of-band resize events (mac only) - while let Ok(sz) = 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); - renderer.resize(w as i32, h as i32); - } - - { - // Draw grid - { - let _sampler = meter.sampler(); - - let size_info = terminal.size_info().clone(); - renderer.with_api(&size_info, |mut api| { - // Draw the grid - api.render_grid(&terminal.render_grid(), &mut glyph_cache); - }); - } - - // Draw render timer - if render_timer { - let timing = format!("{:.3} usec", meter.average()); - let color = Rgb { r: 0xd5, g: 0x4e, b: 0x53 }; - renderer.with_api(terminal.size_info(), |mut api| { - api.render_string(&timing[..], &mut glyph_cache, &color); - }); - } + if process_should_exit() { + break; } } - - window.swap_buffers().unwrap(); - - if process_should_exit() { - break; - } - } - - reader_thread.join().ok(); - println!("Goodbye"); + }) } -