Refactor command dispatch and state handling.

This commit is contained in:
Condorra 2024-08-16 23:22:21 +10:00
parent 1b14973b1d
commit c0131aca62
6 changed files with 167 additions and 157 deletions

1
Cargo.lock generated
View File

@ -1415,6 +1415,7 @@ dependencies = [
name = "worldwideportal" name = "worldwideportal"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow",
"console_error_panic_hook", "console_error_panic_hook",
"im", "im",
"itertools 0.13.0", "itertools 0.13.0",

View File

@ -19,3 +19,4 @@ minicrossterm = { git = "https://git.blastmud.org/blasthavers/minicrossterm.git"
thiserror = "1.0.63" thiserror = "1.0.63"
console_error_panic_hook = "0.1.7" console_error_panic_hook = "0.1.7"
ouroboros = "0.18.4" ouroboros = "0.18.4"
anyhow = "1.0.86"

View File

@ -1,28 +1,25 @@
use itertools::join; use itertools::join;
use std::{cell::RefCell, rc::Rc};
use yew::Callback;
use crate::{ use crate::{
echo_to_term_frame, lua_state::LuaState, parsing::parse_commands, RegisteredTermFrameLens, echo_to_term_frame, lua_state::LuaState, parsing::parse_commands, GlobalCell, TermFrame,
TermFrame,
}; };
fn reentrant_command_handler( fn reentrant_command_handler(
lua_state: &mut LuaState, lua_state: &mut LuaState,
frames: &RegisteredTermFrameLens, globals: &GlobalCell,
term_frame: &TermFrame, term_frame: &TermFrame,
command_in: &str, command_in: &str,
) { ) {
web_sys::console::log_1(&"Inside command handler".into()); 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 { for command in parse_commands(command_in).commands {
match command.split_out_command() { match command.split_out_command() {
None => (), None => (),
Some((cmd, rest)) => { Some((cmd, rest)) => {
if cmd == "##" { if cmd == "##" {
match lua_state.execute(&join(rest.arguments.iter(), " ")) { match lua_state.execute(&join(rest.arguments.iter(), " ")) {
Ok(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(frames, 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( pub fn command_handler(globals: &GlobalCell, term_frame: &TermFrame, command_in: &str) {
lua_state: &RefCell<LuaState>, match globals.lua_engine.try_borrow_mut() {
frames: &RegisteredTermFrameLens,
term_frame: &TermFrame,
command_in: String,
) {
match lua_state.try_borrow_mut() {
Err(_) => echo_to_term_frame( Err(_) => echo_to_term_frame(
frames, globals,
term_frame, term_frame,
"Attempt to re-enter command handler during processing.\n", "Attempt to re-enter command handler during processing.\n",
) )
.unwrap_or(()), // Ignore error handling error. .unwrap_or(()), // Ignore error handling error.
Ok(mut lua_state_m) => { 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<RefCell<LuaState>>,
frames: RegisteredTermFrameLens,
) -> Callback<(TermFrame, String), ()> {
Callback::from(move |(term, cmd)| command_handler(&lua_state, &frames, &term, cmd))
}

View File

@ -1,6 +1,7 @@
use anyhow::Error;
use piccolo::{Callback, Closure, Context, Executor, IntoValue, Lua, StashedExecutor, Table}; 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; use std::str;
pub struct LuaState { pub struct LuaState {
@ -9,22 +10,10 @@ pub struct LuaState {
} }
impl LuaState { impl LuaState {
pub fn setup(frames: RegisteredTermFrameLens) -> Result<LuaState, String> { pub fn setup() -> Result<LuaState, String> {
let mut interp = Lua::core(); let mut interp = Lua::core();
let exec: StashedExecutor = interp.enter(|ctx| { let exec: StashedExecutor =
let cmd_table = Table::new(&ctx); interp.enter(|ctx| Ok::<StashedExecutor, String>(ctx.stash(Executor::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::<StashedExecutor, String>(ctx.stash(Executor::new(ctx)))
})?;
Ok(LuaState { interp, exec }) 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| { Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
let frame_no: u64 = stack.consume(ctx)?; let frame_no: u64 = stack.consume(ctx)?;
let message: piccolo::String = stack.consume(ctx)?; let message: piccolo::String = stack.consume(ctx)?;
let message_str = str::from_utf8(message.as_bytes()) let message_str = str::from_utf8(message.as_bytes())
.map_err(|_| "Expected message to echo to be UTF-8.".into_value(ctx))?; .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))?; .map_err(|m| m.into_value(ctx))?;
Ok(piccolo::CallbackReturn::Return) Ok(piccolo::CallbackReturn::Return)
}) })

View File

@ -8,15 +8,16 @@ pub mod lineengine;
pub mod lua_state; pub mod lua_state;
pub mod parsing; pub mod parsing;
pub mod term_view; pub mod term_view;
use crate::command_handler::*; use crate::lua_state::{install_lua_globals, LuaState};
use crate::lua_state::LuaState;
use crate::term_view::*; use crate::term_view::*;
#[derive(Properties)] #[derive(Properties)]
struct GlobalState { pub struct GlobalState {
frame_registry: RegisteredTermFrames, // No strong references allowed between each of these groups of state.
lua_engine: LuaState, frame_registry: RefCell<RegisteredTermFrames>,
lua_engine: RefCell<LuaState>,
} }
type GlobalCell = Rc<GlobalState>;
// Used only for yew. Always equal since we don't want it to // Used only for yew. Always equal since we don't want it to
// actually look into GlobalState, changes there should never // actually look into GlobalState, changes there should never
@ -29,15 +30,16 @@ impl PartialEq for GlobalState {
#[function_component(App)] #[function_component(App)]
fn app() -> Html { fn app() -> Html {
let global = use_mut_ref(|| Global { let global = use_memo((), |_| GlobalState {
frame_registry: RegisteredTermFrames::new().into(), 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! { html! {
<div class="vpane toplevel"> <div class="vpane toplevel">
<TermView terminal={TermFrame(0)} frames={frames.clone()} handler={command_handler.clone()}/> <TermView terminal={TermFrame(0)} global={global.clone()}/>
<TermView terminal={TermFrame(1)} frames={frames.clone()} handler={command_handler.clone()}/> <TermView terminal={TermFrame(1)} global={global.clone()}/>
</div> </div>
} }
} }

View File

@ -1,14 +1,14 @@
use std::{ use std::collections::HashMap;
cell::RefCell,
rc::{Rc, Weak},
};
use im::hashmap::*;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use web_sys::{Element, Node}; use web_sys::{Element, Node};
use yew::prelude::*; use yew::prelude::*;
use crate::lineengine::line::{Readline, ReadlineEvent}; use crate::{
command_handler::command_handler,
lineengine::line::{Readline, ReadlineEvent},
GlobalCell,
};
#[wasm_bindgen] #[wasm_bindgen]
extern "C" { extern "C" {
@ -73,8 +73,8 @@ pub struct TermFrameData {
pub term: Terminal, pub term: Terminal,
pub fit: FitAddon, pub fit: FitAddon,
pub node: Node, pub node: Node,
pub readline: RefCell<Readline>, pub readline: Readline,
pub retained_closures: RefCell<Option<(Closure<dyn FnMut(String)>, Closure<dyn FnMut(Dims)>)>>, pub retained_closures: Option<(Closure<dyn FnMut(String)>, Closure<dyn FnMut(Dims)>)>,
} }
impl PartialEq for TermFrameData { impl PartialEq for TermFrameData {
@ -84,23 +84,15 @@ impl PartialEq for TermFrameData {
} }
} }
pub type RegisteredTermFrames = HashMap<TermFrame, Rc<TermFrameData>>; pub type RegisteredTermFrames = HashMap<TermFrame, TermFrameData>;
#[derive(Properties, PartialEq, Clone)]
pub struct RegisteredTermFrameLens {
pub get: Callback<(), Rc<RegisteredTermFrames>>,
pub set: Callback<Rc<RegisteredTermFrames>, ()>,
}
fn get_or_make_term_frame( fn get_or_make_term_frame<'a>(
frame: &TermFrame, frame: &TermFrame,
frames: &RegisteredTermFrameLens, frames: &'a mut RegisteredTermFrames,
handler: &Callback<(TermFrame, String), ()>, // Only used for callbacks, expected frames already borrowed mut!
) -> Rc<TermFrameData> { globals: &GlobalCell,
if let Some(tfd) = frames.get.emit(()).get(frame) { ) -> &'a TermFrameData {
return tfd.clone(); frames.entry(frame.clone()).or_insert_with(|| {
}
let mut new_frames: RegisteredTermFrames = (*frames.get.emit(())).clone();
let term = Terminal::new(); let term = Terminal::new();
let fit = FitAddon::new(); let fit = FitAddon::new();
let element = web_sys::window() let element = web_sys::window()
@ -116,7 +108,7 @@ fn get_or_make_term_frame(
} }
let term_for_readline: Terminal = Terminal { obj: term.clone() }; let term_for_readline: Terminal = Terminal { obj: term.clone() };
let initial_size = (term.cols(), term.rows()); let initial_size = (term.cols(), term.rows());
let new_data: Rc<TermFrameData> = TermFrameData { let mut new_data: TermFrameData = TermFrameData {
id: frame.clone(), id: frame.clone(),
term: Terminal { obj: term.clone() }, term: Terminal { obj: term.clone() },
fit, fit,
@ -124,69 +116,82 @@ fn get_or_make_term_frame(
readline: Readline::new( readline: Readline::new(
"".to_owned(), "".to_owned(),
Box::new(move |dat| { Box::new(move |dat| {
term_for_readline term_for_readline.write(
.write(std::str::from_utf8(dat).expect("readline tried to emit invalid UTF-8")) std::str::from_utf8(dat).expect("readline tried to emit invalid UTF-8"),
)
}), }),
initial_size, initial_size,
) )
.into(), .into(),
retained_closures: RefCell::new(None), retained_closures: None,
} };
.into();
let data_for_resize: Weak<TermFrameData> = Rc::downgrade(&new_data); let frame_for_resize: TermFrame = frame.clone();
let resize_closure = Closure::new(move |dims: Dims| match Weak::upgrade(&data_for_resize) { let globals_for_resize: GlobalCell = globals.clone();
None => {} let resize_closure = Closure::new(move |dims: Dims| {
Some(r) => match r.readline.try_borrow_mut() { globals_for_resize
Err(_) => {} .frame_registry
Ok(mut v) => v.handle_resize((dims.cols(), dims.rows())).unwrap_or(()), .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); term.onResize(&resize_closure);
let cloned_handler = (*handler).clone(); let frame_for_ondata: TermFrame = frame.clone();
let emit_frame = frame.clone(); let globals_for_ondata: GlobalCell = globals.clone();
let data_for_on_data: Weak<TermFrameData> = Rc::downgrade(&new_data); let data_closure = Closure::new(move |d: String| {
let new_lines: Vec<String> = match globals_for_ondata
let data_closure = Closure::new(move |d: String| match Weak::upgrade(&data_for_on_data) { .frame_registry
None => {} .try_borrow_mut()
Some(r) => match r.readline.try_borrow_mut() { .expect("Unexpected failure to borrow frame registry in onData")
Err(_) => {} .get_mut(&frame_for_ondata)
Ok(mut v) => { {
for ev in v.readline(d.as_bytes()).expect("Readline failed") { None => vec![],
match ev { Some(frame) => frame
ReadlineEvent::Line(l) => cloned_handler.emit((emit_frame.clone(), l)), .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); term.onData(&data_closure);
new_data new_data
.retained_closures .retained_closures
.replace(Some((data_closure, resize_closure))); .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)] #[derive(Properties, PartialEq)]
pub struct TermViewProps { pub struct TermViewProps {
pub terminal: TermFrame, pub terminal: TermFrame,
pub frames: RegisteredTermFrameLens, pub global: GlobalCell,
pub handler: Callback<(TermFrame, String), ()>,
} }
#[function_component(TermView)] #[function_component(TermView)]
pub fn term_view(props: &TermViewProps) -> Html { 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(); term.fit.fit();
html! { html! {
{Html::VRef(term.node.clone())} {Html::VRef(term.node.clone())}
@ -194,19 +199,22 @@ pub fn term_view(props: &TermViewProps) -> Html {
} }
pub fn echo_to_term_frame( pub fn echo_to_term_frame(
frames: &RegisteredTermFrameLens, global: &GlobalCell,
frame_id: &TermFrame, frame_id: &TermFrame,
message: &str, message: &str,
) -> Result<(), &'static str> { ) -> Result<(), &'static str> {
let frame_val = frames.get.emit(()); global
let frame: &TermFrameData = frame_val.get(frame_id).ok_or_else(|| { .frame_registry
web_sys::console::log_3( .borrow()
.get(frame_id)
.ok_or_else(|| {
web_sys::console::log_2(
&"Attempt to echo to frame that doesn't exist.".into(), &"Attempt to echo to frame that doesn't exist.".into(),
&frame_id.0.into(), &frame_id.0.into(),
&frame_val.iter().count().into(),
); );
"Attempt to echo to frame that doesn't exist." "Attempt to echo to frame that doesn't exist."
})?; })?
frame.term.write(message); .term
.write(message);
Ok(()) Ok(())
} }