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.
This commit is contained in:
Joe Wilm 2016-08-29 19:23:04 -07:00
parent 3c5d46c851
commit fd755831f2
3 changed files with 147 additions and 112 deletions

13
Cargo.lock generated
View File

@ -10,7 +10,7 @@ dependencies = [
"glutin 0.6.1 (git+https://github.com/jwilm/glutin?rev=22cc23997389bedd948a3ddc3833279ed308a673)", "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)", "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)", "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 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_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)", "serde_yaml 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
@ -562,11 +562,20 @@ dependencies = [
[[package]] [[package]]
name = "parking_lot" 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" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "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)", "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)", "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
] ]

View File

@ -13,7 +13,7 @@ notify = "2.6"
bitflags = "*" bitflags = "*"
font = { path = "./font" } font = { path = "./font" }
errno = "0.1.6" errno = "0.1.6"
parking_lot = "0.2.6" parking_lot = "0.3.1"
serde = "0.7" serde = "0.7"
serde_yaml = "0.2" serde_yaml = "0.2"
serde_macros = "0.7" serde_macros = "0.7"
@ -23,4 +23,4 @@ gl_generator = "0.5"
[dependencies.glutin] [dependencies.glutin]
git = "https://github.com/jwilm/glutin" git = "https://github.com/jwilm/glutin"
rev = "22cc23997389bedd948a3ddc3833279ed308a673" rev = "45092a8e2f6706a7b2c613ff212341754e346c9c"

View File

@ -51,7 +51,6 @@ mod util;
mod io; mod io;
mod sync; mod sync;
use std::io::{Write, BufWriter, Read};
use std::sync::{mpsc, Arc}; use std::sync::{mpsc, Arc};
use sync::PriorityMutex; use sync::PriorityMutex;
@ -131,7 +130,7 @@ fn main() {
let mut renderer = QuadRenderer::new(width, height); let mut renderer = QuadRenderer::new(width, height);
// Initialize glyph cache // Initialize glyph cache
let mut glyph_cache = { let glyph_cache = {
println!("Initializing glyph cache"); println!("Initializing glyph cache");
let init_start = ::std::time::Instant::now(); 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 terminal = Term::new(width as f32, height as f32, cell_width as f32, cell_height as f32);
let mut reader = terminal.tty().reader(); let reader = terminal.tty().reader();
let writer = terminal.tty().writer(); let mut writer = terminal.tty().writer();
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
unsafe { unsafe {
@ -164,24 +162,83 @@ fn main() {
} }
let terminal = Arc::new(PriorityMutex::new(terminal)); let terminal = Arc::new(PriorityMutex::new(terminal));
let term_ref = terminal.clone();
let term_updater = terminal.clone();
// Rendering thread let pty_reader_thread = spawn_pty_reader(terminal.clone(), reader);
//
// Holds lock only when updating terminal let window = Arc::new(window);
let reader_thread = thread::spawn_named("TTY Reader", move || {
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<W>(writer: &mut W,
terminal: Arc<PriorityMutex<Term>>,
window: Arc<glutin::Window>)
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<R>(terminal: Arc<PriorityMutex<Term>>, 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 buf = [0u8; 4096];
let mut start = 0; let mut start = 0;
let mut pty_parser = ansi::Parser::new(); let mut pty_parser = ansi::Parser::new();
'reader: loop { loop {
let got = reader.read(&mut buf[start..]).expect("pty fd active"); let got = pty.read(&mut buf[start..]).expect("pty fd active");
let mut remain = 0; let mut remain = 0;
// if `start` is nonzero, then actual bytes in buffer is > `got` by `start` bytes. // if `start` is nonzero, then actual bytes in buffer is > `got` by `start` bytes.
let end = start + got; let end = start + got;
let mut terminal = term_updater.lock_low(); let mut terminal = terminal.lock_low();
for c in Utf8Chars::new(&buf[..end]) { for c in Utf8Chars::new(&buf[..end]) {
match c { match c {
Ok(c) => pty_parser.advance(&mut *terminal, c), Ok(c) => pty_parser.advance(&mut *terminal, c),
@ -201,108 +258,77 @@ fn main() {
} }
start = remain; start = remain;
} }
}); })
}
let mut meter = Meter::new(); fn spawn_renderer(window: Arc<glutin::Window>,
terminal: Arc<PriorityMutex<Term>>,
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); 'render_loop: loop {
let window_ref = window.clone(); // Scope ensures terminal lock isn't held when calling swap_buffers
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
{ {
let mut writer = BufWriter::new(&writer); // Acquire term lock
for event in window_ref.poll_events() { let mut terminal = terminal.lock_high();
match event {
glutin::Event::Closed => break 'main_loop, // Resize events new_size and are handled outside the poll_events
glutin::Event::ReceivedCharacter(c) => { // iterator. This has the effect of coalescing multiple resize
match c { // events into one.
// Ignore BACKSPACE and DEL. These are handled specially. let mut new_size = None;
'\u{8}' | '\u{7f}' => (),
// OSX arrow keys send invalid characters; ignore. unsafe {
'\u{f700}' | '\u{f701}' | '\u{f702}' | '\u{f703}' => (), gl::ClearColor(0.0, 0.0, 0.00, 1.0);
_ => { gl::Clear(gl::COLOR_BUFFER_BIT);
let encoded = c.encode_utf8(); }
writer.write(encoded.as_slice()).unwrap();
} // Check for any out-of-band resize events (mac only)
} while let Ok(sz) = rx.try_recv() {
}, new_size = Some(sz);
glutin::Event::Resized(w, h) => { }
new_size = Some((w, h));
}, // Receive any resize events; only call gl::Viewport on last
glutin::Event::KeyboardInput(state, _code, key, mods) => { // available
input_processor.process(state, if let Some((w, h)) = new_size.take() {
key, terminal.resize(w as f32, h as f32);
mods, renderer.resize(w as i32, h as i32);
&mut input::WriteNotifier(&mut writer), }
*terminal.mode())
}, {
_ => (), // 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 { window.swap_buffers().unwrap();
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) if process_should_exit() {
while let Ok(sz) = rx.try_recv() { break;
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);
});
}
} }
} }
})
window.swap_buffers().unwrap();
if process_should_exit() {
break;
}
}
reader_thread.join().ok();
println!("Goodbye");
} }