Refactor ready for MUD-specific routing

This commit is contained in:
Condorra 2024-09-07 22:05:01 +10:00
parent 466566a6c5
commit 243132eeec
8 changed files with 598 additions and 436 deletions

View File

@ -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 {

View File

@ -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<A, Gc<'gc, A>>>);
struct InternMapSingleton<'gc, A>(GcRefLock<'gc, BTreeMap<A, Value<'gc>>>);
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::<Rootable!['gcb => 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::<Rootable!['gcb => Gc<'gcb, A>]>(&ctx, gc_id).into_value(ctx)
.or_insert_with(|| {
UserData::<'gc>::new::<Rootable!['gcb => Gc<'gcb, A>]>(
&ctx,
Gc::new(&ctx, input.clone()),
)
.into_value(ctx)
})
.clone()
}
pub fn unintern_id<'gc, A>(ctx: Context<'gc>, input: &A)

164
src/lua_engine.rs Normal file
View File

@ -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<LuaState, String> {
let mut interp = Lua::core();
let exec: StashedExecutor =
interp.enter(|ctx| Ok::<StashedExecutor, String>(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::<TermFrame>(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::<Vec<Value>>(),
),
);
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<GlobalLayoutCell>,
) -> 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(())
}

160
src/lua_engine/frames.rs Normal file
View File

@ -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<GlobalLayoutCell>,
) -> 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<GlobalLayoutCell>,
) -> 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<String> = stack
.consume::<Variadic<Vec<Value>>>(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<GlobalLayoutCell>,
) -> 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<GlobalLayoutCell>,
) -> 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<GlobalLayoutCell>,
) -> 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<GlobalLayoutCell>,
) -> 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<TermFrame, piccolo::Error<'gc>> {
match u64::from_value(ctx, *value) {
Ok(v) => Ok(TermFrame(v)),
Err(_) => Ok(UserData::from_value(ctx, *value)?
.downcast::<Rootable!['gcb => Gc<'gcb, TermFrame>]>()?
.as_ref()
.clone()),
}
}

258
src/lua_engine/muds.rs Normal file
View File

@ -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<WebSocketId, piccolo::Error<'gc>> {
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::<Rootable!['gcb => 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::<WebSocketId>(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::<WebSocketId>(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<GlobalLayoutCell>,
) -> 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<GlobalLayoutCell>,
) -> 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<GlobalLayoutCell>,
) -> 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<GlobalLayoutCell>,
) -> 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::<Rootable!['gcb => 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,
})
})
}

View File

@ -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<LuaState, String> {
let mut interp = Lua::core();
let exec: StashedExecutor =
interp.enter(|ctx| Ok::<StashedExecutor, String>(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::<TermFrame>(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::<Vec<Value>>(),
),
);
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<GlobalLayoutCell>,
) -> 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<GlobalLayoutCell>,
) -> 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<GlobalLayoutCell>,
) -> 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<String> = stack
.consume::<Variadic<Vec<Value>>>(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<GlobalLayoutCell>,
) -> 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<GlobalLayoutCell>,
) -> 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<GlobalLayoutCell>,
) -> 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<GlobalLayoutCell>,
) -> 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<TermFrame, piccolo::Error<'gc>> {
match u64::from_value(ctx, *value) {
Ok(v) => Ok(TermFrame(v)),
Err(_) => Ok(UserData::from_value(ctx, *value)?
.downcast::<Rootable!['gcb => Gc<'gcb, TermFrame>]>()?
.as_ref()
.clone()),
}
}
fn try_unwrap_socketid<'gc>(
ctx: Context<'gc>,
value: &Value<'gc>,
) -> Result<WebSocketId, piccolo::Error<'gc>> {
Ok(UserData::from_value(ctx, *value)?
.downcast::<Rootable!['gcb => 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<GlobalLayoutCell>,
) -> 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<GlobalLayoutCell>,
) -> 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)
})
}

View File

@ -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;

View File

@ -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,
};