worldwideportal/src/lua_engine.rs

419 lines
16 KiB
Rust

use self::{frameroutes::*, frames::*, muds::*};
use anyhow::Error;
use piccolo::{
async_callback::{AsyncSequence, Locals},
meta_ops::{self, MetaResult},
stash::Fetchable,
Callback, Closure, Context, Executor, ExternError, FromValue, Function, IntoValue, Lua,
MetaMethod, Stack, StashedError, StashedExecutor, StashedFunction, StashedValue, Table, Value,
Variadic,
};
use yew::UseStateSetter;
use crate::{
id_intern::intern_id,
match_table::{
create_match_table, match_table_add, match_table_lua_table, match_table_remove,
match_table_try_run_sub,
},
parsing::ParsedCommand,
GlobalLayoutCell, GlobalMemoCell, TermFrame,
};
pub struct LuaState {
pub interp: Lua,
pub exec: StashedExecutor,
}
mod frameroutes;
pub 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<(), ExternError> {
self.interp.try_enter(|ctx| {
let info_table = Table::from_value(ctx, ctx.get_global("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: &ParsedCommand,
) -> Result<(), String> {
self.interp
.try_enter(|ctx| {
let commands = ctx.get_global::<Table>("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
.arguments
.iter()
.map(|s| ctx.intern(s.text.as_bytes()).into())
.collect::<Vec<Value>>(),
),
);
Ok(())
})
.map_err(|err| format!("{}", err))?;
self.interp
.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(
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"))?;
};
($sym: ident, $name: literal) => {
cmd_table
.set(
ctx,
ctx.intern_static($name.as_bytes()),
$sym(ctx, &global_memo, &global_layout),
)
.map_err(|_| Error::msg("Can't add command"))?;
};
}
macro_rules! register_stateless_command {
($sym: ident) => {
cmd_table
.set(
ctx,
ctx.intern_static(stringify!($sym).as_bytes()),
$sym(ctx),
)
.map_err(|_| Error::msg("Can't add command"))?;
};
($sym: ident, $name: literal) => {
cmd_table
.set(ctx, ctx.intern_static($name.as_bytes()), $sym(ctx))
.map_err(|_| Error::msg("Can't add command"))?;
};
}
register_stateless_command!(mud_trigger, "act");
register_stateless_command!(mud_trigger, "action");
register_stateless_command!(alias);
register_stateless_command!(unalias);
register_command!(close_mud);
register_command!(connect_mud);
register_stateless_command!(create_match_table);
register_command!(cmd_delete_logs, "deletelogs");
register_command!(delay);
register_command!(delete_mud);
register_command!(cmd_download_logs, "downloadlogs");
register_command!(echo);
register_command!(echo_frame);
register_command!(echo_frame_raw);
register_command!(hsplit);
register_command!(cmd_list_logs, "listlogs");
register_command!(mud_log, "log");
register_command!(panel_merge);
register_command!(sendmud_raw);
register_command!(tick);
register_stateless_command!(mud_trigger, "untrigger");
register_stateless_command!(mud_untrigger, "unact");
register_stateless_command!(mud_untrigger, "unaction");
register_command!(untick, "undelay");
register_command!(untick);
register_stateless_command!(mud_untrigger, "untrigger");
register_command!(vsplit);
ctx.set_global("commands", cmd_table);
let info_table = Table::new(&ctx);
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);
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);
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"))?;
};
}
macro_rules! register_stateless_class_function {
($class_table: ident, $sym: ident) => {
$class_table
.set(
ctx,
ctx.intern_static(stringify!($sym).as_bytes()),
$sym(ctx),
)
.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))
.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, mudinput_line);
register_stateless_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()),
lua_nop(ctx),
)
.map_err(|_| Error::msg("Can't add handler"))?;
};
}
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 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)?;
register_class_function!(frameroute_class_table, "new", new_frameroute);
register_class_function!(frameroute_class_table, "route", frameroute_route);
let match_table_class_table = Table::new(&ctx);
classes_table.set(ctx, "match_table", match_table_class_table)?;
match_table_class_table.set(ctx, MetaMethod::Index, match_table_class_table)?;
register_stateless_class_function!(match_table_class_table, "add", match_table_add);
register_stateless_class_function!(
match_table_class_table,
"remove",
match_table_remove
);
register_stateless_class_function!(
match_table_class_table,
"lua_table",
match_table_lua_table
);
register_class_function!(
match_table_class_table,
"try_run_sub",
match_table_try_run_sub
);
Ok(())
})
.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)
})
}
// 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))
}
}
}
pub async fn call_checking_metatable<'gc, T: Fetchable>(
seq: &mut AsyncSequence,
obj: T,
func_name: &'static str,
arguments: &[StashedValue],
) -> Result<(), StashedError>
where
for<'gcb> <T as Fetchable>::Fetched<'gcb>: IntoValue<'gcb>,
{
let call = seq.try_enter(|ctx, locals, _execution, mut stack| {
let obj = locals.fetch(&obj);
stack.consume(ctx)?;
Ok(prep_metaop_call(
ctx,
stack,
locals,
meta_ops::index(
ctx,
obj.into_value(ctx),
ctx.intern_static(func_name.as_bytes()).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(&obj).into_value(ctx));
for arg in arguments {
stack.push_back(locals.fetch(arg));
}
Ok(locals.stash(&ctx, Function::from_value(ctx, value)?))
})?;
seq.call(&call, 0).await?;
Ok(())
}