Implement mudinput (sending to MUD from terminal)

This commit is contained in:
Condorra 2024-09-17 22:14:07 +10:00
parent ff0411b040
commit 8816d76c53
5 changed files with 298 additions and 18 deletions

View File

@ -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(())
}
}
}
}
}

View File

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

View File

@ -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::<TermFrame>(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))
})
}

View File

@ -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::<Table>("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::<Table>("frames")?
.get::<i64, Value>(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::<piccolo::String, Value>(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::<Table>("commands")?
.get::<piccolo::String, Function>(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(

View File

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