From 8816d76c532039c97c6c225e302b9eb2b1d6e02a Mon Sep 17 00:00:00 2001 From: Condorra Date: Tue, 17 Sep 2024 22:14:07 +1000 Subject: [PATCH] Implement mudinput (sending to MUD from terminal) --- src/command_handler.rs | 14 ++- src/lua_engine.rs | 50 ++++++++++ src/lua_engine/frames.rs | 202 +++++++++++++++++++++++++++++++++++++-- src/lua_engine/muds.rs | 48 ++++++++-- src/main.rs | 2 +- 5 files changed, 298 insertions(+), 18 deletions(-) diff --git a/src/command_handler.rs b/src/command_handler.rs index 84999cb..10a9ac6 100644 --- a/src/command_handler.rs +++ b/src/command_handler.rs @@ -1,8 +1,7 @@ -use itertools::join; - use crate::{ echo_to_term_frame, lua_engine::LuaState, parsing::parse_commands, GlobalMemoCell, TermFrame, }; +use itertools::{join, Itertools}; pub fn debrace(inp: &str) -> &str { let v = inp.trim(); @@ -52,6 +51,17 @@ fn reentrant_command_handler( } } } + } else { + // A normal command. Try dispatching it to the frame object in Lua... + match lua_state + .dispatch_normal_command(term_frame, &command.arguments.iter().join(" ")) + { + Ok(()) => (), + Err(msg) => { + echo_to_term_frame(globals, term_frame, &format!("{}\r\n", msg)) + .unwrap_or(()) + } + } } } } diff --git a/src/lua_engine.rs b/src/lua_engine.rs index cfe314a..4f2c131 100644 --- a/src/lua_engine.rs +++ b/src/lua_engine.rs @@ -90,6 +90,30 @@ impl LuaState { .execute::<()>(&self.exec) .map_err(|err| format!("{}", err)) } + + pub(crate) fn dispatch_normal_command( + &mut self, + frame: &TermFrame, + command: &str, + ) -> anyhow::Result<()> { + self.interp.try_enter(|ctx| { + let frames: Table = ctx.get_global("frames")?; + let frame_tab: Value = frames.get(ctx, frame.0 as i64)?; + if frame_tab.is_nil() { + Err(anyhow::Error::msg( + "Dispatching command to frame missing in Lua frames.", + ))?; + } + ctx.fetch(&self.exec).restart( + ctx, + Function::Callback(send_command_to_frame(ctx)), + (frame_tab, ctx.intern(command.as_bytes())), + ); + Ok(()) + })?; + self.interp.execute(&self.exec)?; + Ok(()) + } } pub fn install_lua_globals( @@ -129,6 +153,8 @@ pub fn install_lua_globals( ctx.set_global("info", info_table); let muds_table = Table::new(&ctx); ctx.set_global("muds", muds_table); + let frames_table = Table::new(&ctx); + ctx.set_global("frames", frames_table); let handlers_table = Table::new(&ctx); ctx.set_global("handlers", handlers_table); @@ -180,6 +206,7 @@ pub fn install_lua_globals( register_class_function!(mud_class_table, mudoutput_do); register_class_function!(mud_class_table, mudoutput_dont); register_class_function!(mud_class_table, mudoutput_subnegotiation); + register_class_function!(mud_class_table, mudinput_line); register_class_function!(mud_class_table, "new", new_mud); macro_rules! register_class_nop { @@ -200,6 +227,12 @@ pub fn install_lua_globals( register_class_nop!(mud_class_table, mudoutput_abort_output); register_class_nop!(mud_class_table, mudoutput_areyouthere); + let frame_class_table = Table::new(&ctx); + classes_table.set(ctx, "frame", frame_class_table)?; + frame_class_table.set(ctx, MetaMethod::Index, frame_class_table)?; + register_class_function!(frame_class_table, "new", new_frame); + register_class_function!(frame_class_table, "input", frame_input); + let frameroute_class_table = Table::new(&ctx); classes_table.set(ctx, "frameroute", frameroute_class_table)?; frameroute_class_table.set(ctx, MetaMethod::Index, frameroute_class_table)?; @@ -210,9 +243,26 @@ pub fn install_lua_globals( }) .map_err(|e| e.to_string())?; + lua_global_initialisation(global_memo)?; Ok(()) } +fn lua_global_initialisation(global_memo: &GlobalMemoCell) -> Result<(), String> { + let mut lua_engine = global_memo.lua_engine.borrow_mut(); + let lua_engine_ref: &mut LuaState = &mut lua_engine; + lua_engine_ref.interp.enter(|ctx| { + ctx.fetch(&lua_engine_ref.exec).restart( + ctx, + Function::Callback(ensure_frame_instance(ctx, &TermFrame(1))), + (), + ); + }); + lua_engine_ref + .interp + .execute(&lua_engine_ref.exec) + .map_err(|e| e.to_string()) +} + pub fn lua_nop(ctx: Context<'_>) -> Callback<'_> { Callback::from_fn(&ctx, |_ctx, _ex, _stack| { Ok(piccolo::CallbackReturn::Return) diff --git a/src/lua_engine/frames.rs b/src/lua_engine/frames.rs index 3dc2758..881cd5c 100644 --- a/src/lua_engine/frames.rs +++ b/src/lua_engine/frames.rs @@ -1,14 +1,17 @@ use crate::{ - command_handler::debrace, echo_to_term_frame, GlobalLayoutCell, GlobalLayoutState, - GlobalMemoCell, TermFrame, + command_handler::debrace, echo_to_term_frame, id_intern::intern_id, GlobalLayoutCell, + GlobalLayoutState, GlobalMemoCell, TermFrame, }; use gc_arena::{Gc, Rootable}; use piccolo::{ - self, Callback, Context, FromValue, Function, IntoValue, Table, UserData, Value, Variadic, + self, async_sequence, meta_ops, Callback, CallbackReturn, Context, FromValue, Function, + IntoValue, SequenceReturn, Table, UserData, Value, Variadic, }; use std::{rc::Rc, str}; use yew::UseStateSetter; +use super::prep_metaop_call; + pub fn echo_frame_raw<'gc, 'a>( ctx: Context<'gc>, global_memo: &'a GlobalMemoCell, @@ -84,19 +87,22 @@ pub fn vsplit<'gc>( Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { let path: String = stack.from_front(ctx)?; let path: &str = debrace(&path); - let frame: u64 = stack.from_front(ctx)?; + let frame: TermFrame = TermFrame(stack.from_front(ctx)?); let new_splits = global_memo .layout .borrow() .term_splits - .vsplit(path, TermFrame(frame)) + .vsplit(path, frame.clone()) .map_err(|e| e.into_value(ctx))?; let new_layout = Rc::new(GlobalLayoutState { term_splits: new_splits.clone(), }); global_layout.set(new_layout.clone()); *(global_memo.layout.borrow_mut()) = new_layout; - Ok(piccolo::CallbackReturn::Return) + Ok(CallbackReturn::Call { + function: Function::Callback(ensure_frame_instance(ctx, &frame)), + then: None, + }) }) } @@ -110,19 +116,22 @@ pub fn hsplit<'gc>( Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { let path: String = stack.from_front(ctx)?; let path: &str = debrace(&path); - let frame: u64 = stack.from_front(ctx)?; + let frame: TermFrame = TermFrame(stack.from_front(ctx)?); let new_splits = global_memo .layout .borrow() .term_splits - .hsplit(path, TermFrame(frame)) + .hsplit(path, frame.clone()) .map_err(|e| e.into_value(ctx))?; let new_layout = Rc::new(GlobalLayoutState { term_splits: new_splits.clone(), }); global_layout.set(new_layout.clone()); *(global_memo.layout.borrow_mut()) = new_layout; - Ok(piccolo::CallbackReturn::Return) + Ok(CallbackReturn::Call { + function: Function::Callback(ensure_frame_instance(ctx, &frame)), + then: None, + }) }) } @@ -163,3 +172,178 @@ pub fn try_unwrap_frame<'gc>( .clone()), } } + +pub fn ensure_frame_instance<'gc>(ctx: Context<'gc>, frame: &TermFrame) -> Callback<'gc> { + let frame = frame.clone(); + Callback::from_fn(&ctx, move |ctx, _ex, _stack| { + let frames: Table = ctx.get_global("frames")?; + let frame_value: Value = frames.get(ctx, frame.0 as i64)?; + if !frame_value.is_nil() { + return Ok(CallbackReturn::Return); + } + + let frame_tab = Table::new(&ctx); + let classes: Table = ctx.get_global("classes")?; + frame_tab.set_metatable(&ctx, Some(classes.get(ctx, "frame")?)); + frames.set(ctx, frame.0 as i64, frame_tab)?; + + // Call frame_tab:new(frame) to setup. + let frame = frame.clone(); + let seq = async_sequence(&ctx, move |locals, mut seq| { + let frame_tab = locals.stash(&ctx, frame_tab); + async move { + let call = seq.try_enter(|ctx, locals, _execution, mut stack| { + let frame_tab = locals.fetch(&frame_tab); + stack.consume(ctx)?; + Ok(prep_metaop_call( + ctx, + stack, + locals, + meta_ops::index( + ctx, + frame_tab.into_value(ctx), + ctx.intern_static(b"new").into_value(ctx), + )?, + )) + })?; + if let Some(call) = call { + seq.call(&call, 0).await?; + } + let call = seq.try_enter(|ctx, locals, _, mut stack| { + let value = stack + .pop_back() + .ok_or_else(|| anyhow::Error::msg("Index didn't return value"))?; + stack.consume(ctx)?; + stack.push_back(locals.fetch(&frame_tab).into_value(ctx)); + stack.push_back(intern_id::(ctx, frame.clone())); + Ok(locals.stash(&ctx, Function::from_value(ctx, value)?)) + })?; + seq.call(&call, 0).await?; + + Ok(SequenceReturn::Return) + } + }); + Ok(CallbackReturn::Sequence(seq)) + }) +} + +pub(super) fn new_frame<'gc>(ctx: Context<'gc>, _global_memo: &GlobalMemoCell) -> Callback<'gc> { + Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { + let frame_tab: Table = Table::from_value( + ctx, + stack + .pop_front() + .ok_or_else(|| anyhow::Error::msg("classes.frame:new missing object!"))?, + )?; + let frame: Value = stack + .pop_front() + .ok_or_else(|| anyhow::Error::msg("classes.frame:new missing frame!"))?; + + frame_tab.set(ctx, ctx.intern_static(b"frame"), frame)?; + + Ok(piccolo::CallbackReturn::Return) + }) +} + +// Note: send_command_to_frame is called from the command parser, and can't be overriden (or even +// called) by the user. +// It will normally result in a call into frame_input, but that can be overriden by scripts. +pub(super) fn send_command_to_frame<'gc>(ctx: Context<'gc>) -> Callback<'gc> { + Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { + let (frame, line): (Table, piccolo::String) = stack.consume(ctx)?; + let seq = async_sequence(&ctx, |locals, mut seq| { + let frame = locals.stash(&ctx, frame); + let line = locals.stash(&ctx, line); + async move { + let call = seq.try_enter(|ctx, locals, _execution, stack| { + let frame = locals.fetch(&frame); + Ok(prep_metaop_call( + ctx, + stack, + locals, + meta_ops::index( + ctx, + frame.into_value(ctx), + ctx.intern_static(b"input").into_value(ctx), + )?, + )) + })?; + if let Some(call) = call { + seq.call(&call, 0).await?; + } + let call = seq.try_enter(|ctx, locals, _, mut stack| { + let value = stack + .pop_back() + .ok_or_else(|| anyhow::Error::msg("Index didn't return value"))?; + stack.consume(ctx)?; + stack.push_back(locals.fetch(&frame).into_value(ctx)); + stack.push_back(locals.fetch(&line).into_value(ctx)); + Ok(locals.stash(&ctx, Function::from_value(ctx, value)?)) + })?; + seq.call(&call, 0).await?; + + Ok(SequenceReturn::Return) + } + }); + Ok(CallbackReturn::Sequence(seq)) + }) +} + +pub(super) fn frame_input<'gc>(ctx: Context<'gc>, _global_memo: &GlobalMemoCell) -> Callback<'gc> { + Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { + let frame_tab: Table = Table::from_value( + ctx, + stack + .pop_front() + .ok_or_else(|| anyhow::Error::msg("classes.frame:new missing object!"))?, + )?; + let line: Value = stack + .pop_front() + .ok_or_else(|| anyhow::Error::msg("classes.frame:new missing line!"))?; + + let linked_mud = frame_tab.get_value(ctx, ctx.intern_static(b"linked_mud")); + if linked_mud.is_nil() { + return Ok(piccolo::CallbackReturn::Return); + } + let linked_mud: Table = Table::from_value(ctx, linked_mud)?; + + // linked_mud:mudinput_line(line) + let seq = async_sequence(&ctx, |locals, mut seq| { + let linked_mud = locals.stash(&ctx, linked_mud); + let line = locals.stash(&ctx, line); + async move { + let call = seq.try_enter(|ctx, locals, _execution, mut stack| { + let linked_mud = locals.fetch(&linked_mud); + stack.consume(ctx)?; + Ok(prep_metaop_call( + ctx, + stack, + locals, + meta_ops::index( + ctx, + linked_mud.into_value(ctx), + ctx.intern_static(b"mudinput_line").into_value(ctx), + )?, + )) + })?; + if let Some(call) = call { + seq.call(&call, 0).await?; + } + let call = seq.try_enter(|ctx, locals, _, mut stack| { + let value = stack + .pop_back() + .ok_or_else(|| anyhow::Error::msg("Index didn't return value"))?; + stack.consume(ctx)?; + stack.push_back(locals.fetch(&linked_mud).into_value(ctx)); + stack.push_back(locals.fetch(&line)); + Ok(locals.stash(&ctx, Function::from_value(ctx, value)?)) + })?; + seq.call(&call, 0).await?; + + Ok(SequenceReturn::Return) + } + }); + + Ok(piccolo::CallbackReturn::Sequence(seq)) + }) +} diff --git a/src/lua_engine/muds.rs b/src/lua_engine/muds.rs index f32908e..3c87405 100644 --- a/src/lua_engine/muds.rs +++ b/src/lua_engine/muds.rs @@ -2,7 +2,7 @@ use anyhow::Error; use gc_arena::{Gc, Rootable}; use piccolo::{ self, async_sequence, meta_ops, Callback, CallbackReturn, Context, FromValue, Function, - IntoValue, MetaMethod, SequenceReturn, StashedValue, Table, UserData, Value, + IntoValue, SequenceReturn, StashedValue, Table, UserData, Value, }; use wasm_bindgen::JsValue; use web_sys::console; @@ -16,7 +16,7 @@ use crate::{ GlobalLayoutCell, GlobalMemoCell, }; -use super::{prep_metaop_call, LuaState}; +use super::{prep_metaop_call, try_unwrap_frame, LuaState}; fn try_unwrap_socketid<'gc>( ctx: Context<'gc>, @@ -171,10 +171,25 @@ pub(super) fn connect_mud<'gc>( muds.set(ctx, new_socket, conntab)?; conntab.set(ctx, ctx.intern_static(b"socket"), new_socket)?; conntab.set(ctx, ctx.intern_static(b"buffer"), ctx.intern_static(b""))?; - let conntab_meta = Table::new(&ctx); - conntab_meta.set(ctx, MetaMethod::Index, mud_class)?; - conntab.set_metatable(&ctx, Some(conntab_meta)); - + conntab.set_metatable(&ctx, Some(mud_class)); + let curframe: Value = ctx + .get_global::("info")? + .get(ctx, ctx.intern_static(b"current_frame"))?; + if let Ok(curframe) = try_unwrap_frame(ctx, &curframe) { + if let Ok(frame) = ctx + .get_global::
("frames")? + .get::(ctx, curframe.0 as i64) + { + if !frame.is_nil() { + Table::from_value(ctx, frame)?.set( + ctx, + ctx.intern_static(b"linked_mud"), + conntab, + )?; + } + } + } + // Call conntab:new... let seq = async_sequence(&ctx, |locals, mut seq| { let conntab = locals.stash(&ctx, conntab); async move { @@ -483,6 +498,27 @@ pub(super) fn mudoutput_subnegotiation<'gc>( Callback::from_fn(&ctx, move |_ctx, _ex, _stack| Ok(CallbackReturn::Return)) } +pub(super) fn mudinput_line<'gc>( + ctx: Context<'gc>, + _global_memo: &GlobalMemoCell, +) -> Callback<'gc> { + Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { + let (mud, line): (Table, piccolo::String) = stack.consume(ctx)?; + stack.push_back(mud.get::(ctx, ctx.intern_static(b"socket"))?); + let line = [line.as_bytes(), b"\r\n"].concat(); + stack.push_back(ctx.intern(&line).into_value(ctx)); + + let func = ctx + .get_global::
("commands")? + .get::(ctx, ctx.intern_static(b"sendmud_raw"))?; + + Ok(CallbackReturn::Call { + function: func, + then: None, + }) + }) +} + pub(super) fn new_mud<'gc>(ctx: Context<'gc>, _global_memo: &GlobalMemoCell) -> Callback<'gc> { Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { let mud: Table = Table::from_value( diff --git a/src/main.rs b/src/main.rs index 22a013b..d158dbd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -70,7 +70,7 @@ fn app() -> Html { }); use_memo((), |_| { install_lua_globals(&global_memo, global_layout.setter()) - .expect("Couldn't install Lua globals") + .expect("Couldn't install Lua globals"); }); html! {