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::{is_placeholder, to_transform};
pub fn register_flow_drain_then(interp: &mut Interpreter) {
let f = Arc::new(Mutex::new(flow_drain_then_bridge));
let info = DynamicFnInfo::new(f, true);
interp.register_dynamic_function_ex("flow:drain_then", info);
interp.set_variable(
"flow:drain_then",
Value::Function(Box::new(FunctionValue::Named("flow:drain_then".to_string()))),
);
}
fn flow_drain_then_bridge(interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, String> {
match args.len() {
0 => Ok(make_partial(None, None)),
1 => {
let a = &args[0];
if is_placeholder(a) {
Ok(make_partial(None, None))
} else if let Value::Function(fb) = a {
Ok(make_partial(None, Some(Value::Function(fb.clone()))))
} else {
Ok(make_partial(Some(a.clone()), None))
}
}
2 => {
let src = &args[0];
let cb = &args[1];
if is_placeholder(src) || is_placeholder(cb) {
let s_opt = if is_placeholder(src) { None } else { Some(src.clone()) };
let c_opt = if is_placeholder(cb) { None } else { Some(cb.clone()) };
return Ok(make_partial(s_opt, c_opt));
}
let cb_fn = match cb {
Value::Function(fb) => fb.clone(),
other => {
return Err(format!(
"flow:drain_then => callback must be Function, got {:?}",
other
))
}
};
spawn_drain_then(interp, src.clone(), cb_fn)
}
n => Err(format!(
"flow:drain_then => expected 0, 1, or 2 arguments, got {}",
n
)),
}
}
fn make_partial(src_opt: Option<Value>, cb_opt: Option<Value>) -> Value {
use mumu::parser::types::FunctionValue::RustClosure;
Value::Function(Box::new(RustClosure(
"flow:drain_then-partial".to_string(),
Arc::new(Mutex::new(move |interp: &mut Interpreter, new_args: Vec<Value>| {
let mut s = src_opt.clone();
let mut c = cb_opt.clone();
for a in new_args {
if s.is_none() {
if !is_placeholder(&a) {
s = Some(a);
}
continue;
}
if c.is_none() {
if !is_placeholder(&a) {
c = Some(a);
}
continue;
}
return Err("flow:drain_then => partial => too many arguments".to_string());
}
match (s.clone(), c.clone()) {
(Some(source), Some(cb_val)) => {
let cb_fn = match cb_val {
Value::Function(fb) => fb,
other => {
return Err(format!(
"flow:drain_then => callback must be Function, got {:?}",
other
))
}
};
spawn_drain_then(interp, source, cb_fn)
}
_ => Ok(make_partial(s, c)),
}
})),
0,
)))
}
fn spawn_drain_then(
interp: &mut Interpreter,
source: Value,
cb_fn: Box<FunctionValue>,
) -> Result<Value, String> {
let tf = to_transform(source)?;
struct DrainState {
tf: Box<FunctionValue>,
count: i32,
done: bool,
callback: Box<FunctionValue>,
callback_fired: bool,
}
let state = Arc::new(Mutex::new(DrainState {
tf,
count: 0,
done: false,
callback: cb_fn,
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 guard = st_for_poller.lock().unwrap();
if guard.done && !guard.callback_fired {
let total = guard.count;
let cb = guard.callback.clone();
guard.callback_fired = true;
drop(guard);
let _ = apply_n_ary_function_value(interp, cb, vec![Value::Int(total)]);
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.clone();
guard.callback_fired = true;
drop(guard);
let _ = apply_n_ary_function_value(interp, cb, 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))
}