Make state updates and rendering event driven

The main thing preventing this system being event driven in the past was
input from the keyboard had to be polled separately from pty activity.
This commit adds a thread for the window event iterator and sends them
on the same channel as pty characters.

With that in place, the render loop looks like

    - Block on 1 available input
    - Get all remaining available input that won't cause blocking
    - Render

Which means that rendering is only performed on state changes. This
obsoleted the need for a `dirty` flag in the Term struct.
This commit is contained in:
Joe Wilm 2016-06-09 08:06:47 -07:00
parent 8b1e82f31a
commit aff56a65a4
No known key found for this signature in database
GPG Key ID: 39B57C6972F518DA
2 changed files with 116 additions and 92 deletions

View File

@ -30,7 +30,7 @@ mod ansi;
mod term;
mod util;
use std::sync::mpsc::TryRecvError;
use std::sync::mpsc;
use std::collections::HashMap;
use std::io::{BufReader, Read, BufRead, Write, BufWriter};
use std::sync::Arc;
@ -46,6 +46,64 @@ use meter::Meter;
use util::thread;
use tty::process_should_exit;
/// Things that the render/update thread needs to respond to
#[derive(Debug)]
enum Event {
PtyChar(char),
Glutin(glutin::Event),
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
enum ShouldExit {
Yes,
No
}
fn handle_event<W>(event: Event,
writer: &mut W,
terminal: &mut Term,
pty_parser: &mut ansi::Parser) -> ShouldExit
where W: Write
{
match event {
// Handle char from pty
Event::PtyChar(c) => pty_parser.advance(terminal, c),
// Handle keyboard/mouse input and other window events
Event::Glutin(gevent) => match gevent {
glutin::Event::Closed => return ShouldExit::Yes,
glutin::Event::ReceivedCharacter(c) => {
let encoded = c.encode_utf8();
writer.write(encoded.as_slice()).unwrap();
},
glutin::Event::KeyboardInput(state, _code, key) => {
match state {
glutin::ElementState::Pressed => {
match key {
Some(glutin::VirtualKeyCode::Up) => {
writer.write("\x1b[A".as_bytes()).unwrap();
},
Some(glutin::VirtualKeyCode::Down) => {
writer.write("\x1b[B".as_bytes()).unwrap();
},
Some(glutin::VirtualKeyCode::Left) => {
writer.write("\x1b[D".as_bytes()).unwrap();
},
Some(glutin::VirtualKeyCode::Right) => {
writer.write("\x1b[C".as_bytes()).unwrap();
},
_ => (),
}
},
_ => (),
}
},
_ => ()
}
}
ShouldExit::No
}
#[derive(Debug, Eq, PartialEq, Copy, Clone, Default)]
pub struct Rgb {
r: u8,
@ -130,11 +188,12 @@ fn main() {
gl::Enable(gl::MULTISAMPLE);
}
let (chars_tx, chars_rx) = ::std::sync::mpsc::channel();
let (tx, rx) = mpsc::channel();
let reader_tx = tx.clone();
let reader_thread = thread::spawn_named("TTY Reader", move || {
for c in reader.chars() {
let c = c.unwrap();
chars_tx.send(c).unwrap();
reader_tx.send(Event::PtyChar(c)).unwrap();
}
});
@ -143,50 +202,45 @@ fn main() {
let mut pty_parser = ansi::Parser::new();
'main_loop: loop {
// Handle keyboard/mouse input and other window events
{
let mut writer = BufWriter::new(&writer);
for event in window.poll_events() {
match event {
glutin::Event::Closed => break 'main_loop,
glutin::Event::ReceivedCharacter(c) => {
let encoded = c.encode_utf8();
writer.write(encoded.as_slice()).unwrap();
},
glutin::Event::KeyboardInput(state, _code, key) => {
match state {
glutin::ElementState::Pressed => {
match key {
Some(glutin::VirtualKeyCode::Up) => {
writer.write("\x1b[A".as_bytes()).unwrap();
},
Some(glutin::VirtualKeyCode::Down) => {
writer.write("\x1b[B".as_bytes()).unwrap();
},
Some(glutin::VirtualKeyCode::Left) => {
writer.write("\x1b[D".as_bytes()).unwrap();
},
Some(glutin::VirtualKeyCode::Right) => {
writer.write("\x1b[C".as_bytes()).unwrap();
},
_ => (),
}
},
_ => (),
}
},
_ => ()
}
let window = Arc::new(window);
let window_ref = window.clone();
let input_thread = thread::spawn_named("Input Thread", move || {
for event in window_ref.wait_events() {
tx.send(Event::Glutin(event));
if process_should_exit() {
break;
}
}
});
'main_loop: loop {
// Block waiting for next event
match rx.recv() {
Ok(e) => {
let res = handle_event(e, &mut writer, &mut terminal, &mut pty_parser);
if res == ShouldExit::Yes {
break;
}
},
Err(mpsc::RecvError) => break,
}
// Handle Any events that have been queued
loop {
match chars_rx.try_recv() {
Ok(c) => pty_parser.advance(&mut terminal, c),
Err(TryRecvError::Disconnected) => break 'main_loop,
Err(TryRecvError::Empty) => break,
match rx.try_recv() {
Ok(e) => {
let res = handle_event(e, &mut writer, &mut terminal, &mut pty_parser);
if res == ShouldExit::Yes {
break;
}
},
Err(mpsc::TryRecvError::Disconnected) => break 'main_loop,
Err(mpsc::TryRecvError::Empty) => break,
}
// TODO make sure this doesn't block renders
}
unsafe {
@ -194,39 +248,36 @@ fn main() {
gl::Clear(gl::COLOR_BUFFER_BIT);
}
if terminal.dirty() {
{
let _sampler = meter.sampler();
{
let _sampler = meter.sampler();
renderer.with_api(&props, |mut api| {
// Draw the grid
api.render_grid(terminal.grid(), &mut glyph_cache);
// Also draw the cursor
if !terminal.mode().contains(term::mode::TEXT_CURSOR) {
api.render_cursor(terminal.cursor(), &mut glyph_cache);
}
})
}
// Draw render timer
let timing = format!("{:.3} usec", meter.average());
let color = Rgb { r: 0xd5, g: 0x4e, b: 0x53 };
renderer.with_api(&props, |mut api| {
api.render_string(&timing[..], &mut glyph_cache, &color);
});
// Draw the grid
api.render_grid(terminal.grid(), &mut glyph_cache);
terminal.clear_dirty();
window.swap_buffers().unwrap();
// Also draw the cursor
if !terminal.mode().contains(term::mode::TEXT_CURSOR) {
api.render_cursor(terminal.cursor(), &mut glyph_cache);
}
})
}
// Draw render timer
let timing = format!("{:.3} usec", meter.average());
let color = Rgb { r: 0xd5, g: 0x4e, b: 0x53 };
renderer.with_api(&props, |mut api| {
api.render_string(&timing[..], &mut glyph_cache, &color);
});
window.swap_buffers().unwrap();
if process_should_exit() {
break;
}
}
reader_thread.join();
reader_thread.join().ok();
input_thread.join().ok();
println!("Goodbye");
}

View File

@ -105,9 +105,6 @@ pub struct Term {
/// Cell attributes
attr: grid::CellFlags,
/// Whether state has changed and needs to be updated.
dirty: bool,
/// Mode flags
mode: TermMode,
@ -136,20 +133,11 @@ impl Term {
tty: tty,
tabs: tabs,
attr: CellFlags::empty(),
dirty: false,
mode: TermMode::empty(),
scroll_region: scroll_region,
}
}
pub fn dirty(&self) -> bool {
self.dirty
}
pub fn clear_dirty(&mut self) {
self.dirty = false;
}
pub fn grid(&self) -> &Grid {
&self.grid
}
@ -190,7 +178,6 @@ impl Term {
/// Set character in current cursor position
fn set_char(&mut self, c: char) {
self.dirty = true;
if self.cursor.x == self.grid.num_cols() as u16 {
println!("wrapping");
self.cursor.y += 1;
@ -253,17 +240,14 @@ impl ansi::Handler for Term {
fn goto(&mut self, x: i64, y: i64) {
println!("goto: x={}, y={}", x, y);
self.dirty = true;
self.cursor.goto(x as u16, y as u16);
}
fn goto_row(&mut self, y: i64) {
self.dirty = true;
println!("goto_row: {}", y);
let x = self.cursor_x();
self.cursor.goto(x, y as u16);
}
fn goto_col(&mut self, x: i64) {
self.dirty = true;
println!("goto_col: {}", x);
let y = self.cursor_y();
self.cursor.goto(x as u16, y);
@ -272,25 +256,21 @@ impl ansi::Handler for Term {
fn insert_blank(&mut self, num: i64) { println!("insert_blank: {}", num); }
fn move_up(&mut self, rows: i64) {
self.dirty = true;
println!("move_up: {}", rows);
self.cursor.advance(-rows, 0);
}
fn move_down(&mut self, rows: i64) {
self.dirty = true;
println!("move_down: {}", rows);
self.cursor.advance(rows, 0);
}
fn move_forward(&mut self, cols: i64) {
self.dirty = true;
println!("move_forward: {}", cols);
self.cursor.advance(0, cols);
}
fn move_backward(&mut self, spaces: i64) {
self.dirty = true;
println!("move_backward: {}", spaces);
self.cursor.advance(0, -spaces);
}
@ -299,7 +279,6 @@ impl ansi::Handler for Term {
fn move_down_and_cr(&mut self, rows: i64) { println!("move_down_and_cr: {}", rows); }
fn move_up_and_cr(&mut self, rows: i64) { println!("move_up_and_cr: {}", rows); }
fn put_tab(&mut self, mut count: i64) {
self.dirty = true;
println!("put_tab: {}", count);
let mut x = self.cursor_x();
@ -320,7 +299,6 @@ impl ansi::Handler for Term {
#[inline]
fn backspace(&mut self, count: i64) {
println!("backspace");
self.dirty = true;
self.cursor.x -= 1;
self.set_char(' ');
}
@ -329,14 +307,12 @@ impl ansi::Handler for Term {
#[inline]
fn carriage_return(&mut self) {
println!("carriage_return");
self.dirty = true;
self.cursor.x = 0;
}
/// Linefeed
#[inline]
fn linefeed(&mut self) {
self.dirty = true;
println!("linefeed");
// TODO handle scroll? not clear what parts of this the pty handle
if self.cursor_y() + 1 >= self.scroll_region.end as u16 {
@ -378,7 +354,6 @@ impl ansi::Handler for Term {
fn save_cursor_position(&mut self) { println!("save_cursor_position"); }
fn restore_cursor_position(&mut self) { println!("restore_cursor_position"); }
fn clear_line(&mut self, mode: ansi::LineClearMode) {
self.dirty = true;
println!("clear_line: {:?}", mode);
match mode {
ansi::LineClearMode::Right => {
@ -393,7 +368,6 @@ impl ansi::Handler for Term {
}
}
fn clear_screen(&mut self, mode: ansi::ClearMode) {
self.dirty = true;
println!("clear_screen: {:?}", mode);
match mode {
ansi::ClearMode::Below => {
@ -417,7 +391,6 @@ impl ansi::Handler for Term {
fn clear_tabs(&mut self, mode: ansi::TabulationClearMode) { println!("clear_tabs: {:?}", mode); }
fn reset_state(&mut self) { println!("reset_state"); }
fn reverse_index(&mut self) {
self.dirty = true;
println!("reverse_index");
// if cursor is at the top
if self.cursor.y == 0 {