Reworks state signalling

This commit is contained in:
Gregor Reitzenstein 2020-11-04 11:51:16 +01:00
parent 21550b411d
commit 3e7127f3df
3 changed files with 73 additions and 76 deletions

View File

@ -35,33 +35,28 @@ pub struct Sute<'a, S> {
// TODO: BE SMART. Inputs change the state, resize signals change the state, futures completing // TODO: BE SMART. Inputs change the state, resize signals change the state, futures completing
// change the state. // change the state.
state: SuteState<'a>, state: Mutable<SuteState>,
signal: S, signal: S,
inputs: Inputs, inputs: Inputs,
api: Option<API>, api: Option<API>,
log: Logger, log: Logger,
drain: Arc<LogDrain<'a>>,
} }
impl<'a, S: Unpin> Sute<'a, S> { impl<'a, S: Unpin> Sute<'a, S> {
pub fn new(s: S, log: Logger, drain: Arc<LogDrain<'a>>, api: API) -> Self { pub fn new(signal: S, log: Logger, drain: Arc<LogDrain<'a>>, api: Option<API>) -> Self {
let inputs = Inputs::new(); let inputs = Inputs::new();
let state = SuteState::new(drain); let state = Mutable::new(SuteState::new());
Self { Self { state, signal, inputs, api, log, drain, }
state: state,
signal: s,
inputs: inputs,
api: Some(api),
log: log,
}
} }
fn run_cmd(&mut self, cmdline: String) { fn run_cmd(&mut self, cmdline: String) {
let mut words = cmdline.split_ascii_whitespace(); let mut words = cmdline.split_ascii_whitespace();
match words.next() { match words.next() {
Some("quit") => self.state.running = false, Some("quit") => self.state.lock_mut().running = false,
Some("connect") => { Some("connect") => {
self.connect(cmdline) self.connect(cmdline)
}, },
@ -74,98 +69,85 @@ impl<'a, S: Unpin> Sute<'a, S> {
} }
fn handle_resize(&mut self, new_size: (u16,u16)) { fn handle_resize(&mut self, new_size: (u16,u16)) {
self.state.size = new_size; self.state.lock_mut().size = new_size;
} }
pub fn get_state(&self) -> SuteState<'a> { pub fn get_state(&self) -> SuteState {
self.state.clone() self.state.get_cloned()
}
// We can make this more efficient by not cloning. But who cares?
pub fn signal(&self) -> impl Signal<Item=SuteState> {
self.state.signal_cloned()
} }
pub fn handle_key(&mut self, key: Key) { pub fn handle_key(&mut self, key: Key) {
let mut state = self.state.lock_mut();
match key { match key {
Key::Char('\n') => { Key::Char('\n') => {
let cmd = mem::replace(&mut self.state.cmd_line, String::new()); let cmd = mem::replace(&mut state.cmd_line, String::new());
// drop the mutably borrowed state here so we can mutably re-borrow self afterwards
mem::drop(state);
self.run_cmd(cmd); self.run_cmd(cmd);
}, },
Key::Char(c) => { Key::Char(c) => {
self.state.cmd_line.push(c); state.cmd_line.push(c);
}, },
Key::Backspace => { Key::Backspace => {
self.state.cmd_line.pop(); state.cmd_line.pop();
}, },
_ => {} _ => {}
} }
} }
} }
impl<'a, S: Signal<Item=(u16,u16)> + Unpin> Signal for Sute<'a, S> { impl <'a, S: Unpin + Signal<Item=(u16,u16)>> Future for Sute<'a, S> {
type Item = SuteState<'a>; type Output = ();
fn poll_change(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> { fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
let mut dirty = false; // Return early if we're not running anymore
if ! self.state.lock_ref().running {
return Poll::Ready(());
}
if let Poll::Ready(Some(size)) = Pin::new(&mut self.signal).poll_change(cx) { if let Poll::Ready(Some(size)) = Pin::new(&mut self.signal).poll_change(cx) {
dirty = true;
self.handle_resize(size); self.handle_resize(size);
} }
match ready!(Pin::new(&mut self.inputs).poll_next(cx)) { match ready!(Pin::new(&mut self.inputs).poll_next(cx)) {
Some(key) => { Some(key) => {
dirty = true;
self.handle_key(key); self.handle_key(key);
}, },
// If the input closes stop the program // If the input closes stop the program
None => { None => {
self.state.running = false; self.state.lock_mut().running = false;
return Poll::Ready(None); return Poll::Ready(());
}, },
} }
if dirty {
Poll::Ready(Some(self.get_state()))
} else {
Poll::Pending Poll::Pending
} }
}
}
impl <'a, S> Future for Sute<'a, S> {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
if self.state.running {
Poll::Pending
} else {
Poll::Ready(())
}
}
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
// TODO: `Signal` struct changes using this struct? pub struct SuteState {
// TODO: If so, procmacro here?
enum SuteStateChange {
active_win(Window),
}
#[derive(Debug, Clone)]
pub struct SuteState<'a> {
pub active_win: Window, pub active_win: Window,
pub size: (u16,u16), pub size: (u16,u16),
pub running: bool, pub running: bool,
pub tick: usize, pub tick: usize,
pub server: Option<&'a str>, pub server: Option<String>,
pub log: Arc<LogDrain<'a>>,
pub cmd_line: String, pub cmd_line: String,
pub tick_c: char, pub tick_c: char,
// TODO: Figure out how to put log lines here signaled
} }
impl<'a> SuteState<'a> { impl SuteState {
pub fn new(log: Arc<LogDrain<'a>>) -> Self { pub fn new() -> Self {
Self { Self {
active_win: Window::Main, active_win: Window::Main,
size: (80,20), size: (80,20),
running: true, running: true,
tick: 0, tick: 0,
server: None, server: None,
log: log,
cmd_line: String::new(), cmd_line: String::new(),
tick_c: '|', tick_c: '|',
} }

View File

@ -69,10 +69,11 @@ fn main() -> Result<(), io::Error> {
let (rpc_future, mut api) = schema::bootstrap(log.clone(), stream); let (rpc_future, mut api) = schema::bootstrap(log.clone(), stream);
let app = app::Sute::new(resize, log.clone(), drain, api); let app = app::Sute::new(resize, log.clone(), drain.clone(), Some(api));
let mut state = app.get_state(); let mut app_state = app.get_state();
let mut stream = app.to_stream(); let mut ui_state = ui::UIState::new(app_state, drain);
let mut stream = app.signal().to_stream();
let ui_future = async move { let ui_future = async move {
let stdout = io::stdout().into_raw_mode()?; let stdout = io::stdout().into_raw_mode()?;
@ -87,7 +88,7 @@ fn main() -> Result<(), io::Error> {
terminal.resize(tui::layout::Rect::new(0, 0, x,y)).unwrap(); terminal.resize(tui::layout::Rect::new(0, 0, x,y)).unwrap();
} }
terminal.draw(|f| ui::draw_ui(f, &mut state)); terminal.draw(|f| ui::draw_ui(f, &mut ui_state));
loop { loop {
if let Some(mut state) = stream.next().await { if let Some(mut state) = stream.next().await {
if !state.running { if !state.running {
@ -102,7 +103,8 @@ fn main() -> Result<(), io::Error> {
_ => '|', _ => '|',
}; };
state.tick_c = tick_c; state.tick_c = tick_c;
terminal.draw(|f| ui::draw_ui(f, &mut state))?; ui_state.app_state = state;
terminal.draw(|f| ui::draw_ui(f, &mut ui_state))?;
} else { } else {
break; break;
} }
@ -114,9 +116,10 @@ fn main() -> Result<(), io::Error> {
Ok(()) Ok(())
}; };
//lex.spawn(rpc_future).detach(); lex.spawn(rpc_future).detach();
let r: Result<(), io::Error> = smol::block_on(lex.run(ui_future)); let t: Task<Result<(), io::Error>> = lex.spawn(ui_future);
//smol::block_on(lex.run(Box::pin(app))); t.detach();
smol::block_on(lex.run(app));
Ok(()) Ok(())
} }

View File

@ -1,3 +1,5 @@
use std::sync::Arc;
use tui::{ use tui::{
backend::Backend, backend::Backend,
layout::{Layout, Direction, Constraint, Rect}, layout::{Layout, Direction, Constraint, Rect},
@ -5,9 +7,19 @@ use tui::{
Frame, Frame,
}; };
use crate::app::SuteState; use crate::app::{SuteState, LogDrain};
pub fn draw_ui<B: Backend>(f: &mut Frame<B>, app: &mut SuteState) { pub struct UIState<'a> {
pub app_state: SuteState,
pub loglines: Arc<LogDrain<'a>>,
}
impl<'a> UIState<'a> {
pub fn new(app_state: SuteState, loglines: Arc<LogDrain<'a>>) -> Self {
Self { app_state, loglines }
}
}
pub fn draw_ui<B: Backend>(f: &mut Frame<B>, state: &mut UIState) {
let outer_layout = Layout::default() let outer_layout = Layout::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
.constraints([ .constraints([
@ -19,32 +31,32 @@ pub fn draw_ui<B: Backend>(f: &mut Frame<B>, app: &mut SuteState) {
.split(f.size()); .split(f.size());
draw_header(f, app, outer_layout[0]); draw_header(f, state, outer_layout[0]);
draw_main(f, app, outer_layout[1]); draw_main(f, state, outer_layout[1]);
draw_command_line(f, app, outer_layout[2]); draw_command_line(f, state, outer_layout[2]);
} }
fn draw_header<B: Backend>(f: &mut Frame<B>, app: &mut SuteState, layout_chunk: Rect) { fn draw_header<B: Backend>(f: &mut Frame<B>, state: &mut UIState, layout_chunk: Rect) {
f.render_widget(Block::default() f.render_widget(Block::default()
.title("Status") .title("Status")
.borders(Borders::ALL), layout_chunk); .borders(Borders::ALL), layout_chunk);
} }
fn draw_main<B: Backend>(f: &mut Frame<B>, app: &mut SuteState, layout_chunk: Rect) { fn draw_main<B: Backend>(f: &mut Frame<B>, state: &mut UIState, layout_chunk: Rect) {
// let chunk = Layout::default() // let chunk = Layout::default()
// .direction(Direction::Horizontal) // .direction(Direction::Horizontal)
// .constraints([Constraint::Percentage(20), Constraint::Percentage(80)].as_ref()) // .constraints([Constraint::Percentage(20), Constraint::Percentage(80)].as_ref())
// .split(layout_chunk); // .split(layout_chunk);
draw_logs(f, app, layout_chunk) draw_logs(f, state, layout_chunk)
} }
fn draw_logs<B: Backend>(f: &mut Frame<B>, app: &mut SuteState, layout_chunk: Rect) { fn draw_logs<B: Backend>(f: &mut Frame<B>, state: &mut UIState, layout_chunk: Rect) {
// TODO: Just use a signal. // TODO: Just use a signal.
let v = app.log.get_inner().clone(); let v = state.loglines.get_inner().clone();
f.render_widget(Paragraph::new(v.into_iter().collect::<Vec<tui::text::Spans>>()), layout_chunk); f.render_widget(Paragraph::new(v), layout_chunk);
} }
fn draw_command_line<B: Backend>(f: &mut Frame<B>, app: &mut SuteState, layout_chunk: Rect) { fn draw_command_line<B: Backend>(f: &mut Frame<B>, state: &mut UIState, layout_chunk: Rect) {
let block = Block::default() let block = Block::default()
.title("Command line") .title("Command line")
.borders(Borders::ALL); .borders(Borders::ALL);
@ -52,6 +64,6 @@ fn draw_command_line<B: Backend>(f: &mut Frame<B>, app: &mut SuteState, layout_c
f.render_widget(block, layout_chunk); f.render_widget(block, layout_chunk);
let cmdline = format!("{} > {}", app.tick_c, app.cmd_line); let cmdline = format!("{} > {}", state.app_state.tick_c, state.app_state.cmd_line);
f.render_widget(Paragraph::new(&cmdline[..]), inner_rect); f.render_widget(Paragraph::new(&cmdline[..]), inner_rect);
} }