worldwideportal/src/lua_engine.rs

178 lines
5.9 KiB
Rust

use self::{frames::*, muds::*};
use anyhow::Error;
use piccolo::{
Callback, Closure, Context, Executor, ExternError, FromValue, Function, Lua, StashedExecutor,
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<(), 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: &VecDeque<&str>,
) -> 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
.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("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 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);
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
.set(
ctx,
ctx.intern_static(stringify!($sym).as_bytes()),
lua_nop(ctx),
)
.map_err(|_| Error::msg("Can't add handler"))?;
};
}
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);
Ok(())
});
Ok(())
}
pub fn lua_nop(ctx: Context<'_>) -> Callback<'_> {
Callback::from_fn(&ctx, |_ctx, _ex, _stack| {
Ok(piccolo::CallbackReturn::Return)
})
}