From 243132eeec49eab3f17a8b793bdd32d8900eb182 Mon Sep 17 00:00:00 2001 From: Condorra Date: Sat, 7 Sep 2024 22:05:01 +1000 Subject: [PATCH] Refactor ready for MUD-specific routing --- src/command_handler.rs | 2 +- src/id_intern.rs | 19 +- src/lua_engine.rs | 164 +++++++++++++++ src/lua_engine/frames.rs | 160 +++++++++++++++ src/lua_engine/muds.rs | 258 ++++++++++++++++++++++++ src/lua_state.rs | 425 --------------------------------------- src/main.rs | 4 +- src/websocket.rs | 2 +- 8 files changed, 598 insertions(+), 436 deletions(-) create mode 100644 src/lua_engine.rs create mode 100644 src/lua_engine/frames.rs create mode 100644 src/lua_engine/muds.rs delete mode 100644 src/lua_state.rs diff --git a/src/command_handler.rs b/src/command_handler.rs index 4999513..84999cb 100644 --- a/src/command_handler.rs +++ b/src/command_handler.rs @@ -1,7 +1,7 @@ use itertools::join; use crate::{ - echo_to_term_frame, lua_state::LuaState, parsing::parse_commands, GlobalMemoCell, TermFrame, + echo_to_term_frame, lua_engine::LuaState, parsing::parse_commands, GlobalMemoCell, TermFrame, }; pub fn debrace(inp: &str) -> &str { diff --git a/src/id_intern.rs b/src/id_intern.rs index e187dea..c278192 100644 --- a/src/id_intern.rs +++ b/src/id_intern.rs @@ -1,11 +1,11 @@ -use std::collections::BTreeMap; +use std::{collections::BTreeMap, fmt::Debug}; use gc_arena::{lock::GcRefLock, Collect, Gc, Rootable}; use piccolo::{Context, IntoValue, Singleton, UserData, Value}; #[derive(Collect)] #[collect(no_drop)] -struct InternMapSingleton<'gc, A>(GcRefLock<'gc, BTreeMap>>); +struct InternMapSingleton<'gc, A>(GcRefLock<'gc, BTreeMap>>); impl<'gc, A> Singleton<'gc> for InternMapSingleton<'gc, A> where @@ -18,17 +18,22 @@ where pub fn intern_id<'gc, A>(ctx: Context<'gc>, input: A) -> Value<'gc> where - A: Collect + Ord + Clone + 'static, + A: Collect + Ord + Clone + Debug + 'static, { let intern_map: &'gc InternMapSingleton<'gc, A> = ctx.singleton:: InternMapSingleton<'gcb, A>]>(); - let gc_id = intern_map + intern_map .0 .borrow_mut(&ctx) .entry(input.clone()) - .or_insert_with(|| Gc::new(&ctx, input.clone())) - .clone(); - UserData::<'gc>::new:: Gc<'gcb, A>]>(&ctx, gc_id).into_value(ctx) + .or_insert_with(|| { + UserData::<'gc>::new:: Gc<'gcb, A>]>( + &ctx, + Gc::new(&ctx, input.clone()), + ) + .into_value(ctx) + }) + .clone() } pub fn unintern_id<'gc, A>(ctx: Context<'gc>, input: &A) diff --git a/src/lua_engine.rs b/src/lua_engine.rs new file mode 100644 index 0000000..a8d5759 --- /dev/null +++ b/src/lua_engine.rs @@ -0,0 +1,164 @@ +use self::{frames::*, muds::*}; +use anyhow::Error; +use piccolo::{ + Closure, Executor, FromValue, Function, IntoValue, Lua, StashedExecutor, StaticError, Table, + Value, Variadic, +}; +use yew::UseStateSetter; + +use crate::{id_intern::intern_id, GlobalLayoutCell, GlobalMemoCell, TermFrame}; +use std::collections::VecDeque; + +pub struct LuaState { + pub interp: Lua, + pub exec: StashedExecutor, +} + +mod frames; +pub mod muds; + +impl LuaState { + pub fn setup() -> Result { + let mut interp = Lua::core(); + let exec: StashedExecutor = + interp.enter(|ctx| Ok::(ctx.stash(Executor::new(ctx))))?; + + Ok(LuaState { interp, exec }) + } + + fn try_set_current_frame(&mut self, frame: &TermFrame) -> Result<(), StaticError> { + self.interp.try_enter(|ctx| { + let info_table = Table::from_value(ctx, ctx.get_global(ctx.intern_static(b"info")))?; + info_table.set( + ctx, + ctx.intern_static(b"current_frame"), + intern_id::(ctx, frame.clone()), + )?; + Ok(()) + }) + } + + pub fn set_current_frame(&mut self, frame: &TermFrame) { + // We silently ignore errors here. Failure can happen if the Lua code does weird things + // like messes with the info global, so better to just ignore. + self.try_set_current_frame(frame).unwrap_or(()) + } + + pub fn execute(&mut self, command: &str) -> Result<(), String> { + self.interp + .try_enter(|ctx| { + let closure = Closure::load(ctx, None, format!("return ({})", command).as_bytes()) + .or_else(|_| Closure::load(ctx, None, command.as_bytes()))?; + ctx.fetch(&self.exec).restart(ctx, closure.into(), ()); + Ok(()) + }) + .map_err(|err| format!("{}", err))?; + self.interp + .execute::<()>(&self.exec) + .map_err(|err| format!("{}", err)) + } + + pub fn execute_command( + &mut self, + command: &str, + arguments: &VecDeque<&str>, + ) -> Result<(), String> { + self.interp + .try_enter(|ctx| { + let commands = + Table::from_value(ctx, ctx.get_global(ctx.intern_static(b"commands")))?; + let command_value: Value = commands.get(ctx, ctx.intern(command.as_bytes())); + if command_value.is_nil() { + Err(anyhow::Error::msg("Unknown command"))?; + } + let command_fn = Function::from_value(ctx, command_value)?; + ctx.fetch(&self.exec).restart( + ctx, + command_fn, + Variadic( + arguments + .iter() + .map(|s| ctx.intern(s.as_bytes()).into()) + .collect::>(), + ), + ); + Ok(()) + }) + .map_err(|err| format!("{}", err))?; + self.interp + .execute::<()>(&self.exec) + .map_err(|err| format!("{}", err)) + } +} + +pub fn install_lua_globals( + global_memo: &GlobalMemoCell, + global_layout: UseStateSetter, +) -> Result<(), String> { + global_memo + .lua_engine + .borrow_mut() + .interp + .try_enter(|ctx| { + let cmd_table = Table::new(&ctx); + macro_rules! register_command { + ($sym: ident) => { + cmd_table + .set( + ctx, + ctx.intern_static(stringify!($sym).as_bytes()), + $sym(ctx, &global_memo, &global_layout), + ) + .map_err(|_| Error::msg("Can't add command"))?; + }; + } + + register_command!(echo); + register_command!(echo_frame); + register_command!(echo_frame_raw); + register_command!(connect_mud); + register_command!(delete_mud); + register_command!(close_mud); + register_command!(sendmud_raw); + register_command!(hsplit); + register_command!(panel_merge); + register_command!(vsplit); + ctx.set_global(ctx.intern_static(b"commands").into_value(ctx), cmd_table) + .map(|_| ()) + .map_err(|_| Error::msg("Can't set commands key"))?; + + let info_table = Table::new(&ctx); + ctx.set_global(ctx.intern_static(b"info").into_value(ctx), info_table) + .map(|_| ()) + .map_err(|_| Error::msg("Can't set info key"))?; + let muds_table = Table::new(&ctx); + ctx.set_global(ctx.intern_static(b"muds").into_value(ctx), muds_table) + .map(|_| ()) + .map_err(|_| Error::msg("Can't set muds key"))?; + + let handlers_table = Table::new(&ctx); + ctx.set_global( + ctx.intern_static(b"handlers").into_value(ctx), + handlers_table, + ) + .map(|_| ()) + .map_err(|_| Error::msg("Can't set handlers key"))?; + macro_rules! register_handler { + ($sym: ident) => { + handlers_table + .set( + ctx, + ctx.intern_static(stringify!($sym).as_bytes()), + $sym(ctx, &global_memo), + ) + .map_err(|_| Error::msg("Can't add handler"))?; + }; + } + register_handler!(mudoutput); + + Ok(()) + }) + .map_err(|e| e.to_string())?; + + Ok(()) +} diff --git a/src/lua_engine/frames.rs b/src/lua_engine/frames.rs new file mode 100644 index 0000000..0f6b335 --- /dev/null +++ b/src/lua_engine/frames.rs @@ -0,0 +1,160 @@ +use crate::{ + command_handler::debrace, echo_to_term_frame, GlobalLayoutCell, GlobalLayoutState, + GlobalMemoCell, TermFrame, +}; +use gc_arena::{Gc, Rootable}; +use piccolo::{ + self, Callback, Context, FromValue, Function, IntoValue, Table, UserData, Value, Variadic, +}; +use std::{rc::Rc, str}; +use yew::UseStateSetter; + +pub fn echo_frame_raw<'gc, 'a>( + ctx: Context<'gc>, + global_memo: &'a GlobalMemoCell, + _global_layout: &'a UseStateSetter, +) -> Callback<'gc> { + let global_memo = global_memo.clone(); + Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { + let frame: TermFrame = try_unwrap_frame(ctx, &stack.pop_front())?; + let message: piccolo::String = stack.from_front(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(&global_memo, &frame, message_str).map_err(|m| m.into_value(ctx))?; + Ok(piccolo::CallbackReturn::Return) + }) +} + +pub fn echo_frame<'gc>( + ctx: Context<'gc>, + _global_memo: &GlobalMemoCell, + _global_layout: &UseStateSetter, +) -> Callback<'gc> { + Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { + let commands: Table<'gc> = + Table::from_value(ctx, ctx.get_global(ctx.intern_static(b"commands")))?; + let function = Function::from_value(ctx, commands.get(ctx, "echo_frame_raw"))?; + let frame_no: Value = stack.pop_front(); + let all_parts: Vec = stack + .consume::>>(ctx)? + .into_iter() + .map(|v| format!("{}", v)) + .collect(); + stack.push_front(frame_no); + let message = ctx.intern((all_parts.join(" ") + "\r\n").as_bytes()); + stack.push_back(message.into()); + Ok(piccolo::CallbackReturn::Call { + function, + then: None, + }) + }) +} + +pub fn echo<'gc>( + ctx: Context<'gc>, + _global_memo: &GlobalMemoCell, + _global_layout: &UseStateSetter, +) -> Callback<'gc> { + Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { + let commands: Table<'gc> = + Table::from_value(ctx, ctx.get_global(ctx.intern_static(b"commands")))?; + let function = Function::from_value(ctx, commands.get(ctx, "echo_frame"))?; + let info: Table<'gc> = Table::from_value(ctx, ctx.get_global(ctx.intern_static(b"info")))?; + let cur_frame = info.get(ctx, ctx.intern_static(b"current_frame")); + stack.push_front(cur_frame); + Ok(piccolo::CallbackReturn::Call { + function, + then: None, + }) + }) +} + +pub fn vsplit<'gc>( + ctx: Context<'gc>, + global_memo: &GlobalMemoCell, + global_layout: &UseStateSetter, +) -> Callback<'gc> { + let global_layout = global_layout.clone(); + let global_memo = global_memo.clone(); + 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 new_splits = global_memo + .layout + .borrow() + .term_splits + .vsplit(path, TermFrame(frame)) + .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) + }) +} + +pub fn hsplit<'gc>( + ctx: Context<'gc>, + global_memo: &GlobalMemoCell, + global_layout: &UseStateSetter, +) -> Callback<'gc> { + let global_layout = global_layout.clone(); + let global_memo = global_memo.clone(); + 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 new_splits = global_memo + .layout + .borrow() + .term_splits + .hsplit(path, TermFrame(frame)) + .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) + }) +} + +pub fn panel_merge<'gc>( + ctx: Context<'gc>, + global_memo: &GlobalMemoCell, + global_layout: &UseStateSetter, +) -> Callback<'gc> { + let global_layout = global_layout.clone(); + let global_memo = global_memo.clone(); + Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { + let path: String = stack.from_front(ctx)?; + let path: &str = debrace(&path); + let new_splits = global_memo + .layout + .borrow() + .term_splits + .join(path) + .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) + }) +} + +fn try_unwrap_frame<'gc>( + ctx: Context<'gc>, + value: &Value<'gc>, +) -> Result> { + match u64::from_value(ctx, *value) { + Ok(v) => Ok(TermFrame(v)), + Err(_) => Ok(UserData::from_value(ctx, *value)? + .downcast:: Gc<'gcb, TermFrame>]>()? + .as_ref() + .clone()), + } +} diff --git a/src/lua_engine/muds.rs b/src/lua_engine/muds.rs new file mode 100644 index 0000000..be9af5a --- /dev/null +++ b/src/lua_engine/muds.rs @@ -0,0 +1,258 @@ +use anyhow::Error; +use gc_arena::{Gc, Rootable}; +use piccolo::{self, Callback, Context, FromValue, Function, Table, UserData, Value}; +use wasm_bindgen::JsValue; +use web_sys::console; +use yew::UseStateSetter; + +use crate::{ + command_handler::debrace, + id_intern::{intern_id, unintern_id}, + websocket::{connect_websocket, send_message_to_mud, WebSocketId}, + GlobalLayoutCell, GlobalMemoCell, +}; + +use super::LuaState; + +fn try_unwrap_socketid<'gc>( + ctx: Context<'gc>, + value: &Value<'gc>, +) -> Result> { + let value = if let Ok(sockname) = String::from_value(ctx, *value) { + let ret = Table::from_value(ctx, ctx.get_global("muds"))?.get(ctx, sockname); + if ret.is_nil() { + Err(Error::msg( + "Could not find a MUD connection with that name.", + ))?; + } + ret + } else { + *value + }; + Ok(UserData::from_value(ctx, value)? + .downcast:: Gc<'gcb, WebSocketId>]>()? + .as_ref() + .clone()) +} + +pub fn handle_websocket_output( + socket: &WebSocketId, + engine: &mut LuaState, + input: &[u8], +) -> Result<(), String> { + engine + .interp + .try_enter(|ctx| { + let handlers = Table::from_value(ctx, ctx.get_global(ctx.intern_static(b"handlers")))?; + let input_fn = + Function::from_value(ctx, handlers.get(ctx, ctx.intern_static(b"mudoutput")))?; + ctx.fetch(&engine.exec).restart( + ctx, + input_fn, + ( + intern_id::(ctx, socket.clone()), + ctx.intern(input), + ), + ); + Ok(()) + }) + .map_err(|err| format!("{}", err))?; + engine + .interp + .execute::<()>(&engine.exec) + .map_err(|err| format!("{}", err)) +} + +pub fn handle_websocket_output_log_err(socket: &WebSocketId, engine: &mut LuaState, input: &[u8]) { + match handle_websocket_output(socket, engine, input) { + Ok(()) => {} + Err(e) => console::log_2( + &JsValue::from_str("An error occurred calling the WebSocket input handler"), + &JsValue::from_str(&e), + ), + } +} + +pub fn handle_websocket_close(socket: &WebSocketId, engine: &mut LuaState) -> Result<(), String> { + engine + .interp + .try_enter(|ctx| { + let handlers = Table::from_value(ctx, ctx.get_global(ctx.intern_static(b"handlers")))?; + let input_fn = + Function::from_value(ctx, handlers.get(ctx, ctx.intern_static(b"mudclose")))?; + ctx.fetch(&engine.exec).restart( + ctx, + input_fn, + intern_id::(ctx, socket.clone()), + ); + Ok(()) + }) + .map_err(|err| format!("{}", err))?; + engine + .interp + .execute::<()>(&engine.exec) + .map_err(|err| format!("{}", err)) +} + +pub fn handle_websocket_close_log_err(socket: &WebSocketId, engine: &mut LuaState) { + match handle_websocket_close(socket, engine) { + Ok(()) => {} + Err(e) => console::log_2( + &JsValue::from_str("An error occurred calling the WebSocket input handler"), + &JsValue::from_str(&e), + ), + } +} + +pub(super) fn sendmud_raw<'gc>( + ctx: Context<'gc>, + global_memo: &GlobalMemoCell, + _global_layout: &UseStateSetter, +) -> Callback<'gc> { + let global_memo = global_memo.clone(); + Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { + let mud: WebSocketId = try_unwrap_socketid(ctx, &stack.pop_front())?; + let msg: piccolo::String = stack.from_front(ctx)?; + send_message_to_mud(&mud, msg.as_bytes(), &global_memo)?; + Ok(piccolo::CallbackReturn::Return) + }) +} + +pub(super) fn connect_mud<'gc>( + ctx: Context<'gc>, + global_memo: &GlobalMemoCell, + _global_layout: &UseStateSetter, +) -> Callback<'gc> { + let global_memo = global_memo.clone(); + Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { + let mut trusted: bool = false; + let name: String = loop { + let v: String = stack.from_front(ctx)?; + if v == "-trust" { + trusted = true; + continue; + } + break v; + }; + let name: Value<'gc> = ctx.intern(debrace(&name).as_bytes()).into(); + + let muds = Table::from_value(ctx, ctx.get_global(ctx.intern_static(b"muds")))?; + if !muds.get_value(name).is_nil() { + Err(Error::msg( + "Attempt to create MUD connection using name that's already taken", + ))? + } + + let url: String = stack.from_front(ctx)?; + let new_socket = intern_id( + ctx, + connect_websocket( + trusted, + &url, + &mut global_memo.ws_registry.borrow_mut(), + &global_memo, + )?, + ); + + muds.set(ctx, name, new_socket)?; + + let conntab = Table::new(&ctx); + muds.set(ctx, new_socket, conntab)?; + + Ok(piccolo::CallbackReturn::Return) + }) +} + +pub(super) fn close_mud<'gc>( + ctx: Context<'gc>, + global_memo: &GlobalMemoCell, + _global_layout: &UseStateSetter, +) -> Callback<'gc> { + let global_memo = global_memo.clone(); + Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { + let mud_value: Value<'gc> = stack.pop_front(); + let socket_id = try_unwrap_socketid(ctx, &mud_value)?; + match global_memo.ws_registry.borrow_mut().get_mut(&socket_id) { + None => Err(Error::msg("That MUD connection doesn't exist."))?, + Some(v) => { + if v.closed { + Err(Error::msg("That MUD connection was already closed."))? + } + v.connection.close().unwrap_or(()); + v.retained_closures = None; + v.closed = true; + } + } + Ok(piccolo::CallbackReturn::Return) + }) +} + +pub(super) fn delete_mud<'gc>( + ctx: Context<'gc>, + global_memo: &GlobalMemoCell, + _global_layout: &UseStateSetter, +) -> Callback<'gc> { + let global_memo = global_memo.clone(); + Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { + let name: String = stack.from_front(ctx)?; + let name: Value<'gc> = ctx.intern(debrace(&name).as_bytes()).into(); + + let muds = Table::from_value(ctx, ctx.get_global(ctx.intern_static(b"muds")))?; + let mud_value = muds.get_value(name); + if mud_value.is_nil() { + Err(Error::msg( + "Attempt to delete MUD connection that wasn't found", + ))? + } + + let socket_id = try_unwrap_socketid(ctx, &mud_value)?; + + // Delete the MUD data if possible. + let _ = muds.set_value(&ctx, mud_value, Value::Nil); + for (k, v) in muds.iter() { + match UserData::from_value(ctx, v) { + Err(_) => continue, + Ok(ud) => match ud.downcast:: Gc<'gcb, WebSocketId>]>() { + Err(_) => continue, + Ok(v) if v.as_ref() != &socket_id => continue, + _ => {} + }, + } + let _ = muds.set_value(&ctx, k, Value::Nil); + } + unintern_id(ctx, &socket_id); + match global_memo.ws_registry.borrow_mut().remove(&socket_id) { + None => {} + Some(sockdat) if sockdat.closed => {} + Some(sockdat) => sockdat.connection.close().unwrap_or(()), + } + + Ok(piccolo::CallbackReturn::Return) + }) +} + +pub(super) fn mudoutput<'gc>(ctx: Context<'gc>, global_memo: &GlobalMemoCell) -> Callback<'gc> { + let global_memo = global_memo.clone(); + Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { + // Temporary hack for testing... alias to echo + let echo = Function::from_value( + ctx, + Table::from_value(ctx, ctx.get_global(ctx.intern_static(b"commands")))? + .get(ctx, ctx.intern_static(b"echo")), + )?; + + let mud: Value<'gc> = stack.pop_front(); + let output: piccolo::String<'gc> = stack.from_front(ctx)?; + + let conntab = + Table::from_value(ctx, ctx.get_global(ctx.intern_static(b"muds")))?.get_value(mud); + if conntab.is_nil() { + Err(Error::msg("Received input from MUD not in muds table."))? + } + + Ok(piccolo::CallbackReturn::Call { + function: echo, + then: None, + }) + }) +} diff --git a/src/lua_state.rs b/src/lua_state.rs deleted file mode 100644 index b5dd855..0000000 --- a/src/lua_state.rs +++ /dev/null @@ -1,425 +0,0 @@ -use anyhow::Error; -use gc_arena::{Gc, Rootable}; -use piccolo::{ - self, Callback, Closure, Context, Executor, FromValue, Function, IntoValue, Lua, - StashedExecutor, StaticError, Table, UserData, Value, Variadic, -}; -use wasm_bindgen::JsValue; -use web_sys::console; -use yew::UseStateSetter; - -use crate::{ - command_handler::debrace, - echo_to_term_frame, - id_intern::intern_id, - websocket::{connect_websocket, send_message_to_mud, WebSocketId}, - GlobalLayoutCell, GlobalLayoutState, GlobalMemoCell, TermFrame, -}; -use std::{collections::VecDeque, rc::Rc, str}; - -pub struct LuaState { - pub interp: Lua, - pub exec: StashedExecutor, -} - -impl LuaState { - pub fn setup() -> Result { - let mut interp = Lua::core(); - let exec: StashedExecutor = - interp.enter(|ctx| Ok::(ctx.stash(Executor::new(ctx))))?; - - Ok(LuaState { interp, exec }) - } - - fn try_set_current_frame(&mut self, frame: &TermFrame) -> Result<(), StaticError> { - self.interp.try_enter(|ctx| { - let info_table = Table::from_value(ctx, ctx.get_global(ctx.intern_static(b"info")))?; - info_table.set( - ctx, - ctx.intern_static(b"current_frame"), - intern_id::(ctx, frame.clone()), - )?; - Ok(()) - }) - } - - pub fn set_current_frame(&mut self, frame: &TermFrame) { - // We silently ignore errors here. Failure can happen if the Lua code does weird things - // like messes with the info global, so better to just ignore. - self.try_set_current_frame(frame).unwrap_or(()) - } - - pub fn execute(&mut self, command: &str) -> Result<(), String> { - self.interp - .try_enter(|ctx| { - let closure = Closure::load(ctx, None, format!("return ({})", command).as_bytes()) - .or_else(|_| Closure::load(ctx, None, command.as_bytes()))?; - ctx.fetch(&self.exec).restart(ctx, closure.into(), ()); - Ok(()) - }) - .map_err(|err| format!("{}", err))?; - self.interp - .execute::<()>(&self.exec) - .map_err(|err| format!("{}", err)) - } - - pub fn execute_command( - &mut self, - command: &str, - arguments: &VecDeque<&str>, - ) -> Result<(), String> { - self.interp - .try_enter(|ctx| { - let commands = - Table::from_value(ctx, ctx.get_global(ctx.intern_static(b"commands")))?; - let command_value: Value = commands.get(ctx, ctx.intern(command.as_bytes())); - if command_value.is_nil() { - Err(anyhow::Error::msg("Unknown command"))?; - } - let command_fn = Function::from_value(ctx, command_value)?; - ctx.fetch(&self.exec).restart( - ctx, - command_fn, - Variadic( - arguments - .iter() - .map(|s| ctx.intern(s.as_bytes()).into()) - .collect::>(), - ), - ); - Ok(()) - }) - .map_err(|err| format!("{}", err))?; - self.interp - .execute::<()>(&self.exec) - .map_err(|err| format!("{}", err)) - } -} - -pub fn install_lua_globals( - global_memo: &GlobalMemoCell, - global_layout: UseStateSetter, -) -> Result<(), String> { - global_memo - .lua_engine - .borrow_mut() - .interp - .try_enter(|ctx| { - let cmd_table = Table::new(&ctx); - macro_rules! register_command { - ($sym: ident) => { - cmd_table - .set( - ctx, - ctx.intern_static(stringify!($sym).as_bytes()), - $sym(ctx, &global_memo, &global_layout), - ) - .map_err(|_| Error::msg("Can't add command"))?; - }; - } - - register_command!(echo); - register_command!(echo_frame); - register_command!(echo_frame_raw); - register_command!(connect_mud); - register_command!(sendmud_raw); - register_command!(hsplit); - register_command!(panel_merge); - register_command!(vsplit); - ctx.set_global(ctx.intern_static(b"commands").into_value(ctx), cmd_table) - .map(|_| ()) - .map_err(|_| Error::msg("Can't set commands key"))?; - - let info_table = Table::new(&ctx); - ctx.set_global(ctx.intern_static(b"info").into_value(ctx), info_table) - .map(|_| ()) - .map_err(|_| Error::msg("Can't set info key"))?; - let muds_table = Table::new(&ctx); - ctx.set_global(ctx.intern_static(b"muds").into_value(ctx), muds_table) - .map(|_| ()) - .map_err(|_| Error::msg("Can't set muds key"))?; - - Ok(()) - }) - .map_err(|e| e.to_string())?; - - Ok(()) -} - -fn echo_frame_raw<'gc, 'a>( - ctx: Context<'gc>, - global_memo: &'a GlobalMemoCell, - _global_layout: &'a UseStateSetter, -) -> Callback<'gc> { - let global_memo = global_memo.clone(); - Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { - let frame: TermFrame = try_unwrap_frame(ctx, &stack.pop_front())?; - let message: piccolo::String = stack.from_front(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(&global_memo, &frame, message_str).map_err(|m| m.into_value(ctx))?; - Ok(piccolo::CallbackReturn::Return) - }) -} - -fn echo_frame<'gc>( - ctx: Context<'gc>, - _global_memo: &GlobalMemoCell, - _global_layout: &UseStateSetter, -) -> Callback<'gc> { - Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { - let commands: Table<'gc> = - Table::from_value(ctx, ctx.get_global(ctx.intern_static(b"commands")))?; - let function = Function::from_value(ctx, commands.get(ctx, "echo_frame_raw"))?; - let frame_no: Value = stack.pop_front(); - let all_parts: Vec = stack - .consume::>>(ctx)? - .into_iter() - .map(|v| format!("{}", v)) - .collect(); - stack.push_front(frame_no); - let message = ctx.intern((all_parts.join(" ") + "\r\n").as_bytes()); - stack.push_back(message.into()); - Ok(piccolo::CallbackReturn::Call { - function, - then: None, - }) - }) -} - -fn echo<'gc>( - ctx: Context<'gc>, - _global_memo: &GlobalMemoCell, - _global_layout: &UseStateSetter, -) -> Callback<'gc> { - Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { - let commands: Table<'gc> = - Table::from_value(ctx, ctx.get_global(ctx.intern_static(b"commands")))?; - let function = Function::from_value(ctx, commands.get(ctx, "echo_frame"))?; - let info: Table<'gc> = Table::from_value(ctx, ctx.get_global(ctx.intern_static(b"info")))?; - let cur_frame = info.get(ctx, ctx.intern_static(b"current_frame")); - stack.push_front(cur_frame); - Ok(piccolo::CallbackReturn::Call { - function, - then: None, - }) - }) -} - -fn vsplit<'gc>( - ctx: Context<'gc>, - global_memo: &GlobalMemoCell, - global_layout: &UseStateSetter, -) -> Callback<'gc> { - let global_layout = global_layout.clone(); - let global_memo = global_memo.clone(); - 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 new_splits = global_memo - .layout - .borrow() - .term_splits - .vsplit(path, TermFrame(frame)) - .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) - }) -} - -fn hsplit<'gc>( - ctx: Context<'gc>, - global_memo: &GlobalMemoCell, - global_layout: &UseStateSetter, -) -> Callback<'gc> { - let global_layout = global_layout.clone(); - let global_memo = global_memo.clone(); - 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 new_splits = global_memo - .layout - .borrow() - .term_splits - .hsplit(path, TermFrame(frame)) - .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) - }) -} - -fn panel_merge<'gc>( - ctx: Context<'gc>, - global_memo: &GlobalMemoCell, - global_layout: &UseStateSetter, -) -> Callback<'gc> { - let global_layout = global_layout.clone(); - let global_memo = global_memo.clone(); - Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { - let path: String = stack.from_front(ctx)?; - let path: &str = debrace(&path); - let new_splits = global_memo - .layout - .borrow() - .term_splits - .join(path) - .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) - }) -} - -fn try_unwrap_frame<'gc>( - ctx: Context<'gc>, - value: &Value<'gc>, -) -> Result> { - match u64::from_value(ctx, *value) { - Ok(v) => Ok(TermFrame(v)), - Err(_) => Ok(UserData::from_value(ctx, *value)? - .downcast:: Gc<'gcb, TermFrame>]>()? - .as_ref() - .clone()), - } -} - -fn try_unwrap_socketid<'gc>( - ctx: Context<'gc>, - value: &Value<'gc>, -) -> Result> { - Ok(UserData::from_value(ctx, *value)? - .downcast:: Gc<'gcb, WebSocketId>]>()? - .as_ref() - .clone()) -} - -pub fn handle_websocket_output( - socket: &WebSocketId, - engine: &mut LuaState, - input: &[u8], -) -> Result<(), String> { - engine - .interp - .try_enter(|ctx| { - let handlers = Table::from_value(ctx, ctx.get_global(ctx.intern_static(b"handlers")))?; - let input_fn = - Function::from_value(ctx, handlers.get(ctx, ctx.intern_static(b"mudoutput")))?; - ctx.fetch(&engine.exec).restart( - ctx, - input_fn, - (intern_id(ctx, socket.clone()), ctx.intern(input)), - ); - Ok(()) - }) - .map_err(|err| format!("{}", err))?; - engine - .interp - .execute::<()>(&engine.exec) - .map_err(|err| format!("{}", err)) -} - -pub fn handle_websocket_output_log_err(socket: &WebSocketId, engine: &mut LuaState, input: &[u8]) { - match handle_websocket_output(socket, engine, input) { - Ok(()) => {} - Err(e) => console::log_2( - &JsValue::from_str("An error occurred calling the WebSocket input handler"), - &JsValue::from_str(&e), - ), - } -} - -pub fn handle_websocket_close(socket: &WebSocketId, engine: &mut LuaState) -> Result<(), String> { - engine - .interp - .try_enter(|ctx| { - let handlers = Table::from_value(ctx, ctx.get_global(ctx.intern_static(b"handlers")))?; - let input_fn = - Function::from_value(ctx, handlers.get(ctx, ctx.intern_static(b"mudclose")))?; - ctx.fetch(&engine.exec) - .restart(ctx, input_fn, intern_id(ctx, socket.clone())); - Ok(()) - }) - .map_err(|err| format!("{}", err))?; - engine - .interp - .execute::<()>(&engine.exec) - .map_err(|err| format!("{}", err)) -} - -pub fn handle_websocket_close_log_err(socket: &WebSocketId, engine: &mut LuaState) { - match handle_websocket_close(socket, engine) { - Ok(()) => {} - Err(e) => console::log_2( - &JsValue::from_str("An error occurred calling the WebSocket input handler"), - &JsValue::from_str(&e), - ), - } -} - -fn sendmud_raw<'gc>( - ctx: Context<'gc>, - global_memo: &GlobalMemoCell, - _global_layout: &UseStateSetter, -) -> Callback<'gc> { - let global_memo = global_memo.clone(); - Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { - let mud: WebSocketId = try_unwrap_socketid(ctx, &stack.pop_front())?; - let msg: piccolo::String = stack.from_front(ctx)?; - send_message_to_mud(&mud, msg.as_bytes(), &global_memo)?; - Ok(piccolo::CallbackReturn::Return) - }) -} - -fn connect_mud<'gc>( - ctx: Context<'gc>, - global_memo: &GlobalMemoCell, - _global_layout: &UseStateSetter, -) -> Callback<'gc> { - let global_memo = global_memo.clone(); - Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { - let mut trusted: bool = false; - let name: String = loop { - let v: String = stack.from_front(ctx)?; - if v == "-trust" { - trusted = true; - continue; - } - break v; - }; - let name: Value<'gc> = ctx.intern(debrace(&name).as_bytes()).into(); - - let muds = Table::from_value(ctx, ctx.get_global(ctx.intern_static(b"muds")))?; - if !muds.get_value(name).is_nil() { - Err(Error::msg( - "Attempt to create MUD connection using name that's already taken", - ))? - } - - let url: String = stack.from_front(ctx)?; - let new_socket = intern_id( - ctx, - connect_websocket( - trusted, - &url, - &mut global_memo.ws_registry.borrow_mut(), - &global_memo, - )?, - ); - - muds.set(ctx, name, new_socket)?; - Ok(piccolo::CallbackReturn::Return) - }) -} diff --git a/src/main.rs b/src/main.rs index 8d73fdf..1ff7e17 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,13 +7,13 @@ use yew::prelude::*; pub mod command_handler; pub mod id_intern; pub mod lineengine; -pub mod lua_state; +pub mod lua_engine; pub mod parsing; pub mod split_panel; pub mod term_split; pub mod term_view; pub mod websocket; -use crate::lua_state::{install_lua_globals, LuaState}; +use crate::lua_engine::{install_lua_globals, LuaState}; use crate::split_panel::*; use crate::term_view::*; use crate::websocket::RegisteredWebSockets; diff --git a/src/websocket.rs b/src/websocket.rs index 7769d19..f7a4576 100644 --- a/src/websocket.rs +++ b/src/websocket.rs @@ -10,7 +10,7 @@ use web_sys::{ }; use crate::{ - lua_state::{handle_websocket_close_log_err, handle_websocket_output_log_err}, + lua_engine::muds::{handle_websocket_close_log_err, handle_websocket_output_log_err}, GlobalMemoCell, };