Set up frameroutes to route MUD messages to termframes

This commit is contained in:
Condorra 2024-09-14 21:51:37 +10:00
parent 1015bcb4ad
commit ff0411b040
7 changed files with 421 additions and 123 deletions

2
Cargo.lock generated
View File

@ -747,7 +747,7 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "minicrossterm"
version = "0.28.1"
source = "git+https://git.blastmud.org/blasthavers/minicrossterm.git?rev=494f89daef41162fbd89d5266e261018ed5ff6dc#494f89daef41162fbd89d5266e261018ed5ff6dc"
source = "git+https://git.blastmud.org/blasthavers/minicrossterm.git?rev=0c8c6d4f0cf445adf7bb957811081a1b710bd933#0c8c6d4f0cf445adf7bb957811081a1b710bd933"
dependencies = [
"bitflags",
]

View File

@ -15,7 +15,7 @@ unicode-width = "0.1.13"
wasm-bindgen = "0.2.92"
web-sys = { version = "0.3.69", features = ["ResizeObserver", "DomRect", "CssStyleDeclaration"] }
yew = { version = "0.21.0", features = ["csr"] }
minicrossterm = { git = "https://git.blastmud.org/blasthavers/minicrossterm.git", rev = "494f89daef41162fbd89d5266e261018ed5ff6dc" }
minicrossterm = { git = "https://git.blastmud.org/blasthavers/minicrossterm.git", rev = "0c8c6d4f0cf445adf7bb957811081a1b710bd933" }
thiserror = "1.0.63"
console_error_panic_hook = "0.1.7"
anyhow = "1.0.86"

View File

@ -1,7 +1,8 @@
use self::{frames::*, muds::*};
use self::{frameroutes::*, frames::*, muds::*};
use anyhow::Error;
use piccolo::{
Callback, Closure, Context, Executor, ExternError, FromValue, Function, Lua, StashedExecutor,
async_callback::Locals, meta_ops::MetaResult, Callback, Closure, Context, Executor,
ExternError, FromValue, Function, Lua, MetaMethod, Stack, StashedExecutor, StashedFunction,
Table, Value, Variadic,
};
use yew::UseStateSetter;
@ -14,7 +15,8 @@ pub struct LuaState {
pub exec: StashedExecutor,
}
mod frames;
mod frameroutes;
pub mod frames;
pub mod muds;
impl LuaState {
@ -94,7 +96,11 @@ pub fn install_lua_globals(
global_memo: &GlobalMemoCell,
global_layout: UseStateSetter<GlobalLayoutCell>,
) -> Result<(), String> {
global_memo.lua_engine.borrow_mut().interp.try_enter(|ctx| {
global_memo
.lua_engine
.borrow_mut()
.interp
.try_enter(|ctx| {
let cmd_table = Table::new(&ctx);
macro_rules! register_command {
($sym: ident) => {
@ -138,17 +144,47 @@ pub fn install_lua_globals(
};
}
register_handler!(mudoutput);
register_handler!(mudoutput_line);
register_handler!(mudoutput_prompt);
register_handler!(mudoutput_will);
register_handler!(mudoutput_wont);
register_handler!(mudoutput_do);
register_handler!(mudoutput_dont);
register_handler!(mudoutput_subnegotiation);
macro_rules! register_nop_handler {
($sym: ident) => {
handlers_table
let classes_table = Table::new(&ctx);
ctx.set_global("classes", classes_table);
let mud_class_table = Table::new(&ctx);
classes_table.set(ctx, "mud", mud_class_table)?;
mud_class_table.set(ctx, MetaMethod::Index, mud_class_table)?;
macro_rules! register_class_function {
($class_table: ident, $sym: ident) => {
$class_table
.set(
ctx,
ctx.intern_static(stringify!($sym).as_bytes()),
$sym(ctx, &global_memo),
)
.map_err(|_| Error::msg("Can't add handler"))?;
};
($class_table: ident, $name: literal, $sym: ident) => {
$class_table
.set(
ctx,
ctx.intern_static($name.as_bytes()),
$sym(ctx, &global_memo),
)
.map_err(|_| Error::msg("Can't add handler"))?;
};
}
register_class_function!(mud_class_table, mudoutput_line);
register_class_function!(mud_class_table, mudoutput_prompt);
register_class_function!(mud_class_table, mudoutput_will);
register_class_function!(mud_class_table, mudoutput_wont);
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, "new", new_mud);
macro_rules! register_class_nop {
($class_table: ident, $sym: ident) => {
$class_table
.set(
ctx,
ctx.intern_static(stringify!($sym).as_bytes()),
@ -158,14 +194,21 @@ pub fn install_lua_globals(
};
}
register_nop_handler!(mudoutput_break);
register_nop_handler!(mudoutput_sync);
register_nop_handler!(mudoutput_interrupt);
register_nop_handler!(mudoutput_abort_output);
register_nop_handler!(mudoutput_areyouthere);
register_class_nop!(mud_class_table, mudoutput_break);
register_class_nop!(mud_class_table, mudoutput_sync);
register_class_nop!(mud_class_table, mudoutput_interrupt);
register_class_nop!(mud_class_table, mudoutput_abort_output);
register_class_nop!(mud_class_table, mudoutput_areyouthere);
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)?;
register_class_function!(frameroute_class_table, "new", new_frameroute);
register_class_function!(frameroute_class_table, "route", frameroute_route);
Ok(())
});
})
.map_err(|e| e.to_string())?;
Ok(())
}
@ -175,3 +218,22 @@ pub fn lua_nop(ctx: Context<'_>) -> Callback<'_> {
Ok(piccolo::CallbackReturn::Return)
})
}
// Borrowed from piccolo source.
pub fn prep_metaop_call<'gc, const N: usize>(
ctx: Context<'gc>,
mut stack: Stack<'gc, '_>,
locals: Locals<'gc, '_>,
res: MetaResult<'gc, N>,
) -> Option<StashedFunction> {
match res {
MetaResult::Value(v) => {
stack.push_back(v);
None
}
MetaResult::Call(call) => {
stack.extend(call.args);
Some(locals.stash(&ctx, call.function))
}
}
}

View File

@ -0,0 +1,58 @@
use piccolo::{Callback, CallbackReturn, Context, FromValue, Table};
use crate::{echo_to_term_frame, id_intern::intern_id, GlobalMemoCell, TermFrame};
use super::try_unwrap_frame;
pub(super) fn new_frameroute<'gc>(
ctx: Context<'gc>,
_global_memo: &GlobalMemoCell,
) -> Callback<'gc> {
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
let frameroute: Table = Table::from_value(
ctx,
stack
.pop_front()
.ok_or_else(|| anyhow::Error::msg("classes.frameroute:new missing object!"))?,
)?;
let frame: TermFrame = try_unwrap_frame(
ctx,
&stack
.pop_front()
.ok_or_else(|| anyhow::Error::msg("classes.frameroute:new missing frame!"))?,
)?;
// We re-intern it to ensure we always point at the userdata.
let frame = intern_id(ctx, frame);
frameroute.set(ctx, ctx.intern_static(b"frame"), frame)?;
Ok(piccolo::CallbackReturn::Return)
})
}
pub(super) fn frameroute_route<'gc>(
ctx: Context<'gc>,
global_memo: &GlobalMemoCell,
) -> Callback<'gc> {
let global_memo = global_memo.clone();
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
let frameroute = Table::from_value(
ctx,
stack
.pop_front()
.ok_or_else(|| anyhow::Error::msg("frameroute:route called without self!"))?,
)?;
let line = piccolo::String::from_value(
ctx,
stack
.pop_front()
.ok_or_else(|| anyhow::Error::msg("frameroute:route called without line!"))?,
)?;
let frame: TermFrame =
try_unwrap_frame(ctx, &frameroute.get(ctx, ctx.intern_static(b"frame"))?)?;
// We ignore errors with the term frame to avoid breaking the entire client for one closed frame.
echo_to_term_frame(&global_memo, &frame, &String::from_utf8_lossy(&line)).unwrap_or(());
Ok(CallbackReturn::Return)
})
}

View File

@ -151,7 +151,7 @@ pub fn panel_merge<'gc>(
})
}
fn try_unwrap_frame<'gc>(
pub fn try_unwrap_frame<'gc>(
ctx: Context<'gc>,
value: &Value<'gc>,
) -> Result<TermFrame, piccolo::Error<'gc>> {

View File

@ -1,8 +1,8 @@
use anyhow::Error;
use gc_arena::{Gc, Rootable};
use piccolo::{
self, Callback, CallbackReturn, Context, FromValue, Function, IntoValue, StashedValue, Table,
UserData, Value,
self, async_sequence, meta_ops, Callback, CallbackReturn, Context, FromValue, Function,
IntoValue, MetaMethod, SequenceReturn, StashedValue, Table, UserData, Value,
};
use wasm_bindgen::JsValue;
use web_sys::console;
@ -16,7 +16,7 @@ use crate::{
GlobalLayoutCell, GlobalMemoCell,
};
use super::LuaState;
use super::{prep_metaop_call, LuaState};
fn try_unwrap_socketid<'gc>(
ctx: Context<'gc>,
@ -163,10 +163,49 @@ pub(super) fn connect_mud<'gc>(
muds.set(ctx, name, new_socket)?;
let mud_class: Table = ctx
.get_global::<Table>("classes")?
.get(ctx, ctx.intern_static(b"mud"))?;
let conntab = Table::new(&ctx);
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));
Ok(piccolo::CallbackReturn::Return)
let seq = async_sequence(&ctx, |locals, mut seq| {
let conntab = locals.stash(&ctx, conntab);
async move {
let call = seq.try_enter(|ctx, locals, _execution, mut stack| {
let conntab = locals.fetch(&conntab);
stack.consume(ctx)?;
Ok(prep_metaop_call(
ctx,
stack,
locals,
meta_ops::index(
ctx,
conntab.into_value(ctx),
ctx.intern_static(b"new").into_value(ctx),
)?,
))
})?;
if let Some(call) = call {
seq.call(&call, 0).await?;
}
let new_fn = seq.try_enter(|ctx, locals, _execution, mut stack| {
let new_fn: Function = stack.consume(ctx)?;
stack.push_back(locals.fetch(&conntab).into_value(ctx));
Ok(locals.stash(&ctx, new_fn))
})?;
seq.call(&new_fn, 0).await?;
Ok(SequenceReturn::Return)
}
});
Ok(piccolo::CallbackReturn::Sequence(seq))
})
}
@ -257,16 +296,59 @@ pub(super) fn mudoutput<'gc>(ctx: Context<'gc>, _global_memo: &GlobalMemoCell) -
.as_bytes();
let mut cur_buf: Vec<u8> = [buf, output].concat();
let handlers = Table::from_value(ctx, ctx.get_global("handlers")?)?;
let mut fns: Vec<(&'static [u8], Vec<StashedValue>)> = vec![];
let mut fns: Vec<(&'static [u8], Vec<Value>)> = vec![];
loop {
match parse_telnet_buf(&cur_buf) {
(new_buf, None) => {
conntab.set(ctx, ctx.intern_static(b"buffer"), ctx.intern(&new_buf))?;
let seq = piccolo::async_sequence(&ctx, |locals, seq| async {
let seq = piccolo::async_sequence(&ctx, |locals, mut seq| {
let conntab = locals.stash(&ctx, conntab);
let fns: Vec<(&'static [u8], Vec<StashedValue>)> = fns
.into_iter()
.map(|fnv| {
(
fnv.0,
fnv.1.into_iter().map(|v| locals.stash(&ctx, v)).collect(),
)
})
.collect();
async move {
for (func_name, params) in fns {
let call =
seq.try_enter(|ctx, locals, _execution, mut stack| {
let conntab = locals.fetch(&conntab);
stack.consume(ctx)?;
Ok(prep_metaop_call(
ctx,
stack,
locals,
meta_ops::index(
ctx,
conntab.into_value(ctx),
ctx.intern_static(func_name).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(&conntab).into_value(ctx));
for param in &params {
stack.push_back(locals.fetch(param));
}
Ok(locals.stash(&ctx, Function::from_value(ctx, value)?))
})?;
seq.call(&call, 0).await?;
}
Ok(piccolo::SequenceReturn::Return)
}
});
return Ok(piccolo::CallbackReturn::Sequence(seq));
@ -274,47 +356,32 @@ pub(super) fn mudoutput<'gc>(ctx: Context<'gc>, _global_memo: &GlobalMemoCell) -
(new_buf, Some(cmd)) => {
cur_buf = new_buf;
match cmd {
TelnetOutput::Line(l) => fns.push((
b"mudoutput_line",
vec![ctx.stash(mud), ctx.stash(ctx.intern(&l).into_value(ctx))],
)),
TelnetOutput::Prompt(p) => fns.push((
b"mudoutput_prompt",
vec![ctx.stash(mud), ctx.stash(ctx.intern(&p).into_value(ctx))],
)),
TelnetOutput::Line(l) => {
fns.push((b"mudoutput_line", vec![ctx.intern(&l).into_value(ctx)]))
}
TelnetOutput::Prompt(p) => {
fns.push((b"mudoutput_prompt", vec![ctx.intern(&p).into_value(ctx)]))
}
TelnetOutput::Nop => {}
TelnetOutput::Break => fns.push((b"mudoutput_break", vec![ctx.stash(mud)])),
TelnetOutput::Sync => fns.push((b"mudoutput_sync", vec![ctx.stash(mud)])),
TelnetOutput::Interrupt => {
fns.push((b"mudoutput_interrupt", vec![ctx.stash(mud)]))
}
TelnetOutput::Break => fns.push((b"mudoutput_break", vec![])),
TelnetOutput::Sync => fns.push((b"mudoutput_sync", vec![])),
TelnetOutput::Interrupt => fns.push((b"mudoutput_interrupt", vec![])),
TelnetOutput::AbortOutput => {
fns.push((b"mudoutput_abort_output", vec![ctx.stash(mud)]))
TelnetOutput::AbortOutput => fns.push((b"mudoutput_abort_output", vec![])),
TelnetOutput::AreYouThere => fns.push((b"mudoutput_areyouthere", vec![])),
TelnetOutput::Will(v) => {
fns.push((b"mudoutput_will", vec![v.into_value(ctx)]))
}
TelnetOutput::AreYouThere => {
fns.push((b"mudoutput_areyouthere", vec![ctx.stash(mud)]))
TelnetOutput::Wont(v) => {
fns.push((b"mudoutput_wont", vec![v.into_value(ctx)]))
}
TelnetOutput::Do(v) => fns.push((b"mudoutput_do", vec![v.into_value(ctx)])),
TelnetOutput::Dont(v) => {
fns.push((b"mudoutput_dont", vec![v.into_value(ctx)]))
}
TelnetOutput::Subnegotiation(t) => {
fns.push((b"mudoutput_subnegotiation", vec![t.into_value(ctx)]))
}
TelnetOutput::Will(v) => fns.push((
b"mudoutput_will",
vec![ctx.stash(mud), ctx.stash(v.into_value(ctx))],
)),
TelnetOutput::Wont(v) => fns.push((
b"mudoutput_wont",
vec![ctx.stash(mud), ctx.stash(v.into_value(ctx))],
)),
TelnetOutput::Do(v) => fns.push((
b"mudoutput_do",
vec![ctx.stash(mud), ctx.stash(v.into_value(ctx))],
)),
TelnetOutput::Dont(v) => fns.push((
b"mudoutput_dont",
vec![ctx.stash(mud), ctx.stash(v.into_value(ctx))],
)),
TelnetOutput::Subnegotiation(t) => fns.push((
b"mudoutput_subnegotiation",
vec![ctx.stash(mud), ctx.stash(t.into_value(ctx))],
)),
}
}
}
@ -326,7 +393,61 @@ pub(super) fn mudoutput_line<'gc>(
ctx: Context<'gc>,
_global_memo: &GlobalMemoCell,
) -> Callback<'gc> {
Callback::from_fn(&ctx, move |_ctx, _ex, _stack| Ok(CallbackReturn::Return))
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
let mud = Table::from_value(
ctx,
stack
.pop_front()
.ok_or_else(|| anyhow::Error::msg("Missing muds:mudoutput_line self"))?,
)?;
let line = piccolo::String::from_value(
ctx,
stack
.pop_front()
.ok_or_else(|| anyhow::Error::msg("Missing muds:mudoutput_line line"))?,
)?;
let frameroutes: Table = mud.get(ctx, "frameroutes")?;
let seq = async_sequence(&ctx, |locals, mut seq| {
let frameroutes: Vec<StashedValue> = frameroutes
.iter()
.map(|fr| locals.stash(&ctx, fr.1))
.collect();
let line = locals.stash(&ctx, line);
async move {
for frameroute in frameroutes {
let call = seq.try_enter(|ctx, locals, _execution, mut stack| {
let frameroute = locals.fetch(&frameroute);
stack.consume(ctx)?;
Ok(prep_metaop_call(
ctx,
stack,
locals,
meta_ops::index(
ctx,
frameroute.into_value(ctx),
ctx.intern_static(b"route").into_value(ctx),
)?,
))
})?;
if let Some(call) = call {
seq.call(&call, 0).await?;
}
let route_fn = seq.try_enter(|ctx, locals, _execution, mut stack| {
let route_fn: Function = stack.consume(ctx)?;
stack.push_back(locals.fetch(&frameroute).into_value(ctx));
stack.push_back(locals.fetch(&line).into_value(ctx));
Ok(locals.stash(&ctx, route_fn))
})?;
seq.call(&route_fn, 0).await?;
}
Ok(SequenceReturn::Return)
}
});
Ok(CallbackReturn::Sequence(seq))
})
}
pub(super) fn mudoutput_prompt<'gc>(
ctx: Context<'gc>,
@ -361,3 +482,63 @@ pub(super) fn mudoutput_subnegotiation<'gc>(
) -> Callback<'gc> {
Callback::from_fn(&ctx, move |_ctx, _ex, _stack| Ok(CallbackReturn::Return))
}
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(
ctx,
stack
.pop_front()
.ok_or_else(|| anyhow::Error::msg("classes.mud:new missing object!"))?,
)?;
let frameroutes: Table = Table::new(&ctx);
let frameroute: Table = Table::new(&ctx);
frameroutes.set(ctx, 0, frameroute)?;
frameroute.set_metatable(
&ctx,
Some(
ctx.get_global::<Table>("classes")?
.get(ctx, ctx.intern_static(b"frameroute"))?,
),
);
mud.set(ctx, ctx.intern_static(b"frameroutes"), frameroutes)?;
// And call new on the frameroute, for the current frame.
let seq = async_sequence(&ctx, |locals, mut seq| {
let frameroute = locals.stash(&ctx, frameroute);
async move {
let call = seq.try_enter(|ctx, locals, _execution, mut stack| {
let frameroute = locals.fetch(&frameroute);
stack.consume(ctx)?;
Ok(prep_metaop_call(
ctx,
stack,
locals,
meta_ops::index(
ctx,
frameroute.into_value(ctx),
ctx.intern_static(b"new").into_value(ctx),
)?,
))
})?;
if let Some(call) = call {
seq.call(&call, 0).await?;
}
let new_fn = seq.try_enter(|ctx, locals, _execution, mut stack| {
let new_fn: Function = stack.consume(ctx)?;
stack.push_back(locals.fetch(&frameroute).into_value(ctx));
stack.push_back(
ctx.get_global::<Table>("info")?
.get(ctx, ctx.intern_static(b"current_frame"))?,
);
Ok(locals.stash(&ctx, new_fn))
})?;
seq.call(&new_fn, 0).await?;
Ok(SequenceReturn::Return)
}
});
Ok(piccolo::CallbackReturn::Sequence(seq))
})
}

View File

@ -120,9 +120,6 @@ fn get_or_make_term_frame<'a>(
term.open(&element);
term.loadAddon(&fit);
fit.fit();
for i in 0..100 {
term.write(&format!("{} Hello world\r\n", i));
}
let term_for_readline: Terminal = Terminal { obj: term.clone() };
let initial_size = (term.cols(), term.rows());
let mut new_data: TermFrameData = TermFrameData {