use crate::host::{is_callable, take_exc_or_error, type_error, with_host, JsObj};
use fusevm::Value;
use indexmap::IndexMap;
use std::cell::RefCell;
pub const METHODS: &[&str] = &["create", "createDomain"];
pub const DOMAIN_METHODS: &[&str] = &[
"run",
"add",
"remove",
"bind",
"intercept",
"enter",
"exit",
"dispose",
];
thread_local! {
static STACK: RefCell<Vec<Value>> = const { RefCell::new(Vec::new()) };
}
pub fn new_domain() -> Value {
let members = with_host(|h| h.new_array(Vec::new()));
let mut extra = IndexMap::new();
extra.insert("@@members".to_string(), members);
super::net::new_emitter_object("Domain", extra)
}
pub fn call(method: &str, _args: &[Value]) -> Option<Result<Value, String>> {
match method {
"create" | "createDomain" => Some(Ok(new_domain())),
_ => None,
}
}
pub fn construct(_args: &[Value]) -> Result<Value, String> {
Ok(new_domain())
}
pub fn constant(name: &str) -> Option<Value> {
match name {
"active" => Some(active().unwrap_or_else(|| with_host(|h| h.null()))),
_ => None,
}
}
pub fn instance_call(recv: &Value, method: &str, args: Vec<Value>) -> Result<Value, String> {
if let Some(r) = emitter_dispatch(recv, method, &args) {
return r;
}
match method {
"run" => {
let f = args.first().cloned().unwrap_or(Value::Undef);
let call_args = args.get(1..).map(|s| s.to_vec()).unwrap_or_default();
domain_run(recv, &f, call_args)
}
"add" => {
if let Some(e) = args.into_iter().next() {
track(recv, e, true);
}
Ok(Value::Undef)
}
"remove" => {
if let Some(e) = args.into_iter().next() {
track(recv, e, false);
}
Ok(Value::Undef)
}
"bind" => Ok(make_wrapper(
recv,
args.into_iter().next().unwrap_or(Value::Undef),
"@@bound",
)),
"intercept" => Ok(make_wrapper(
recv,
args.into_iter().next().unwrap_or(Value::Undef),
"@@intercept",
)),
"enter" => {
enter(recv);
Ok(Value::Undef)
}
"exit" => {
exit(recv);
Ok(Value::Undef)
}
"dispose" => Ok(Value::Undef),
"@@bound" => {
let domain = get_prop(recv, "@@boundDomain").unwrap_or_else(|| recv.clone());
let f = get_prop(recv, "@@boundFn").unwrap_or(Value::Undef);
domain_run(&domain, &f, args)
}
"@@intercept" => {
let domain = get_prop(recv, "@@boundDomain").unwrap_or_else(|| recv.clone());
let f = get_prop(recv, "@@boundFn").unwrap_or(Value::Undef);
let err = args.first().cloned().unwrap_or(Value::Undef);
let is_err = with_host(|h| !matches!(err, Value::Undef) && !h.is_null(&err));
if is_err {
emit_error(&domain, err);
Ok(Value::Undef)
} else {
let rest = args.get(1..).map(|s| s.to_vec()).unwrap_or_default();
domain_run(&domain, &f, rest)
}
}
_ => Err(type_error(&format!("domain.{method} is not a function"))),
}
}
fn domain_run(domain: &Value, f: &Value, call_args: Vec<Value>) -> Result<Value, String> {
if !with_host(|h| is_callable(h, f)) {
return Err(type_error("domain.run requires a function"));
}
enter(domain);
let r = crate::host::invoke(f, call_args, None);
exit(domain);
match r {
Ok(v) => Ok(v),
Err(e) => {
let err = take_exc_or_error(&e);
with_host(|h| h.signal = None);
emit_error(domain, err);
Ok(Value::Undef)
}
}
}
fn emit_error(domain: &Value, err: Value) {
let name = with_host(|h| h.new_str("error"));
let _ = super::events::instance_call(domain, "emit", vec![name, err]);
}
fn make_wrapper(domain: &Value, f: Value, kind: &str) -> Value {
let mut extra = IndexMap::new();
extra.insert("@@boundFn".to_string(), f);
extra.insert("@@boundDomain".to_string(), domain.clone());
let holder = super::net::new_emitter_object("Domain", extra);
with_host(|h| {
h.alloc(JsObj::BoundMethod {
recv: holder,
name: kind.to_string(),
})
})
}
fn enter(domain: &Value) {
STACK.with(|s| s.borrow_mut().push(domain.clone()));
}
fn exit(domain: &Value) {
STACK.with(|s| {
let mut s = s.borrow_mut();
if let Some(pos) = s.iter().rposition(|x| x == domain) {
s.remove(pos);
}
});
}
fn active() -> Option<Value> {
STACK.with(|s| s.borrow().last().cloned())
}
fn track(recv: &Value, emitter: Value, add: bool) {
with_host(|h| {
let arr = match h.get(recv) {
Some(JsObj::Object(p)) => p.get("@@members").cloned(),
_ => None,
};
if let Some(a) = arr {
if let Some(JsObj::Array(items)) = h.get_mut(&a) {
if add {
if !items.iter().any(|x| x == &emitter) {
items.push(emitter);
}
} else if let Some(pos) = items.iter().position(|x| x == &emitter) {
items.remove(pos);
}
}
}
});
}
fn get_prop(recv: &Value, key: &str) -> Option<Value> {
with_host(|h| match h.get(recv) {
Some(JsObj::Object(p)) => p.get(key).cloned(),
_ => None,
})
}
fn emitter_dispatch(recv: &Value, method: &str, args: &[Value]) -> Option<Result<Value, String>> {
super::events::METHODS
.contains(&method)
.then(|| super::events::instance_call(recv, method, args.to_vec()))
}