use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use mumu::parser::interpreter::{DynamicFnInfo, Interpreter, PollerFn};
use mumu::parser::interpreter::apply::apply_n_ary_function_value;
use mumu::parser::types::{FunctionValue, Value};
use crate::common::to_transform;
pub fn register_flow_drain_async(interp: &mut Interpreter) {
let f = Arc::new(Mutex::new(flow_drain_async_bridge));
let info = DynamicFnInfo::new(f, true);
interp.register_dynamic_function_ex("flow:drain_async", info);
interp.set_variable(
"flow:drain_async",
Value::Function(Box::new(FunctionValue::Named("flow:drain_async".to_string()))),
);
}
fn flow_drain_async_bridge(interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, String> {
match args.len() {
1 => spawn_drain(interp, args[0].clone(), None),
2 => {
let src = args[0].clone();
let cb = match &args[1] {
Value::Function(fb) => Some(fb.clone()),
other => return Err(format!("flow:drain_async => callback must be Function, got {:?}", other)),
};
spawn_drain(interp, src, cb)
}
n => Err(format!("flow:drain_async => expected 1 or 2 args (source [, callback]), got {}", n)),
}
}
fn spawn_drain(interp: &mut Interpreter, source: Value, cb: Option<Box<FunctionValue>>) -> Result<Value, String> {
let tf = to_transform(source)?;
struct DrainState {
tf: Box<FunctionValue>,
count: i32,
done: bool,
callback: Option<Box<FunctionValue>>,
callback_fired: bool,
}
let state = Arc::new(Mutex::new(DrainState {
tf,
count: 0,
done: false,
callback: cb,
callback_fired: false,
}));
let burst_per_tick: usize = std::env::var("LAVA_FLOW_BURST")
.ok()
.and_then(|s| s.parse::<usize>().ok())
.unwrap_or(64);
let budget_us: u64 = std::env::var("LAVA_FLOW_BUDGET_US")
.ok()
.and_then(|s| s.parse::<u64>().ok())
.unwrap_or(500);
let time_budget = Duration::from_micros(budget_us);
let st_for_poller = Arc::clone(&state);
let poller: PollerFn = Arc::new(Mutex::new(move |interp: &mut Interpreter| {
let tick_start = Instant::now();
let mut processed = 0usize;
let mut saw_again = false;
let mut cb_to_fire: Option<(Box<FunctionValue>, i32)> = None;
{
let mut guard = st_for_poller.lock().unwrap();
if guard.done {
if !guard.callback_fired {
let cb = guard.callback.take();
guard.callback_fired = true;
if let Some(cb_fn) = cb {
cb_to_fire = Some((cb_fn, 0));
}
}
}
}
if let Some((cb_fn, c)) = cb_to_fire.take() {
let _ = apply_n_ary_function_value(interp, cb_fn, vec![Value::Int(c)]);
return 0;
}
loop {
let tf_clone = {
let guard = st_for_poller.lock().unwrap();
if guard.done {
break;
}
guard.tf.clone()
};
match apply_n_ary_function_value(interp, tf_clone, vec![]) {
Ok(_v) => {
let mut guard = st_for_poller.lock().unwrap();
guard.count = guard.count.saturating_add(1);
drop(guard);
processed += 1;
}
Err(e) if e == "AGAIN" => {
saw_again = true;
break;
}
Err(e) if e == "NO_MORE_DATA" => {
let mut guard = st_for_poller.lock().unwrap();
guard.done = true;
let total = guard.count;
let cb = guard.callback.take();
guard.callback_fired = true;
drop(guard);
if let Some(cb_fn) = cb {
let _ = apply_n_ary_function_value(interp, cb_fn, vec![Value::Int(total)]);
}
break;
}
Err(_e) => {
let mut guard = st_for_poller.lock().unwrap();
guard.done = true;
drop(guard);
break;
}
}
if processed >= burst_per_tick || tick_start.elapsed() >= time_budget {
break;
}
}
if processed == 0 && saw_again {
return 1;
}
processed
}));
interp.add_poller(poller);
Ok(Value::Int(0))
}