Implement command queue, and let match table interact with it
It can be called from Lua.
This commit is contained in:
parent
b1bf0f317a
commit
ff840c04c2
@ -1,7 +1,12 @@
|
||||
use crate::{
|
||||
echo_to_term_frame, lua_engine::LuaState, parsing::parse_commands, GlobalMemoCell, TermFrame,
|
||||
echo_to_term_frame,
|
||||
lua_engine::LuaState,
|
||||
parsing::{parse_commands, ParsedCommand},
|
||||
GlobalMemoCell, TermFrame,
|
||||
};
|
||||
use itertools::{join, Itertools};
|
||||
use itertools::Itertools;
|
||||
use wasm_bindgen::JsValue;
|
||||
use web_sys::console;
|
||||
|
||||
pub fn debrace(inp: &str) -> &str {
|
||||
let v = inp.trim();
|
||||
@ -16,11 +21,10 @@ fn reentrant_command_handler(
|
||||
lua_state: &mut LuaState,
|
||||
globals: &GlobalMemoCell,
|
||||
term_frame: &TermFrame,
|
||||
command_in: &str,
|
||||
commands_in: &[ParsedCommand],
|
||||
) {
|
||||
echo_to_term_frame(globals, term_frame, "\r").unwrap_or(());
|
||||
lua_state.set_current_frame(term_frame);
|
||||
for command in parse_commands(command_in).commands {
|
||||
for command in commands_in {
|
||||
match command.split_out_command() {
|
||||
None => (),
|
||||
Some((cmd, rest)) => {
|
||||
@ -34,13 +38,9 @@ fn reentrant_command_handler(
|
||||
}
|
||||
}
|
||||
} else if let Ok(repeat_count) = command_rest.parse::<u16>() {
|
||||
let cmds = &[rest];
|
||||
for _ in 0..repeat_count {
|
||||
reentrant_command_handler(
|
||||
lua_state,
|
||||
globals,
|
||||
term_frame,
|
||||
&join(rest.arguments.iter(), " "),
|
||||
);
|
||||
reentrant_command_handler(lua_state, globals, term_frame, cmds);
|
||||
}
|
||||
} else {
|
||||
match lua_state.execute_command(command_rest, &rest) {
|
||||
@ -69,15 +69,53 @@ fn reentrant_command_handler(
|
||||
}
|
||||
|
||||
pub fn command_handler(globals: &GlobalMemoCell, term_frame: &TermFrame, command_in: &str) {
|
||||
echo_to_term_frame(globals, term_frame, "\r").unwrap_or(());
|
||||
{
|
||||
let mut cq = globals.command_queue.borrow_mut();
|
||||
for cmd in parse_commands(command_in).commands {
|
||||
cq.push_back((term_frame.clone(), cmd));
|
||||
}
|
||||
}
|
||||
execute_queue(globals);
|
||||
}
|
||||
|
||||
pub fn execute_queue(globals: &GlobalMemoCell) {
|
||||
let mut steps: u64 = 0;
|
||||
const STEP_LIMIT: u64 = 500;
|
||||
loop {
|
||||
let queue_head = globals.command_queue.borrow_mut().pop_front();
|
||||
match queue_head {
|
||||
None => return,
|
||||
Some((frame, command_in)) => {
|
||||
steps += 1;
|
||||
match globals.lua_engine.try_borrow_mut() {
|
||||
Err(_) => echo_to_term_frame(
|
||||
globals,
|
||||
term_frame,
|
||||
"Attempt to re-enter command handler during processing.\r\n",
|
||||
)
|
||||
.unwrap_or(()), // Ignore error handling error.
|
||||
Err(_) => console::log_1(&JsValue::from_str(
|
||||
"Can't borrow lua_engine when executing queue!",
|
||||
)),
|
||||
Ok(mut lua_state_m) => {
|
||||
reentrant_command_handler(&mut lua_state_m, globals, term_frame, command_in)
|
||||
reentrant_command_handler(
|
||||
&mut lua_state_m,
|
||||
globals,
|
||||
&frame,
|
||||
&[command_in.clone()],
|
||||
);
|
||||
if steps > STEP_LIMIT {
|
||||
let new_queue = globals.command_queue.take();
|
||||
if !new_queue.is_empty() {
|
||||
echo_to_term_frame(
|
||||
globals,
|
||||
&frame,
|
||||
&format!("Executing queued actions resulted in more than {} steps. This usually means a command is creating similar commands in a loop. The following commands were dropped from the queue to stop execution: {}.",
|
||||
STEP_LIMIT,
|
||||
&new_queue.iter().map(
|
||||
|(_fr, cmd)| cmd.to_string())
|
||||
.join("; "))).unwrap_or(());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,13 @@ use piccolo::{
|
||||
use yew::UseStateSetter;
|
||||
|
||||
use crate::{
|
||||
id_intern::intern_id, parsing::ParsedCommand, GlobalLayoutCell, GlobalMemoCell, TermFrame,
|
||||
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 {
|
||||
@ -145,6 +151,7 @@ pub fn install_lua_globals(
|
||||
register_command!(alias);
|
||||
register_command!(close_mud);
|
||||
register_command!(connect_mud);
|
||||
register_command!(create_match_table);
|
||||
register_command!(delete_mud);
|
||||
register_command!(echo);
|
||||
register_command!(echo_frame);
|
||||
@ -244,6 +251,18 @@ pub fn install_lua_globals(
|
||||
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_class_function!(match_table_class_table, "add", match_table_add);
|
||||
register_class_function!(match_table_class_table, "remove", match_table_remove);
|
||||
register_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())?;
|
||||
|
@ -9,6 +9,7 @@ use web_sys::console;
|
||||
use yew::UseStateSetter;
|
||||
|
||||
use crate::{
|
||||
command_handler::execute_queue,
|
||||
id_intern::{intern_id, unintern_id},
|
||||
telnet::{parse_telnet_buf, TelnetOutput},
|
||||
websocket::{connect_websocket, send_message_to_mud, WebSocketId},
|
||||
@ -65,7 +66,12 @@ pub fn handle_websocket_output(
|
||||
.map_err(|err| format!("{}", err))
|
||||
}
|
||||
|
||||
pub fn handle_websocket_output_log_err(socket: &WebSocketId, engine: &mut LuaState, input: &[u8]) {
|
||||
pub fn handle_websocket_output_log_err(
|
||||
socket: &WebSocketId,
|
||||
globals: &GlobalMemoCell,
|
||||
engine: &mut LuaState,
|
||||
input: &[u8],
|
||||
) {
|
||||
match handle_websocket_output(socket, engine, input) {
|
||||
Ok(()) => {}
|
||||
Err(e) => console::log_2(
|
||||
@ -73,6 +79,7 @@ pub fn handle_websocket_output_log_err(socket: &WebSocketId, engine: &mut LuaSta
|
||||
&JsValue::from_str(&e),
|
||||
),
|
||||
}
|
||||
execute_queue(globals);
|
||||
}
|
||||
|
||||
pub fn handle_websocket_close(socket: &WebSocketId, engine: &mut LuaState) -> Result<(), String> {
|
||||
@ -95,7 +102,11 @@ pub fn handle_websocket_close(socket: &WebSocketId, engine: &mut LuaState) -> Re
|
||||
.map_err(|err| format!("{}", err))
|
||||
}
|
||||
|
||||
pub fn handle_websocket_close_log_err(socket: &WebSocketId, engine: &mut LuaState) {
|
||||
pub fn handle_websocket_close_log_err(
|
||||
socket: &WebSocketId,
|
||||
globals: &GlobalMemoCell,
|
||||
engine: &mut LuaState,
|
||||
) {
|
||||
match handle_websocket_close(socket, engine) {
|
||||
Ok(()) => {}
|
||||
Err(e) => console::log_2(
|
||||
@ -103,6 +114,7 @@ pub fn handle_websocket_close_log_err(socket: &WebSocketId, engine: &mut LuaStat
|
||||
&JsValue::from_str(&e),
|
||||
),
|
||||
}
|
||||
execute_queue(globals);
|
||||
}
|
||||
|
||||
pub(super) fn sendmud_raw<'gc>(
|
||||
|
@ -1,6 +1,8 @@
|
||||
use std::cell::RefCell;
|
||||
use std::collections::VecDeque;
|
||||
use std::rc::Rc;
|
||||
|
||||
use parsing::ParsedCommand;
|
||||
use term_split::TermSplit;
|
||||
use yew::prelude::*;
|
||||
|
||||
@ -26,6 +28,7 @@ pub struct GlobalMemoState {
|
||||
frame_registry: RefCell<RegisteredTermFrames>,
|
||||
lua_engine: RefCell<LuaState>,
|
||||
ws_registry: RefCell<RegisteredWebSockets>,
|
||||
command_queue: RefCell<VecDeque<(TermFrame, ParsedCommand)>>,
|
||||
|
||||
// A cache of the latest layout info (separate from the state).
|
||||
// Updating this doesn't force a relayout, so only update the cache when
|
||||
@ -66,6 +69,7 @@ fn app() -> Html {
|
||||
let global_memo = use_memo((), |_| GlobalMemoState {
|
||||
frame_registry: RegisteredTermFrames::new().into(),
|
||||
ws_registry: RegisteredWebSockets::new().into(),
|
||||
command_queue: VecDeque::new().into(),
|
||||
lua_engine: LuaState::setup().expect("Can create interpreter").into(),
|
||||
layout: RefCell::new((*global_layout).clone()),
|
||||
});
|
||||
|
@ -1,13 +1,23 @@
|
||||
use std::{collections::VecDeque, str::FromStr};
|
||||
use std::{
|
||||
collections::{BTreeSet, VecDeque},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use anyhow::bail;
|
||||
use gc_arena::{Collect, GcRefLock, Rootable};
|
||||
use itertools::Itertools;
|
||||
use piccolo::{Context, IntoValue, Table, Value};
|
||||
use piccolo::{Callback, Context, IntoValue, Table, UserData, Value};
|
||||
use regex::Regex;
|
||||
use yew::UseStateSetter;
|
||||
|
||||
use crate::parsing::{parse_commands, quote_string, ArgumentGuard, ParsedArgument, ParsedCommand};
|
||||
use crate::{
|
||||
lua_engine::frames::try_unwrap_frame,
|
||||
parsing::{parse_commands, quote_string, ArgumentGuard, ParsedArgument, ParsedCommand},
|
||||
GlobalLayoutCell, GlobalMemoCell,
|
||||
};
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
#[derive(Default, Debug, Collect)]
|
||||
#[collect(require_static)]
|
||||
pub struct MatchSubTable {
|
||||
contents: Vec<MatchRecord>,
|
||||
}
|
||||
@ -74,6 +84,7 @@ impl MatchSubTable {
|
||||
}
|
||||
|
||||
pub fn add_record(&mut self, match_text: &str, sub_text: &str) -> anyhow::Result<()> {
|
||||
self.remove_record(match_text).unwrap_or(());
|
||||
let rex = Regex::new(match_text)?;
|
||||
|
||||
let parse_result = parse_commands(sub_text);
|
||||
@ -91,6 +102,27 @@ impl MatchSubTable {
|
||||
})
|
||||
.collect::<anyhow::Result<Vec<SubCommand>>>()?;
|
||||
|
||||
let vars: BTreeSet<String> = sub_commands
|
||||
.iter()
|
||||
.flat_map(|sc| {
|
||||
sc.arguments.iter().flat_map(|arg| {
|
||||
arg.text_parts.iter().filter_map(|tp| match tp {
|
||||
SubTextPart::Variable(v) => Some(v.clone()),
|
||||
_ => None,
|
||||
})
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
let max_captures = rex.captures_len();
|
||||
let valid_vars: BTreeSet<String> = (0..max_captures)
|
||||
.map(|n| n.to_string())
|
||||
.chain(rex.capture_names().filter_map(|o| o.map(|n| n.to_string())))
|
||||
.collect();
|
||||
let invalid_vars: Vec<String> = vars.difference(&valid_vars).cloned().collect();
|
||||
if !invalid_vars.is_empty() {
|
||||
bail!("Invalid variables in substitution: {:?}", invalid_vars);
|
||||
}
|
||||
|
||||
self.contents.push(MatchRecord {
|
||||
match_text: match_text.to_owned(),
|
||||
match_regex: rex,
|
||||
@ -99,6 +131,19 @@ impl MatchSubTable {
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove_record(&mut self, match_text: &str) -> anyhow::Result<()> {
|
||||
match self
|
||||
.contents
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_idx, rec)| rec.match_text == match_text)
|
||||
{
|
||||
None => bail!("No matching record found."),
|
||||
Some((idx, _)) => self.contents.remove(idx),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn parsedarg_to_subarg(parsedarg: ParsedArgument) -> anyhow::Result<SubArgument> {
|
||||
@ -398,4 +443,106 @@ mod tests {
|
||||
);
|
||||
assert_eq!(parse_commands(&ser_result).commands, vec![expected]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matchsubtable_rejects_invalid() {
|
||||
let mut table: MatchSubTable = Default::default();
|
||||
assert!(table
|
||||
.add_record("^foo (?<bar>[a-z]+) baz", "$wrong")
|
||||
.is_err())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_match_table<'gc, 'a>(
|
||||
ctx: Context<'gc>,
|
||||
_global_memo: &'a GlobalMemoCell,
|
||||
_global_layout: &'a UseStateSetter<GlobalLayoutCell>,
|
||||
) -> Callback<'gc> {
|
||||
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
|
||||
let _: () = stack.consume(ctx)?;
|
||||
let user_data = UserData::<'gc>::new::<Rootable!['gcb => GcRefLock<'gcb, MatchSubTable>]>(
|
||||
&ctx,
|
||||
GcRefLock::new(&ctx, <MatchSubTable as Default>::default().into()),
|
||||
);
|
||||
let match_table_class: Table = ctx
|
||||
.get_global::<Table>("classes")?
|
||||
.get(ctx, "match_table")?;
|
||||
user_data.set_metatable(&ctx, Some(match_table_class));
|
||||
|
||||
stack.push_back(user_data.into_value(ctx));
|
||||
Ok(piccolo::CallbackReturn::Return)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn match_table_add<'gc, 'a>(
|
||||
ctx: Context<'gc>,
|
||||
_global_memo: &'a GlobalMemoCell,
|
||||
) -> Callback<'gc> {
|
||||
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
|
||||
let (match_table, match_text, sub_text): (UserData, piccolo::String, piccolo::String) =
|
||||
stack.consume(ctx)?;
|
||||
match_table
|
||||
.downcast::<Rootable!['gcb => GcRefLock<'gcb, MatchSubTable>]>()?
|
||||
.borrow_mut(&ctx)
|
||||
.add_record(match_text.to_str()?, sub_text.to_str()?)?;
|
||||
Ok(piccolo::CallbackReturn::Return)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn match_table_remove<'gc, 'a>(
|
||||
ctx: Context<'gc>,
|
||||
_global_memo: &'a GlobalMemoCell,
|
||||
) -> Callback<'gc> {
|
||||
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
|
||||
let (match_table, match_text): (UserData, piccolo::String) = stack.consume(ctx)?;
|
||||
match_table
|
||||
.downcast::<Rootable!['gcb => GcRefLock<'gcb, MatchSubTable>]>()?
|
||||
.borrow_mut(&ctx)
|
||||
.remove_record(match_text.to_str()?)?;
|
||||
Ok(piccolo::CallbackReturn::Return)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn match_table_lua_table<'gc, 'a>(
|
||||
ctx: Context<'gc>,
|
||||
_global_memo: &'a GlobalMemoCell,
|
||||
) -> Callback<'gc> {
|
||||
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
|
||||
let match_table: UserData = stack.consume(ctx)?;
|
||||
stack.push_back(
|
||||
match_table
|
||||
.downcast::<Rootable!['gcb => GcRefLock<'gcb, MatchSubTable>]>()?
|
||||
.borrow_mut(&ctx)
|
||||
.to_value(ctx)?,
|
||||
);
|
||||
Ok(piccolo::CallbackReturn::Return)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn match_table_try_run_sub<'gc, 'a>(
|
||||
ctx: Context<'gc>,
|
||||
global_memo: &'a GlobalMemoCell,
|
||||
) -> Callback<'gc> {
|
||||
let global_memo = global_memo.clone();
|
||||
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
|
||||
let (match_table, sub, frame): (UserData, piccolo::String, Value) = stack.consume(ctx)?;
|
||||
let frame = try_unwrap_frame(ctx, &frame)?;
|
||||
|
||||
let cmds = match_table
|
||||
.downcast::<Rootable!['gcb => GcRefLock<'gcb, MatchSubTable>]>()?
|
||||
.borrow()
|
||||
.try_sub(sub.to_str()?);
|
||||
|
||||
match cmds {
|
||||
None => stack.push_back(false.into_value(ctx)),
|
||||
Some(cmds) => {
|
||||
let mut cq = global_memo.command_queue.borrow_mut();
|
||||
for cmd in cmds.into_iter().rev() {
|
||||
cq.push_front((frame.clone(), cmd));
|
||||
}
|
||||
stack.push_back(Value::Boolean(true))
|
||||
}
|
||||
}
|
||||
Ok(piccolo::CallbackReturn::Return)
|
||||
})
|
||||
}
|
||||
|
@ -65,6 +65,7 @@ pub fn connect_websocket(
|
||||
if data.has_type::<ArrayBuffer>() {
|
||||
handle_websocket_output_log_err(
|
||||
&data_new_id,
|
||||
&data_globals,
|
||||
&mut data_globals.lua_engine.borrow_mut(),
|
||||
&Uint8Array::new(&data).to_vec(),
|
||||
);
|
||||
@ -100,6 +101,7 @@ pub fn connect_websocket(
|
||||
closed_socket.retained_closures = None;
|
||||
handle_websocket_close_log_err(
|
||||
&close_id,
|
||||
&close_globals,
|
||||
&mut close_globals.lua_engine.borrow_mut(),
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user