use core_mumu::{
parser::interpreter::apply::apply_n_ary_function_value,
parser::types::Value,
Interpreter,
};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use lazy_static::lazy_static;
pub struct IntervalTask {
pub id: i32, pub period: Duration, pub next_tick: Instant,
pub callback: Value,
pub canceled: bool,
}
lazy_static! {
pub static ref INTERVAL_TASKS: Mutex<Vec<IntervalTask>> = Mutex::new(Vec::new());
}
lazy_static! {
static ref NEXT_INTERVAL_ID: Mutex<i32> = Mutex::new(1000);
}
pub fn event_interval_bridge(
interp: &mut Interpreter,
args: Vec<Value>
) -> Result<Value, String> {
match args.len() {
0 => {
Ok(make_interval_partial(None, None))
}
1 => {
let first = &args[0];
match first {
Value::Placeholder => Ok(make_interval_partial(None, None)),
Value::Int(_) | Value::IntArray(_) => {
Ok(make_interval_partial(Some(first.clone()), None))
}
Value::Function(_) => {
Ok(make_interval_partial(None, Some(first.clone())))
}
other => Err(format!(
"event:interval => single arg must be int, function, or '_', got {:?}",
other
)),
}
}
2 => {
let ms_val = args[0].clone();
let cb_val = args[1].clone();
let ms_is_pl = matches!(ms_val, Value::Placeholder);
let cb_is_pl = matches!(cb_val, Value::Placeholder);
if !ms_is_pl && !cb_is_pl {
schedule_interval(interp, ms_val, cb_val)
} else {
let maybe_ms = if ms_is_pl { None } else { Some(ms_val) };
let maybe_cb = if cb_is_pl { None } else { Some(cb_val) };
Ok(make_interval_partial(maybe_ms, maybe_cb))
}
}
n => Err(format!(
"event:interval => expected up to 2 arguments, got {}",
n
)),
}
}
fn make_interval_partial(ms_opt: Option<Value>, cb_opt: Option<Value>) -> Value {
use core_mumu::parser::types::FunctionValue::RustClosure;
let closure = move |interp: &mut Interpreter, new_args: Vec<Value>| {
let mut current_ms = ms_opt.clone();
let mut current_cb = cb_opt.clone();
for arg in new_args {
if current_ms.is_none() {
if matches!(arg, Value::Placeholder) {
} else {
current_ms = Some(arg);
}
continue;
}
if current_cb.is_none() {
if matches!(arg, Value::Placeholder) {
} else {
current_cb = Some(arg);
}
continue;
}
return Err("event:interval => partial => too many arguments".to_string());
}
if current_ms.is_some() && current_cb.is_some() {
schedule_interval(interp, current_ms.as_ref().unwrap().clone(), current_cb.as_ref().unwrap().clone())
} else {
Ok(make_interval_partial(current_ms, current_cb))
}
};
Value::Function(Box::new(RustClosure(
"event:interval-partial".to_string(),
Arc::new(Mutex::new(closure)),
0,
)))
}
fn schedule_interval(
interp: &mut Interpreter,
ms_val: Value,
cb_val: Value
) -> Result<Value, String> {
let ms_i32 = match ms_val {
Value::Int(n) => n,
Value::IntArray(ref arr) if arr.len() == 1 => arr[0],
other => {
return Err(format!(
"event:interval => 'ms' must be int or single-element IntArray, got {:?}",
other
));
}
};
if ms_i32 < 0 {
return Err("event:interval => 'ms' must be >= 0".to_string());
}
if let Value::Function(_) = cb_val {
} else {
return Err(format!(
"event:interval => second argument must be Function(...), got {:?}",
cb_val
));
}
let mut lock_id = NEXT_INTERVAL_ID.lock().unwrap();
let new_id = *lock_id;
*lock_id += 1;
drop(lock_id);
let period = Duration::from_millis(ms_i32 as u64);
let now = Instant::now();
let new_task = IntervalTask {
id: new_id,
period,
next_tick: now + period,
callback: cb_val,
canceled: false,
};
{
let mut lock = INTERVAL_TASKS.lock().unwrap();
lock.push(new_task);
}
if interp.is_verbose() {
eprintln!(
"[event:interval] => created new IntervalTask => id={}, ms={}, next_tick={:?}",
new_id, ms_i32, now + period
);
}
Ok(Value::Int(new_id))
}
pub fn execute_interval_callback(
interp: &mut Interpreter,
cb_val: &Value
) -> Result<(), String> {
if let Value::Function(fb) = cb_val {
apply_n_ary_function_value(interp, fb.clone(), vec![])?;
Ok(())
} else {
Err("execute_interval_callback => not a function".to_string())
}
}
pub fn event_stop_bridge(
_interp: &mut Interpreter,
mut args: Vec<Value>
) -> Result<Value, String> {
if args.len() != 1 {
return Err(format!("event:stop => expected 1 argument => the handle, got {}", args.len()));
}
let handle_val = args.remove(0);
let id_i32 = match handle_val {
Value::Int(i) => i,
Value::IntArray(ref arr) if arr.len() == 1 => arr[0],
other => {
return Err(format!(
"event:stop => handle must be an int or single-element IntArray, got {:?}",
other
));
}
};
let mut _canceled = false;
{
let mut lock = INTERVAL_TASKS.lock().unwrap();
for task in lock.iter_mut() {
if task.id == id_i32 {
task.canceled = true;
_canceled = true;
break;
}
}
}
crate::r#loop::stop_loop_task_by_id(id_i32);
Ok(Value::Bool(true))
}