worldwideportal/src/lua_engine/muds.rs

545 lines
21 KiB
Rust

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,
};
use wasm_bindgen::JsValue;
use web_sys::console;
use yew::UseStateSetter;
use crate::{
command_handler::debrace,
id_intern::{intern_id, unintern_id},
telnet::{parse_telnet_buf, TelnetOutput},
websocket::{connect_websocket, send_message_to_mud, WebSocketId},
GlobalLayoutCell, GlobalMemoCell,
};
use super::{prep_metaop_call, 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: Value<'gc> = ctx.get_global::<Table<'gc>>("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![Gc<'_, 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 = ctx.get_global("handlers")?;
let input_fn: Function = 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 = ctx.get_global("handlers")?;
let input_fn: Function = 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()
.ok_or_else(|| anyhow::Error::msg("Missing MUD argument"))?,
)?;
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 = ctx.get_global("muds")?;
if !muds.get_value(ctx, 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 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));
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))
})
}
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()
.ok_or_else(|| anyhow::Error::msg("Missing MUD value argument"))?;
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 = ctx.get_global("muds")?;
let mud_value = muds.get_value(ctx, 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_raw(&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_raw(&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> {
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
let mud: Value<'gc> = stack
.pop_front()
.ok_or_else(|| anyhow::Error::msg("Missing argument to mudoutput"))?;
let output: &'gc [u8] = stack.from_front::<piccolo::String<'gc>>(ctx)?.as_bytes();
let conntab: Table<'gc> = Table::from_value(
ctx,
Table::from_value(ctx, ctx.get_global("muds")?)?.get_value(ctx, mud),
)?;
let buf: &'gc [u8] =
piccolo::String::from_value(ctx, conntab.get(ctx, ctx.intern_static(b"buffer"))?)?
.as_bytes();
let mut cur_buf: Vec<u8> = [buf, output].concat();
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, 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));
}
(new_buf, Some(cmd)) => {
cur_buf = new_buf;
match cmd {
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![])),
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![])),
TelnetOutput::AreYouThere => fns.push((b"mudoutput_areyouthere", vec![])),
TelnetOutput::Will(v) => {
fns.push((b"mudoutput_will", vec![v.into_value(ctx)]))
}
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)]))
}
}
}
}
}
})
}
pub(super) fn mudoutput_line<'gc>(
ctx: Context<'gc>,
_global_memo: &GlobalMemoCell,
) -> Callback<'gc> {
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>,
_global_memo: &GlobalMemoCell,
) -> Callback<'gc> {
Callback::from_fn(&ctx, move |_ctx, _ex, _stack| Ok(CallbackReturn::Return))
}
pub(super) fn mudoutput_will<'gc>(
ctx: Context<'gc>,
_global_memo: &GlobalMemoCell,
) -> Callback<'gc> {
Callback::from_fn(&ctx, move |_ctx, _ex, _stack| Ok(CallbackReturn::Return))
}
pub(super) fn mudoutput_wont<'gc>(
ctx: Context<'gc>,
_global_memo: &GlobalMemoCell,
) -> Callback<'gc> {
Callback::from_fn(&ctx, move |_ctx, _ex, _stack| Ok(CallbackReturn::Return))
}
pub(super) fn mudoutput_do<'gc>(ctx: Context<'gc>, _global_memo: &GlobalMemoCell) -> Callback<'gc> {
Callback::from_fn(&ctx, move |_ctx, _ex, _stack| Ok(CallbackReturn::Return))
}
pub(super) fn mudoutput_dont<'gc>(
ctx: Context<'gc>,
_global_memo: &GlobalMemoCell,
) -> Callback<'gc> {
Callback::from_fn(&ctx, move |_ctx, _ex, _stack| Ok(CallbackReturn::Return))
}
pub(super) fn mudoutput_subnegotiation<'gc>(
ctx: Context<'gc>,
_global_memo: &GlobalMemoCell,
) -> 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))
})
}