mumu-event 0.1.1

event plugin for the mumu ecosystem
Documentation
// src/loop.rs

// event/src/loop.rs
//
// Implements `event:loop(cb)` – the callback is executed on **every interpreter poll tick**.

use core_mumu::{
    parser::{
        interpreter::apply::apply_n_ary_function_value,
        types::Value,
    },
    Interpreter,
};
use std::sync::Mutex;
use lazy_static::lazy_static;

/// One endless-loop task.
pub struct LoopTask {
    pub id: i32,
    pub callback: Value,
    pub canceled: bool,
}

lazy_static! {
    pub static ref LOOP_TASKS: Mutex<Vec<LoopTask>> = Mutex::new(Vec::new());
    static ref NEXT_LOOP_ID: Mutex<i32> = Mutex::new(4000);
}

/// `event:loop(cb)` bridge. Returns an Int handle.
/// Partial application is not supported here (unlike interval/timeout), but can be added if needed.
pub fn event_loop_bridge(
    interp: &mut Interpreter,
    mut args: Vec<Value>
) -> Result<Value, String> {
    if args.len() != 1 {
        return Err(format!(
            "event:loop ⇒ expected exactly 1 argument (callback), got {}",
            args.len()
        ));
    }

    let cb = args.remove(0);
    if !matches!(cb, Value::Function(_)) {
        return Err(format!(
            "event:loop ⇒ argument must be a Function(...), got {:?}",
            cb
        ));
    }

    // Generate unique ID
    let mut id_lock = NEXT_LOOP_ID.lock().unwrap();
    let id = *id_lock;
    *id_lock += 1;
    drop(id_lock);

    // Store task
    LOOP_TASKS.lock().unwrap().push(LoopTask {
        id,
        callback: cb,
        canceled: false,
    });

    if interp.is_verbose() {
        eprintln!("[event:loop] ⇒ registered LoopTask id={}", id);
    }

    Ok(Value::Int(id))
}

/// Called from the main poller each tick.  
/// Executes every non-canceled loop callback and returns how many tasks remain.
pub fn execute_loop_callbacks(interp: &mut Interpreter) -> usize {
    let mut tasks = LOOP_TASKS.lock().unwrap();
    let mut count = 0;

    for task in tasks.iter_mut() {
        if task.canceled {
            continue;
        }
        if let Value::Function(f) = &task.callback {
            // Ignore user errors; keep looping.
            let _ = apply_n_ary_function_value(interp, f.clone(), vec![]);
        }
        count += 1;
    }
    count
}

/// Stop (cancel) a LoopTask by its handler.
pub fn stop_loop_task_by_id(id: i32) {
    let mut tasks = LOOP_TASKS.lock().unwrap();
    for task in tasks.iter_mut() {
        if task.id == id {
            task.canceled = true;
            break;
        }
    }
}