Add tick / delay / untick timer commands.
This commit is contained in:
parent
cfe2e6cd6d
commit
56edc3d484
@ -181,6 +181,7 @@ pub fn install_lua_globals(
|
||||
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);
|
||||
@ -191,9 +192,12 @@ pub fn install_lua_globals(
|
||||
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);
|
||||
|
@ -2,6 +2,7 @@ use crate::{
|
||||
echo_to_term_frame,
|
||||
id_intern::intern_id,
|
||||
match_table::{create_match_table, match_table_add, match_table_remove},
|
||||
timer_host::TimerHostAccessContext,
|
||||
GlobalLayoutCell, GlobalLayoutState, GlobalMemoCell, TermFrame,
|
||||
};
|
||||
use gc_arena::{Gc, Rootable};
|
||||
@ -439,3 +440,238 @@ pub(super) fn frame_input<'gc>(ctx: Context<'gc>, _global_memo: &GlobalMemoCell)
|
||||
callback_return
|
||||
})
|
||||
}
|
||||
|
||||
fn list_timers<'gc>(
|
||||
ctx: Context<'gc>,
|
||||
global_memo: &GlobalMemoCell,
|
||||
cur_frame_id: TermFrame,
|
||||
) -> Result<(), Error<'gc>> {
|
||||
let timer_val = match global_memo.frame_registry.borrow().get(&cur_frame_id) {
|
||||
None => Err(anyhow::Error::msg("Frame no longer exists"))?,
|
||||
Some(frame_dat) => Table::from_value(ctx, frame_dat.timer_host.to_value(ctx)?)?,
|
||||
};
|
||||
if timer_val.iter().next().is_none() {
|
||||
let _ = echo_to_term_frame(
|
||||
global_memo,
|
||||
&cur_frame_id,
|
||||
"No timers are currently active.\r\n",
|
||||
);
|
||||
} else {
|
||||
let _ = echo_to_term_frame(
|
||||
global_memo,
|
||||
&cur_frame_id,
|
||||
&timer_val
|
||||
.iter()
|
||||
.map(|(_k, v)| {
|
||||
let t = Table::from_value(ctx, v)?;
|
||||
Ok(format!(
|
||||
"{}: {} {} {{{}}}\r\n",
|
||||
t.get::<&str, piccolo::String>(ctx, "name")?.to_str()?,
|
||||
if t.get::<&str, bool>(ctx, "recurring")? {
|
||||
"tick"
|
||||
} else {
|
||||
"delay"
|
||||
},
|
||||
t.get::<&str, i64>(ctx, "interval_millis")? as f64 / 1000.0,
|
||||
t.get::<&str, piccolo::String>(ctx, "commands")?.to_str()?,
|
||||
))
|
||||
})
|
||||
.collect::<Result<Vec<String>, Error<'gc>>>()?
|
||||
.iter()
|
||||
.join(""),
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct GlobalCellTermFrameTimerHostAccess {
|
||||
global_memo: GlobalMemoCell,
|
||||
frame: TermFrame,
|
||||
}
|
||||
impl TimerHostAccessContext for GlobalCellTermFrameTimerHostAccess {
|
||||
fn with_ref<F>(&self, f: F)
|
||||
where
|
||||
F: Fn(&mut crate::timer_host::TimerHost),
|
||||
{
|
||||
if let Some(frame_dat) = self
|
||||
.global_memo
|
||||
.frame_registry
|
||||
.borrow_mut()
|
||||
.get_mut(&self.frame)
|
||||
{
|
||||
f(&mut frame_dat.timer_host);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn tick<'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 info: Table = ctx.get_global("info")?;
|
||||
let cur_frame_id: TermFrame =
|
||||
try_unwrap_frame(ctx, &info.get(ctx, ctx.intern_static(b"current_frame"))?)?;
|
||||
|
||||
if stack.is_empty() {
|
||||
list_timers(ctx, &global_memo, cur_frame_id)?;
|
||||
return Ok(CallbackReturn::Return);
|
||||
}
|
||||
|
||||
let secs: f64 = f64::from_value(
|
||||
ctx,
|
||||
stack
|
||||
.pop_front()
|
||||
.ok_or_else(|| anyhow::Error::msg("Missing timer frequency"))?,
|
||||
)?;
|
||||
if secs < 0.001 {
|
||||
Err(anyhow::Error::msg("Minimum timer duration is 0.001s"))?;
|
||||
}
|
||||
if secs >= (2.0_f64).powi(31) {
|
||||
Err(anyhow::Error::msg("Timer duration is too long"))?;
|
||||
}
|
||||
let millis: u32 = (secs * 1000.0) as u32;
|
||||
|
||||
let commands = piccolo::String::from_value(
|
||||
ctx,
|
||||
stack
|
||||
.pop_front()
|
||||
.ok_or_else(|| anyhow::Error::msg("Missing timer commands"))?,
|
||||
)?
|
||||
.to_str()?;
|
||||
|
||||
let timer_name = match stack.len() {
|
||||
0 => None,
|
||||
1 => Some(stack.consume::<piccolo::String>(ctx)?.to_str()?.to_owned()),
|
||||
_ => Err(anyhow::Error::msg(
|
||||
"Extra arguments given; expecting #tick duration {command} optional_name",
|
||||
))?,
|
||||
};
|
||||
|
||||
let new_id = match global_memo
|
||||
.frame_registry
|
||||
.borrow_mut()
|
||||
.get_mut(&cur_frame_id)
|
||||
{
|
||||
None => Err(anyhow::Error::msg("Frame no longer exists"))?,
|
||||
Some(frame_dat) => frame_dat.timer_host.add_record(
|
||||
GlobalCellTermFrameTimerHostAccess {
|
||||
global_memo: global_memo.clone(),
|
||||
frame: cur_frame_id.clone(),
|
||||
},
|
||||
&global_memo,
|
||||
true,
|
||||
millis,
|
||||
commands.to_owned(),
|
||||
timer_name,
|
||||
)?,
|
||||
};
|
||||
|
||||
let new_id = new_id.into_value(ctx);
|
||||
ctx.get_global::<Table>("info")?
|
||||
.set(ctx, "last_timer", new_id)?;
|
||||
stack.push_back(new_id);
|
||||
|
||||
Ok(piccolo::CallbackReturn::Return)
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn delay<'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 info: Table = ctx.get_global("info")?;
|
||||
let cur_frame_id: TermFrame =
|
||||
try_unwrap_frame(ctx, &info.get(ctx, ctx.intern_static(b"current_frame"))?)?;
|
||||
|
||||
if stack.is_empty() {
|
||||
list_timers(ctx, &global_memo, cur_frame_id)?;
|
||||
return Ok(CallbackReturn::Return);
|
||||
}
|
||||
|
||||
let secs: f64 = f64::from_value(
|
||||
ctx,
|
||||
stack
|
||||
.pop_front()
|
||||
.ok_or_else(|| anyhow::Error::msg("Missing timer frequency"))?,
|
||||
)?;
|
||||
if secs < 0.001 {
|
||||
Err(anyhow::Error::msg("Minimum timer duration is 0.001s"))?;
|
||||
}
|
||||
if secs >= (2.0_f64).powi(31) {
|
||||
Err(anyhow::Error::msg("Timer duration is too long"))?;
|
||||
}
|
||||
let millis: u32 = (secs * 1000.0) as u32;
|
||||
|
||||
let commands = piccolo::String::from_value(
|
||||
ctx,
|
||||
stack
|
||||
.pop_front()
|
||||
.ok_or_else(|| anyhow::Error::msg("Missing timer commands"))?,
|
||||
)?
|
||||
.to_str()?;
|
||||
|
||||
let timer_name = match stack.len() {
|
||||
0 => None,
|
||||
1 => Some(stack.consume::<piccolo::String>(ctx)?.to_str()?.to_owned()),
|
||||
_ => Err(anyhow::Error::msg(
|
||||
"Extra arguments given; expecting #delay duration {command} optional_name",
|
||||
))?,
|
||||
};
|
||||
|
||||
let new_id = match global_memo
|
||||
.frame_registry
|
||||
.borrow_mut()
|
||||
.get_mut(&cur_frame_id)
|
||||
{
|
||||
None => Err(anyhow::Error::msg("Frame no longer exists"))?,
|
||||
Some(frame_dat) => frame_dat.timer_host.add_record(
|
||||
GlobalCellTermFrameTimerHostAccess {
|
||||
global_memo: global_memo.clone(),
|
||||
frame: cur_frame_id.clone(),
|
||||
},
|
||||
&global_memo,
|
||||
false,
|
||||
millis,
|
||||
commands.to_owned(),
|
||||
timer_name,
|
||||
)?,
|
||||
};
|
||||
|
||||
let new_id = new_id.into_value(ctx);
|
||||
ctx.get_global::<Table>("info")?
|
||||
.set(ctx, "last_timer", new_id)?;
|
||||
stack.push_back(new_id);
|
||||
|
||||
Ok(piccolo::CallbackReturn::Return)
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn untick<'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 info: Table = ctx.get_global("info")?;
|
||||
let cur_frame_id: TermFrame =
|
||||
try_unwrap_frame(ctx, &info.get(ctx, ctx.intern_static(b"current_frame"))?)?;
|
||||
let name = stack.consume::<piccolo::String>(ctx)?.to_str()?.to_owned();
|
||||
|
||||
match global_memo
|
||||
.frame_registry
|
||||
.borrow_mut()
|
||||
.get_mut(&cur_frame_id)
|
||||
{
|
||||
None => Err(anyhow::Error::msg("Frame no longer exists"))?,
|
||||
Some(frame_dat) => frame_dat.timer_host.remove_timer(name)?,
|
||||
};
|
||||
Ok(piccolo::CallbackReturn::Return)
|
||||
})
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ pub mod split_panel;
|
||||
pub mod telnet;
|
||||
pub mod term_split;
|
||||
pub mod term_view;
|
||||
pub mod timer_host;
|
||||
pub mod websocket;
|
||||
use crate::lua_engine::{install_lua_globals, LuaState};
|
||||
use crate::split_panel::*;
|
||||
|
@ -9,6 +9,7 @@ use crate::{
|
||||
command_handler::command_handler,
|
||||
lineengine::line::{Readline, ReadlineEvent},
|
||||
term_split::TermSplit,
|
||||
timer_host::TimerHost,
|
||||
GlobalLayoutCell, GlobalMemoCell, PanelDirection, SplitPanel,
|
||||
};
|
||||
|
||||
@ -77,6 +78,7 @@ pub struct TermFrameData {
|
||||
pub fit: FitAddon,
|
||||
pub node: Node,
|
||||
pub readline: Readline,
|
||||
pub timer_host: TimerHost,
|
||||
pub retained_closures: Option<(
|
||||
Closure<dyn FnMut(String)>,
|
||||
Closure<dyn FnMut(Dims)>,
|
||||
@ -136,6 +138,7 @@ fn get_or_make_term_frame<'a>(
|
||||
}),
|
||||
initial_size,
|
||||
),
|
||||
timer_host: TimerHost::new(frame.clone()),
|
||||
retained_closures: None,
|
||||
};
|
||||
|
||||
|
173
src/timer_host.rs
Normal file
173
src/timer_host.rs
Normal file
@ -0,0 +1,173 @@
|
||||
use anyhow::bail;
|
||||
use piccolo::{Context, IntoValue, Table, Value};
|
||||
use wasm_bindgen::{closure::Closure, JsCast};
|
||||
|
||||
use crate::{
|
||||
command_handler::execute_queue,
|
||||
parsing::{parse_commands, ParsedCommand},
|
||||
GlobalMemoCell, TermFrame,
|
||||
};
|
||||
|
||||
pub struct TimerHost {
|
||||
timers: Vec<TimerRecord>,
|
||||
frame_id: TermFrame,
|
||||
next_id: u64,
|
||||
}
|
||||
|
||||
impl Drop for TimerHost {
|
||||
fn drop(&mut self) {
|
||||
if let Some(window) = web_sys::window() {
|
||||
for timer in &self.timers {
|
||||
if timer.recurring {
|
||||
window.clear_interval_with_handle(timer.js_id);
|
||||
} else {
|
||||
window.clear_timeout_with_handle(timer.js_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TimerHost {
|
||||
pub fn new(frame_id: TermFrame) -> Self {
|
||||
Self {
|
||||
timers: vec![],
|
||||
frame_id,
|
||||
next_id: 1,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_record<AC>(
|
||||
&mut self,
|
||||
access_context: AC,
|
||||
global: &GlobalMemoCell,
|
||||
recurring: bool,
|
||||
interval_millis: u32,
|
||||
commands: String,
|
||||
timer_name: Option<String>,
|
||||
) -> anyhow::Result<String>
|
||||
where
|
||||
AC: TimerHostAccessContext + 'static,
|
||||
{
|
||||
let window = web_sys::window().ok_or_else(|| anyhow::Error::msg("Can't fetch window"))?;
|
||||
let new_id = self.next_id;
|
||||
self.next_id += 1;
|
||||
let global = global.clone();
|
||||
|
||||
if timer_name
|
||||
.as_ref()
|
||||
.map(|tn| tn.starts_with('#'))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
bail!(
|
||||
"Timer name can't start with #, it is reserved for referencing timers by number."
|
||||
);
|
||||
}
|
||||
|
||||
let frame = self.frame_id.clone();
|
||||
let callback_commands: Vec<ParsedCommand> = parse_commands(&commands).commands;
|
||||
let callback: Closure<dyn FnMut()> = Closure::new(move || {
|
||||
let mut cq = global.command_queue.borrow_mut();
|
||||
for cmd in &callback_commands {
|
||||
cq.push_back((frame.clone(), cmd.clone()));
|
||||
}
|
||||
drop(cq);
|
||||
|
||||
execute_queue(&global);
|
||||
|
||||
if !recurring {
|
||||
access_context.with_ref(|timer_host| {
|
||||
timer_host.timers.retain(|t| t.id != new_id);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let js_id = if recurring {
|
||||
window
|
||||
.set_interval_with_callback_and_timeout_and_arguments_0(
|
||||
callback.as_ref().unchecked_ref(),
|
||||
interval_millis as i32,
|
||||
)
|
||||
.map_err(|_| anyhow::Error::msg("setInterval failed"))?
|
||||
} else {
|
||||
window
|
||||
.set_timeout_with_callback_and_timeout_and_arguments_0(
|
||||
callback.as_ref().unchecked_ref(),
|
||||
interval_millis as i32,
|
||||
)
|
||||
.map_err(|_| anyhow::Error::msg("setTimeout failed"))?
|
||||
};
|
||||
|
||||
self.timers.push(TimerRecord {
|
||||
id: new_id,
|
||||
js_id,
|
||||
timer_name,
|
||||
recurring,
|
||||
interval_millis,
|
||||
commands,
|
||||
retained_closure: callback,
|
||||
});
|
||||
|
||||
Ok(format!("#{}", new_id))
|
||||
}
|
||||
|
||||
pub fn remove_timer(&mut self, timer_name: String) -> anyhow::Result<()> {
|
||||
let timer = if let Some(suffix) = timer_name.strip_prefix('#') {
|
||||
let id = suffix
|
||||
.parse::<u64>()
|
||||
.map_err(|_| anyhow::Error::msg("Should follow # with a number"))?;
|
||||
self.timers.iter().enumerate().find(|t| t.1.id == id)
|
||||
} else {
|
||||
self.timers
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|t| t.1.timer_name.as_ref() == Some(&timer_name))
|
||||
};
|
||||
let timer = timer.ok_or_else(|| anyhow::Error::msg("No matching timer found."))?;
|
||||
let window = web_sys::window().ok_or_else(|| anyhow::Error::msg("Can't fetch window"))?;
|
||||
if timer.1.recurring {
|
||||
window.clear_interval_with_handle(timer.1.js_id);
|
||||
} else {
|
||||
window.clear_timeout_with_handle(timer.1.js_id);
|
||||
}
|
||||
self.timers.remove(timer.0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn to_value<'gc>(&self, ctx: Context<'gc>) -> anyhow::Result<Value<'gc>> {
|
||||
let tbl = Table::new(&ctx);
|
||||
for timer in self.timers.iter() {
|
||||
let timer_tbl = Table::new(&ctx);
|
||||
timer_tbl.set(
|
||||
ctx,
|
||||
"name",
|
||||
timer
|
||||
.timer_name
|
||||
.clone()
|
||||
.unwrap_or_else(|| format!("#{}", timer.id)),
|
||||
)?;
|
||||
timer_tbl.set(ctx, "commands", timer.commands.clone())?;
|
||||
timer_tbl.set(ctx, "recurring", timer.recurring)?;
|
||||
timer_tbl.set(ctx, "interval_millis", timer.interval_millis)?;
|
||||
tbl.set(ctx, timer.id as i64, timer_tbl)?;
|
||||
}
|
||||
Ok(tbl.into_value(ctx))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait TimerHostAccessContext {
|
||||
fn with_ref<F>(&self, f: F)
|
||||
where
|
||||
F: Fn(&mut TimerHost);
|
||||
}
|
||||
|
||||
pub struct TimerRecord {
|
||||
id: u64,
|
||||
js_id: i32,
|
||||
timer_name: Option<String>,
|
||||
recurring: bool,
|
||||
interval_millis: u32,
|
||||
commands: String,
|
||||
#[allow(dead_code)]
|
||||
retained_closure: Closure<dyn FnMut()>,
|
||||
}
|
Loading…
Reference in New Issue
Block a user