use std::cmp::Reverse;
use std::collections::BinaryHeap;
use std::sync::{Arc, Mutex};
use mimium_lang::runtime::Time;
use mimium_lang::runtime::wasm::WasmSystemPluginAudioWorker;
use crate::scheduler::Task;
#[derive(Debug, Default)]
struct SharedState {
tasks: BinaryHeap<Reverse<Task>>,
current_time: u64,
}
#[derive(Clone, Debug, Default)]
pub struct WasmSchedulerHandle {
state: Arc<Mutex<SharedState>>,
}
impl WasmSchedulerHandle {
pub fn set_current_time(&self, time: u64) {
self.state.lock().unwrap().current_time = time;
}
pub fn drain_due_tasks(&self) -> Vec<i64> {
let mut state = self.state.lock().unwrap();
let now = Time(state.current_time);
let mut ready = Vec::new();
while let Some(Reverse(task)) = state.tasks.peek() {
if task.when <= now {
ready.push(state.tasks.pop().unwrap().0.closure as i64);
} else {
break;
}
}
ready
}
pub fn into_wasm_plugin_fn_map(&self) -> mimium_lang::runtime::wasm::WasmPluginFnMap {
let state = Arc::clone(&self.state);
let schedule_fn: mimium_lang::runtime::wasm::WasmPluginFn =
Arc::new(move |args: &[f64]| -> Option<f64> {
if args.len() < 2 {
log::error!("_mimium_schedule_at: expected 2 args, got {}", args.len());
return Some(0.0);
}
let when = args[0] as u64;
let closure_addr = args[1] as i64;
let mut s = state.lock().unwrap();
if when <= s.current_time {
panic!(
"Scheduled time {} must be in the future (current time: {})",
when, s.current_time
);
}
s.tasks
.push(Reverse(Task::new(Time(when), closure_addr as u64)));
Some(0.0)
});
let mut map = mimium_lang::runtime::wasm::WasmPluginFnMap::new();
map.insert("_mimium_schedule_at".to_string(), schedule_fn);
map
}
}
impl WasmSystemPluginAudioWorker for WasmSchedulerHandle {
fn on_sample(
&mut self,
time: mimium_lang::runtime::Time,
engine: &mut mimium_lang::runtime::wasm::engine::WasmEngine,
) -> mimium_lang::runtime::vm::ReturnCode {
self.set_current_time(time.0);
for closure_addr in self.drain_due_tasks() {
if let Err(e) =
engine.execute_function("_mimium_exec_closure_void", &[closure_addr as u64])
{
log::error!("_mimium_exec_closure_void error: {e}");
}
}
0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_schedule_and_drain() {
let handle = WasmSchedulerHandle::default();
let map = handle.into_wasm_plugin_fn_map();
let schedule = map.get("_mimium_schedule_at").unwrap();
schedule(&[3.0, 100.0]); schedule(&[5.0, 200.0]);
handle.set_current_time(2);
assert!(handle.drain_due_tasks().is_empty());
handle.set_current_time(3);
let due = handle.drain_due_tasks();
assert_eq!(due, vec![100]);
handle.set_current_time(10);
let due = handle.drain_due_tasks();
assert_eq!(due, vec![200]);
assert!(handle.drain_due_tasks().is_empty());
}
}