1use crate::host::{with_host, JsObj};
20use fusevm::Value;
21use indexmap::IndexMap;
22use std::cell::RefCell;
23use std::collections::HashMap;
24
25thread_local! {
26 static CHANNELS: RefCell<HashMap<String, Value>> = RefCell::new(HashMap::new());
28}
29
30pub const METHODS: &[&str] = &[
31 "channel",
32 "subscribe",
33 "unsubscribe",
34 "hasSubscribers",
35 "tracingChannel",
36 "boundedChannel",
37];
38
39pub const TRACING_CHANNEL_METHODS: &[&str] = &["subscribe", "unsubscribe", "traceSync"];
42
43const TRACING_SUBS: &[&str] = &["start", "end", "asyncStart", "asyncEnd", "error"];
45
46pub fn call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
47 let name = super::arg_str(args, 0);
48 Some(match method {
49 "channel" => Ok(get_or_create(&name)),
50 "subscribe" => {
53 let ch = get_or_create(&name);
54 add_sub(&ch, args.get(1).cloned().unwrap_or(Value::Undef));
55 Ok(Value::Undef)
56 }
57 "unsubscribe" => {
58 let ch = get_or_create(&name);
59 Ok(Value::Bool(remove_sub(
60 &ch,
61 &args.get(1).cloned().unwrap_or(Value::Undef),
62 )))
63 }
64 "hasSubscribers" => Ok(Value::Bool(sub_count(&get_or_create(&name)) > 0)),
65 "tracingChannel" => Ok(tracing_channel(&name)),
68 "boundedChannel" => Ok(get_or_create(&name)),
74 _ => return None,
75 })
76}
77
78pub fn constant(name: &str) -> Option<Value> {
85 match name {
86 "Channel" | "TracingChannel" | "BoundedChannel" => {
87 Some(with_host(|h| h.alloc(JsObj::Builtin(name.into()))))
88 }
89 _ => None,
90 }
91}
92
93fn tracing_channel(name: &str) -> Value {
96 let subs: Vec<(String, Value)> = TRACING_SUBS
97 .iter()
98 .map(|s| {
99 (
100 (*s).to_string(),
101 get_or_create(&format!("tracing:{name}:{s}")),
102 )
103 })
104 .collect();
105 with_host(|h| {
106 let mut m = IndexMap::new();
107 m.insert("@@native".into(), h.new_str("TracingChannel"));
108 m.insert("@@name".into(), h.new_str(name));
109 for (k, v) in subs {
110 m.insert(k, v);
111 }
112 h.new_object(m)
113 })
114}
115
116pub fn tracing_instance_call(recv: &Value, method: &str, args: &[Value]) -> Result<Value, String> {
118 match method {
119 "subscribe" | "unsubscribe" => {
123 let handlers = args.first().cloned().unwrap_or(Value::Undef);
124 for sub in TRACING_SUBS {
125 let cb = with_host(|h| match h.get(&handlers) {
126 Some(JsObj::Object(p)) => p.get(*sub).cloned(),
127 _ => None,
128 });
129 let (Some(cb), Some(ch)) = (cb, sub_channel(recv, sub)) else {
130 continue;
131 };
132 if method == "subscribe" {
133 add_sub(&ch, cb);
134 } else {
135 remove_sub(&ch, &cb);
136 }
137 }
138 Ok(Value::Undef)
139 }
140 "traceSync" => {
144 let fn_v = args.first().cloned().unwrap_or(Value::Undef);
145 let ctx = args.get(1).cloned().unwrap_or(Value::Undef);
146 let this = args.get(2).cloned();
147 let call_args: Vec<Value> = args.iter().skip(3).cloned().collect();
148 if let Some(start) = sub_channel(recv, "start") {
149 publish(&start, ctx.clone())?;
150 }
151 match crate::host::invoke(&fn_v, call_args, this) {
152 Ok(v) => {
153 if let Some(end) = sub_channel(recv, "end") {
154 publish(&end, ctx)?;
155 }
156 Ok(v)
157 }
158 Err(e) => {
159 if let Some(err_ch) = sub_channel(recv, "error") {
160 let _ = publish(&err_ch, ctx.clone());
161 }
162 if let Some(end) = sub_channel(recv, "end") {
163 let _ = publish(&end, ctx);
164 }
165 Err(e)
166 }
167 }
168 }
169 _ => Err(crate::host::type_error(&format!(
170 "{method} is not a function"
171 ))),
172 }
173}
174
175fn sub_channel(recv: &Value, sub: &str) -> Option<Value> {
177 with_host(|h| match h.get(recv) {
178 Some(JsObj::Object(p)) => p.get(sub).cloned(),
179 _ => None,
180 })
181}
182
183pub fn instance_call(recv: &Value, method: &str, args: &[Value]) -> Result<Value, String> {
185 match method {
186 "subscribe" => {
187 add_sub(recv, args.first().cloned().unwrap_or(Value::Undef));
188 Ok(Value::Undef)
189 }
190 "unsubscribe" => Ok(Value::Bool(remove_sub(
191 recv,
192 &args.first().cloned().unwrap_or(Value::Undef),
193 ))),
194 "publish" => publish(recv, args.first().cloned().unwrap_or(Value::Undef)),
195 _ => Err(crate::host::type_error(&format!(
196 "{method} is not a function"
197 ))),
198 }
199}
200
201fn get_or_create(name: &str) -> Value {
203 if let Some(ch) = CHANNELS.with(|c| c.borrow().get(name).cloned()) {
204 return ch;
205 }
206 let ch = with_host(|h| {
207 let subs = h.new_array(Vec::new());
208 let mut m = IndexMap::new();
209 m.insert("@@native".into(), h.new_str("Channel"));
210 m.insert("@@name".into(), h.new_str(name));
211 m.insert("@@subs".into(), subs);
212 m.insert("name".into(), h.new_str(name));
213 m.insert("hasSubscribers".into(), Value::Bool(false));
214 h.new_object(m)
215 });
216 CHANNELS.with(|c| c.borrow_mut().insert(name.to_string(), ch.clone()));
217 ch
218}
219
220fn subs_array(ch: &Value) -> Option<Value> {
222 with_host(|h| match h.get(ch) {
223 Some(JsObj::Object(p)) => p.get("@@subs").cloned(),
224 _ => None,
225 })
226}
227
228fn sub_count(ch: &Value) -> usize {
230 match subs_array(ch) {
231 Some(arr) => with_host(|h| match h.get(&arr) {
232 Some(JsObj::Array(items)) => items.len(),
233 _ => 0,
234 }),
235 None => 0,
236 }
237}
238
239fn add_sub(ch: &Value, cb: Value) {
241 if let Some(arr) = subs_array(ch) {
242 with_host(|h| {
243 if let Some(JsObj::Array(items)) = h.get_mut(&arr) {
244 items.push(cb);
245 }
246 });
247 refresh_has(ch);
248 }
249}
250
251fn remove_sub(ch: &Value, cb: &Value) -> bool {
254 let removed = match subs_array(ch) {
255 Some(arr) => with_host(|h| {
256 if let Some(JsObj::Array(items)) = h.get_mut(&arr) {
257 if let Some(i) = items.iter().position(|x| same_ref(x, cb)) {
258 items.remove(i);
259 return true;
260 }
261 }
262 false
263 }),
264 None => false,
265 };
266 if removed {
267 refresh_has(ch);
268 }
269 removed
270}
271
272fn publish(ch: &Value, msg: Value) -> Result<Value, String> {
274 let subs: Vec<Value> = with_host(|h| match h.get(ch) {
277 Some(JsObj::Object(p)) => match p.get("@@subs").and_then(|a| h.get(a)) {
278 Some(JsObj::Array(items)) => items.clone(),
279 _ => Vec::new(),
280 },
281 _ => Vec::new(),
282 });
283 let name_val = with_host(|h| match h.get(ch) {
284 Some(JsObj::Object(p)) => p.get("@@name").cloned().unwrap_or(Value::Undef),
285 _ => Value::Undef,
286 });
287 for cb in subs {
288 crate::host::invoke(&cb, vec![msg.clone(), name_val.clone()], None)?;
289 }
290 Ok(Value::Undef)
291}
292
293fn refresh_has(ch: &Value) {
295 let has = sub_count(ch) > 0;
296 with_host(|h| {
297 if let Some(JsObj::Object(p)) = h.get_mut(ch) {
298 p.insert("hasSubscribers".into(), Value::Bool(has));
299 }
300 });
301}
302
303fn same_ref(a: &Value, b: &Value) -> bool {
306 matches!((a, b), (Value::Obj(x), Value::Obj(y)) if x == y)
307}