use crate::host::{invoke, with_host};
use fusevm::Value;
use indexmap::IndexMap;
use std::cell::RefCell;
use std::collections::HashMap;
thread_local! {
static STORES: RefCell<HashMap<u32, Vec<Value>>> = RefCell::new(HashMap::new());
static NEXT_ASYNC_ID: RefCell<f64> = const { RefCell::new(2.0) };
static EXEC_STACK: RefCell<Vec<(f64, f64)>> = const { RefCell::new(Vec::new()) };
}
pub fn execution_async_id() -> f64 {
EXEC_STACK.with(|s| s.borrow().last().map(|p| p.0).unwrap_or(1.0))
}
pub fn trigger_async_id() -> f64 {
EXEC_STACK.with(|s| s.borrow().last().map(|p| p.1).unwrap_or(0.0))
}
fn fresh_async_id() -> f64 {
NEXT_ASYNC_ID.with(|n| {
let mut n = n.borrow_mut();
let id = *n;
*n += 1.0;
id
})
}
fn in_async_scope<T>(async_id: f64, trigger_id: f64, f: impl FnOnce() -> T) -> T {
EXEC_STACK.with(|s| s.borrow_mut().push((async_id, trigger_id)));
let r = f();
EXEC_STACK.with(|s| {
s.borrow_mut().pop();
});
r
}
pub const METHODS: &[&str] = &["executionAsyncId", "triggerAsyncId", "createHook"];
pub const ALS_METHODS: &[&str] = &["getStore", "run", "enterWith", "exit", "disable"];
pub const HOOK_METHODS: &[&str] = &["enable", "disable"];
pub const RESOURCE_METHODS: &[&str] = &[
"runInAsyncScope",
"emitDestroy",
"asyncId",
"triggerAsyncId",
"bind",
];
pub const RESOURCE_STATIC_METHODS: &[&str] = &["bind"];
pub fn static_call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
match method {
"bind" => {
let f = args.first().cloned().unwrap_or(Value::Undef);
Some(Ok(bind_to(f, args.get(2).cloned())))
}
_ => None,
}
}
fn bind_to(f: Value, this: Option<Value>) -> Value {
match this.filter(|t| !with_host(|h| h.is_nullish(t))) {
Some(t) => with_host(|h| {
h.alloc(crate::host::JsObj::BoundFunc {
target: f,
this: t,
args: Vec::new(),
})
}),
None => f,
}
}
pub fn call(method: &str, _args: &[Value]) -> Option<Result<Value, String>> {
Some(match method {
"executionAsyncId" => Ok(Value::Float(execution_async_id())),
"triggerAsyncId" => Ok(Value::Float(trigger_async_id())),
"createHook" => Ok(new_hook()),
_ => return None,
})
}
pub fn construct(name: &str, args: &[Value]) -> Option<Result<Value, String>> {
match name {
"AsyncLocalStorage" => Some(Ok(new_native("AsyncLocalStorage"))),
"AsyncResource" => {
let r = new_native("AsyncResource");
let trigger = args
.get(1)
.and_then(|o| {
with_host(|h| match h.get(o) {
Some(crate::host::JsObj::Object(p)) => p
.get("triggerAsyncId")
.filter(|v| !matches!(v, Value::Undef))
.map(|v| h.to_number(v)),
_ => None,
})
})
.unwrap_or_else(execution_async_id);
with_host(|h| {
if let Some(crate::host::JsObj::Object(p)) = h.get_mut(&r) {
p.insert("@@asyncId".into(), Value::Float(fresh_async_id()));
p.insert("@@triggerAsyncId".into(), Value::Float(trigger));
}
});
Some(Ok(r))
}
_ => None,
}
}
fn new_native(tag: &'static str) -> Value {
with_host(|h| {
let mut m = IndexMap::new();
m.insert("@@native".into(), h.new_str(tag));
h.new_object(m)
})
}
fn hidden_num(recv: &Value, key: &str, fallback: f64) -> f64 {
with_host(|h| match h.get(recv) {
Some(crate::host::JsObj::Object(p)) => {
p.get(key).map(|v| h.to_number(v)).unwrap_or(fallback)
}
_ => fallback,
})
}
fn new_hook() -> Value {
new_native("AsyncHook")
}
pub fn instance_call(
tag: &str,
recv: &Value,
method: &str,
args: Vec<Value>,
) -> Result<Value, String> {
match tag {
"AsyncHook" => match method {
"enable" | "disable" => Ok(recv.clone()),
_ => Err(crate::host::type_error(&format!(
"{method} is not a function"
))),
},
"AsyncLocalStorage" => als_call(recv, method, args),
"AsyncResource" => resource_call(recv, method, args),
_ => Err(crate::host::type_error(&format!(
"{method} is not a function"
))),
}
}
fn resource_call(recv: &Value, method: &str, args: Vec<Value>) -> Result<Value, String> {
match method {
"runInAsyncScope" => {
let f = args.first().cloned().unwrap_or(Value::Undef);
let this = args.get(1).cloned();
let rest = args.get(2..).map(|s| s.to_vec()).unwrap_or_default();
let id = hidden_num(recv, "@@asyncId", 1.0);
let trigger = hidden_num(recv, "@@triggerAsyncId", 0.0);
in_async_scope(id, trigger, || invoke(&f, rest, this))
}
"bind" => {
let f = args.first().cloned().unwrap_or(Value::Undef);
Ok(bind_to(f, args.get(1).cloned()))
}
"emitDestroy" => Ok(recv.clone()),
"asyncId" => Ok(Value::Float(hidden_num(recv, "@@asyncId", 1.0))),
"triggerAsyncId" => Ok(Value::Float(hidden_num(recv, "@@triggerAsyncId", 0.0))),
_ => Err(crate::host::type_error(&format!(
"{method} is not a function"
))),
}
}
fn key(recv: &Value) -> u32 {
match recv {
Value::Obj(i) => *i,
_ => 0,
}
}
fn als_call(recv: &Value, method: &str, args: Vec<Value>) -> Result<Value, String> {
let id = key(recv);
match method {
"getStore" => Ok(STORES.with(|s| {
s.borrow()
.get(&id)
.and_then(|v| v.last().cloned())
.unwrap_or(Value::Undef)
})),
"run" => {
let store = args.first().cloned().unwrap_or(Value::Undef);
let cb = args.get(1).cloned().unwrap_or(Value::Undef);
let rest = args.get(2..).map(|s| s.to_vec()).unwrap_or_default();
with_store(id, store, cb, rest)
}
"exit" => {
let cb = args.first().cloned().unwrap_or(Value::Undef);
let rest = args.get(1..).map(|s| s.to_vec()).unwrap_or_default();
with_store(id, Value::Undef, cb, rest)
}
"enterWith" => {
let store = args.first().cloned().unwrap_or(Value::Undef);
STORES.with(|s| s.borrow_mut().entry(id).or_default().push(store));
Ok(Value::Undef)
}
"disable" => {
STORES.with(|s| {
s.borrow_mut().remove(&id);
});
Ok(Value::Undef)
}
_ => Err(crate::host::type_error(&format!(
"{method} is not a function"
))),
}
}
fn with_store(id: u32, store: Value, cb: Value, rest: Vec<Value>) -> Result<Value, String> {
STORES.with(|s| s.borrow_mut().entry(id).or_default().push(store));
let r = invoke(&cb, rest, None);
STORES.with(|s| {
if let Some(v) = s.borrow_mut().get_mut(&id) {
v.pop();
}
});
r
}