From c0131aca625ebd6415d2d6474c8f709da20062d2 Mon Sep 17 00:00:00 2001 From: Condorra Date: Fri, 16 Aug 2024 23:22:21 +1000 Subject: [PATCH] Refactor command dispatch and state handling. --- Cargo.lock | 1 + Cargo.toml | 1 + src/command_handler.rs | 33 ++----- src/lua_state.rs | 49 +++++---- src/main.rs | 20 ++-- src/term_view.rs | 220 +++++++++++++++++++++-------------------- 6 files changed, 167 insertions(+), 157 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fb9187f..d3d94f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1415,6 +1415,7 @@ dependencies = [ name = "worldwideportal" version = "0.1.0" dependencies = [ + "anyhow", "console_error_panic_hook", "im", "itertools 0.13.0", diff --git a/Cargo.toml b/Cargo.toml index 9f12617..436efc6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,3 +19,4 @@ minicrossterm = { git = "https://git.blastmud.org/blasthavers/minicrossterm.git" thiserror = "1.0.63" console_error_panic_hook = "0.1.7" ouroboros = "0.18.4" +anyhow = "1.0.86" diff --git a/src/command_handler.rs b/src/command_handler.rs index 61e45a4..a3b31ec 100644 --- a/src/command_handler.rs +++ b/src/command_handler.rs @@ -1,28 +1,25 @@ use itertools::join; -use std::{cell::RefCell, rc::Rc}; -use yew::Callback; use crate::{ - echo_to_term_frame, lua_state::LuaState, parsing::parse_commands, RegisteredTermFrameLens, - TermFrame, + echo_to_term_frame, lua_state::LuaState, parsing::parse_commands, GlobalCell, TermFrame, }; fn reentrant_command_handler( lua_state: &mut LuaState, - frames: &RegisteredTermFrameLens, + globals: &GlobalCell, term_frame: &TermFrame, command_in: &str, ) { web_sys::console::log_1(&"Inside command handler".into()); - echo_to_term_frame(frames, term_frame, "Hello World!\n"); + echo_to_term_frame(globals, term_frame, "Hello World!\n").unwrap_or(()); for command in parse_commands(command_in).commands { match command.split_out_command() { None => (), Some((cmd, rest)) => { if cmd == "##" { match lua_state.execute(&join(rest.arguments.iter(), " ")) { - Ok(msg) => echo_to_term_frame(frames, term_frame, &msg).unwrap_or(()), - Err(msg) => echo_to_term_frame(frames, term_frame, &msg).unwrap_or(()), + Ok(msg) => echo_to_term_frame(globals, term_frame, &msg).unwrap_or(()), + Err(msg) => echo_to_term_frame(globals, term_frame, &msg).unwrap_or(()), } } } @@ -30,28 +27,16 @@ fn reentrant_command_handler( } } -fn command_handler( - lua_state: &RefCell, - frames: &RegisteredTermFrameLens, - term_frame: &TermFrame, - command_in: String, -) { - match lua_state.try_borrow_mut() { +pub fn command_handler(globals: &GlobalCell, term_frame: &TermFrame, command_in: &str) { + match globals.lua_engine.try_borrow_mut() { Err(_) => echo_to_term_frame( - frames, + globals, term_frame, "Attempt to re-enter command handler during processing.\n", ) .unwrap_or(()), // Ignore error handling error. Ok(mut lua_state_m) => { - reentrant_command_handler(&mut lua_state_m, frames, term_frame, &command_in) + reentrant_command_handler(&mut lua_state_m, globals, term_frame, command_in) } } } - -pub fn make_command_handler_callback( - lua_state: Rc>, - frames: RegisteredTermFrameLens, -) -> Callback<(TermFrame, String), ()> { - Callback::from(move |(term, cmd)| command_handler(&lua_state, &frames, &term, cmd)) -} diff --git a/src/lua_state.rs b/src/lua_state.rs index dfcc1a5..f339f58 100644 --- a/src/lua_state.rs +++ b/src/lua_state.rs @@ -1,6 +1,7 @@ +use anyhow::Error; use piccolo::{Callback, Closure, Context, Executor, IntoValue, Lua, StashedExecutor, Table}; -use crate::{echo_to_term_frame, RegisteredTermFrameLens, TermFrame}; +use crate::{echo_to_term_frame, GlobalCell, TermFrame}; use std::str; pub struct LuaState { @@ -9,22 +10,10 @@ pub struct LuaState { } impl LuaState { - pub fn setup(frames: RegisteredTermFrameLens) -> Result { + pub fn setup() -> Result { let mut interp = Lua::core(); - let exec: StashedExecutor = interp.enter(|ctx| { - let cmd_table = Table::new(&ctx); - cmd_table - .set( - ctx, - ctx.intern_static(b"echo_frame"), - echo_frame(ctx, frames), - ) - .map_err(|_| "Can't add command")?; - ctx.set_global(ctx.intern_static(b"commands").into_value(ctx), cmd_table) - .map(|_| ()) - .map_err(|_| "Can't set commands key".to_owned())?; - Ok::(ctx.stash(Executor::new(ctx))) - })?; + let exec: StashedExecutor = + interp.enter(|ctx| Ok::(ctx.stash(Executor::new(ctx))))?; Ok(LuaState { interp, exec }) } @@ -42,13 +31,37 @@ impl LuaState { } } -fn echo_frame(ctx: Context, frames: RegisteredTermFrameLens) -> Callback { +pub fn install_lua_globals(global: &GlobalCell) -> Result<(), String> { + global + .lua_engine + .borrow_mut() + .interp + .try_enter(|ctx| { + let cmd_table = Table::new(&ctx); + cmd_table + .set( + ctx, + ctx.intern_static(b"echo_frame"), + echo_frame(ctx, global.clone()), + ) + .map_err(|_| Error::msg("Can't add command"))?; + ctx.set_global(ctx.intern_static(b"commands").into_value(ctx), cmd_table) + .map(|_| ()) + .map_err(|_| Error::msg("Can't set commands key"))?; + Ok(()) + }) + .map_err(|e| e.to_string())?; + + Ok(()) +} + +fn echo_frame(ctx: Context, global: GlobalCell) -> Callback { Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { let frame_no: u64 = stack.consume(ctx)?; let message: piccolo::String = stack.consume(ctx)?; let message_str = str::from_utf8(message.as_bytes()) .map_err(|_| "Expected message to echo to be UTF-8.".into_value(ctx))?; - echo_to_term_frame(&frames, &TermFrame(frame_no), message_str) + echo_to_term_frame(&global, &TermFrame(frame_no), message_str) .map_err(|m| m.into_value(ctx))?; Ok(piccolo::CallbackReturn::Return) }) diff --git a/src/main.rs b/src/main.rs index e2ae3b1..18e8d24 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,15 +8,16 @@ pub mod lineengine; pub mod lua_state; pub mod parsing; pub mod term_view; -use crate::command_handler::*; -use crate::lua_state::LuaState; +use crate::lua_state::{install_lua_globals, LuaState}; use crate::term_view::*; #[derive(Properties)] -struct GlobalState { - frame_registry: RegisteredTermFrames, - lua_engine: LuaState, +pub struct GlobalState { + // No strong references allowed between each of these groups of state. + frame_registry: RefCell, + lua_engine: RefCell, } +type GlobalCell = Rc; // Used only for yew. Always equal since we don't want it to // actually look into GlobalState, changes there should never @@ -29,15 +30,16 @@ impl PartialEq for GlobalState { #[function_component(App)] fn app() -> Html { - let global = use_mut_ref(|| Global { + let global = use_memo((), |_| GlobalState { frame_registry: RegisteredTermFrames::new().into(), - lua_engine: LuaState::setup(frames.clone()).expect("Can create interpreter"), + lua_engine: LuaState::setup().expect("Can create interpreter").into(), }); + install_lua_globals(&global).expect("Couldn't install Lua globals"); html! {
- - + +
} } diff --git a/src/term_view.rs b/src/term_view.rs index a648872..e1aa0cf 100644 --- a/src/term_view.rs +++ b/src/term_view.rs @@ -1,14 +1,14 @@ -use std::{ - cell::RefCell, - rc::{Rc, Weak}, -}; +use std::collections::HashMap; -use im::hashmap::*; use wasm_bindgen::prelude::*; use web_sys::{Element, Node}; use yew::prelude::*; -use crate::lineengine::line::{Readline, ReadlineEvent}; +use crate::{ + command_handler::command_handler, + lineengine::line::{Readline, ReadlineEvent}, + GlobalCell, +}; #[wasm_bindgen] extern "C" { @@ -73,8 +73,8 @@ pub struct TermFrameData { pub term: Terminal, pub fit: FitAddon, pub node: Node, - pub readline: RefCell, - pub retained_closures: RefCell, Closure)>>, + pub readline: Readline, + pub retained_closures: Option<(Closure, Closure)>, } impl PartialEq for TermFrameData { @@ -84,109 +84,114 @@ impl PartialEq for TermFrameData { } } -pub type RegisteredTermFrames = HashMap>; -#[derive(Properties, PartialEq, Clone)] -pub struct RegisteredTermFrameLens { - pub get: Callback<(), Rc>, - pub set: Callback, ()>, -} +pub type RegisteredTermFrames = HashMap; -fn get_or_make_term_frame( +fn get_or_make_term_frame<'a>( frame: &TermFrame, - frames: &RegisteredTermFrameLens, - handler: &Callback<(TermFrame, String), ()>, -) -> Rc { - if let Some(tfd) = frames.get.emit(()).get(frame) { - return tfd.clone(); - } + frames: &'a mut RegisteredTermFrames, + // Only used for callbacks, expected frames already borrowed mut! + globals: &GlobalCell, +) -> &'a TermFrameData { + frames.entry(frame.clone()).or_insert_with(|| { + let term = Terminal::new(); + let fit = FitAddon::new(); + let element = web_sys::window() + .and_then(|w| w.document()) + .and_then(|d| d.create_element("div").ok()) + .expect("Can create element for term"); + element.set_class_name("hterminal"); + term.open(&element); + term.loadAddon(&fit); + fit.fit(); + for i in 0..100 { + term.write(&format!("{} Hello world\r\n", i)); + } + let term_for_readline: Terminal = Terminal { obj: term.clone() }; + let initial_size = (term.cols(), term.rows()); + let mut new_data: TermFrameData = TermFrameData { + id: frame.clone(), + term: Terminal { obj: term.clone() }, + fit, + node: element.into(), + readline: Readline::new( + "".to_owned(), + Box::new(move |dat| { + term_for_readline.write( + std::str::from_utf8(dat).expect("readline tried to emit invalid UTF-8"), + ) + }), + initial_size, + ) + .into(), + retained_closures: None, + }; - let mut new_frames: RegisteredTermFrames = (*frames.get.emit(())).clone(); - let term = Terminal::new(); - let fit = FitAddon::new(); - let element = web_sys::window() - .and_then(|w| w.document()) - .and_then(|d| d.create_element("div").ok()) - .expect("Can create element for term"); - element.set_class_name("hterminal"); - term.open(&element); - term.loadAddon(&fit); - fit.fit(); - for i in 0..100 { - term.write(&format!("{} Hello world\r\n", i)); - } - let term_for_readline: Terminal = Terminal { obj: term.clone() }; - let initial_size = (term.cols(), term.rows()); - let new_data: Rc = TermFrameData { - id: frame.clone(), - term: Terminal { obj: term.clone() }, - fit, - node: element.into(), - readline: Readline::new( - "".to_owned(), - Box::new(move |dat| { - term_for_readline - .write(std::str::from_utf8(dat).expect("readline tried to emit invalid UTF-8")) - }), - initial_size, - ) - .into(), - retained_closures: RefCell::new(None), - } - .into(); + let frame_for_resize: TermFrame = frame.clone(); + let globals_for_resize: GlobalCell = globals.clone(); + let resize_closure = Closure::new(move |dims: Dims| { + globals_for_resize + .frame_registry + .try_borrow_mut() + .ok() + .and_then(|mut frame_reg| { + frame_reg.get_mut(&frame_for_resize).and_then(|frame| { + frame + .readline + .handle_resize((dims.cols(), dims.rows())) + .ok() + }) + }) + .unwrap_or(()) + }); + term.onResize(&resize_closure); - let data_for_resize: Weak = Rc::downgrade(&new_data); - let resize_closure = Closure::new(move |dims: Dims| match Weak::upgrade(&data_for_resize) { - None => {} - Some(r) => match r.readline.try_borrow_mut() { - Err(_) => {} - Ok(mut v) => v.handle_resize((dims.cols(), dims.rows())).unwrap_or(()), - }, - }); - term.onResize(&resize_closure); - - let cloned_handler = (*handler).clone(); - let emit_frame = frame.clone(); - let data_for_on_data: Weak = Rc::downgrade(&new_data); - - let data_closure = Closure::new(move |d: String| match Weak::upgrade(&data_for_on_data) { - None => {} - Some(r) => match r.readline.try_borrow_mut() { - Err(_) => {} - Ok(mut v) => { - for ev in v.readline(d.as_bytes()).expect("Readline failed") { - match ev { - ReadlineEvent::Line(l) => cloned_handler.emit((emit_frame.clone(), l)), - _ => {} - } - } + let frame_for_ondata: TermFrame = frame.clone(); + let globals_for_ondata: GlobalCell = globals.clone(); + let data_closure = Closure::new(move |d: String| { + let new_lines: Vec = match globals_for_ondata + .frame_registry + .try_borrow_mut() + .expect("Unexpected failure to borrow frame registry in onData") + .get_mut(&frame_for_ondata) + { + None => vec![], + Some(frame) => frame + .readline + .readline(d.as_bytes()) + .expect("Readline failed") + .into_iter() + .filter_map(|ev| match ev { + ReadlineEvent::Line(l) => Some(l), + _ => None, + }) + .collect(), + }; + // We deliberately stop borrowing anything from RefCells here since command_handler has + // lots of its own borrows, and we don't want them to conflict. + for cmd in &new_lines { + command_handler(&globals_for_ondata, &frame_for_ondata, cmd); } - }, - }); + }); - term.onData(&data_closure); - new_data - .retained_closures - .replace(Some((data_closure, resize_closure))); + term.onData(&data_closure); + new_data + .retained_closures + .replace((data_closure, resize_closure)); - new_frames.insert(frame.clone(), new_data.clone()); - web_sys::console::log_2( - &"Setting frames to have length: ".into(), - &new_frames.iter().count().into(), - ); - frames.set.emit(new_frames.into()); - new_data + new_data + }) } #[derive(Properties, PartialEq)] pub struct TermViewProps { pub terminal: TermFrame, - pub frames: RegisteredTermFrameLens, - pub handler: Callback<(TermFrame, String), ()>, + pub global: GlobalCell, } #[function_component(TermView)] pub fn term_view(props: &TermViewProps) -> Html { - let term = get_or_make_term_frame(&props.terminal, &props.frames, &props.handler); + let mut frame_reg = props.global.frame_registry.borrow_mut(); + let term = get_or_make_term_frame(&props.terminal, &mut frame_reg, &props.global); term.fit.fit(); html! { {Html::VRef(term.node.clone())} @@ -194,19 +199,22 @@ pub fn term_view(props: &TermViewProps) -> Html { } pub fn echo_to_term_frame( - frames: &RegisteredTermFrameLens, + global: &GlobalCell, frame_id: &TermFrame, message: &str, ) -> Result<(), &'static str> { - let frame_val = frames.get.emit(()); - let frame: &TermFrameData = frame_val.get(frame_id).ok_or_else(|| { - web_sys::console::log_3( - &"Attempt to echo to frame that doesn't exist.".into(), - &frame_id.0.into(), - &frame_val.iter().count().into(), - ); - "Attempt to echo to frame that doesn't exist." - })?; - frame.term.write(message); + global + .frame_registry + .borrow() + .get(frame_id) + .ok_or_else(|| { + web_sys::console::log_2( + &"Attempt to echo to frame that doesn't exist.".into(), + &frame_id.0.into(), + ); + "Attempt to echo to frame that doesn't exist." + })? + .term + .write(message); Ok(()) }