1#![cfg(target_arch = "wasm32")]
21
22use std::cell::RefCell;
23use std::collections::HashMap;
24use std::pin::Pin;
25use std::rc::Rc;
26
27use arora_engine::{
28 call::{CallBridge, Callable, CallableId},
29 engine::EngineBuilder,
30 executor::browser::{BrowserExecutor, SharedLoaderRc},
31 load::load_module_from_parts,
32};
33use arora_types::module::low::Header;
34use arora_types::{
35 call::Call,
36 value::{Enumeration, StructureField, StructureWithoutId, Value},
37};
38use uuid::Uuid;
39use wasm_bindgen::prelude::*;
40
41fn install_panic_hook() {
47 console_error_panic_hook::set_once();
48}
49
50use arora::{Arora, BehaviorTreeInterpreter, StepOutcome};
70use arora_behavior::BehaviorInterpreter;
71use arora_bridge::{Bridge, FakeBridge};
72use arora_hal::{FakeHal, Hal};
73use arora_simple_data_store::SimpleDataStore;
74use arora_types::data::{DataStore, Key, StateChange, Subscription};
75use std::time::Duration;
76
77pub struct BrowserRuntime {
86 arora: Arora,
87 changes: Subscription,
88}
89
90impl BrowserRuntime {
91 pub async fn start(
105 hal: Box<dyn Hal>,
106 bridge: Box<dyn Bridge>,
107 store: Box<dyn DataStore>,
108 behavior_interpreter: Box<dyn BehaviorInterpreter>,
109 ) -> Result<BrowserRuntime, JsValue> {
110 install_panic_hook();
111 let changes = store.subscribe();
112 let arora = Arora::builder()
113 .with_hal(hal)
114 .with_bridge(bridge)
115 .with_data_store(store)
116 .with_behavior_interpreter(behavior_interpreter)
117 .build()
118 .map_err(|e| JsValue::from_str(&format!("arora build failed: {e:?}")))?;
119 Ok(BrowserRuntime { arora, changes })
120 }
121
122 pub fn store(&self) -> &dyn DataStore {
124 self.arora.store()
125 }
126
127 pub fn step(&mut self, dt: Duration) -> Result<bool, JsValue> {
134 match self.arora.step(dt) {
135 Ok(StepOutcome::Live) => Ok(true),
136 Ok(StepOutcome::Unregistered) => Ok(false),
137 Err(e) => Err(JsValue::from_str(&format!("step failed: {e}"))),
138 }
139 }
140
141 pub fn set_value(&self, path: &str, value_json: &str) -> Result<(), JsValue> {
144 let value: Value = serde_json::from_str(value_json)
145 .map_err(|e| JsValue::from_str(&format!("invalid value json for {path}: {e}")))?;
146 self.store()
147 .write(StateChange::set(path, value))
148 .map_err(|e| JsValue::from_str(&format!("write {path} failed: {e}")))
149 }
150
151 pub fn write_values(&self, values_json: &str) -> Result<(), JsValue> {
155 let map: serde_json::Map<String, serde_json::Value> = serde_json::from_str(values_json)
156 .map_err(|e| JsValue::from_str(&format!("invalid values json: {e}")))?;
157 let mut change = StateChange::new();
158 for (path, raw) in map {
159 let value: Value = serde_json::from_value(raw)
160 .map_err(|e| JsValue::from_str(&format!("invalid value for {path}: {e}")))?;
161 change.set.insert(Key::new(path), Some(value));
162 }
163 self.store()
164 .write(change)
165 .map_err(|e| JsValue::from_str(&format!("write failed: {e}")))
166 }
167
168 pub fn read_values(&self, paths: JsValue) -> Result<JsValue, JsValue> {
171 let paths: Vec<String> = serde_wasm_bindgen::from_value(paths)
172 .map_err(|e| JsValue::from_str(&format!("paths must be a string[]: {e}")))?;
173 let keys: Vec<Key> = paths.iter().map(Key::new).collect();
174 let values = self.store().read(&keys);
175 let mut out = serde_json::Map::with_capacity(paths.len());
176 for (path, value) in paths.into_iter().zip(values) {
177 out.insert(path, value_to_json(value)?);
178 }
179 to_js_object(out)
180 }
181
182 pub fn snapshot(&self) -> Result<JsValue, JsValue> {
185 let state = self.store().snapshot();
186 let mut out = serde_json::Map::with_capacity(state.storage.len());
187 for (key, value) in state.storage {
188 out.insert(key.path, value_to_json(value)?);
189 }
190 to_js_object(out)
191 }
192
193 pub fn drain_changes(&self) -> Result<JsValue, JsValue> {
199 let mut out = serde_json::Map::new();
200 while let Some(change) = self.changes.try_recv() {
201 for (key, value) in change.set {
202 out.insert(key.path, value_to_json(value)?);
203 }
204 for key in change.unset {
205 out.insert(key.path, serde_json::Value::Null);
206 }
207 }
208 to_js_object(out)
209 }
210}
211
212fn to_js_object(out: serde_json::Map<String, serde_json::Value>) -> Result<JsValue, JsValue> {
216 use serde::Serialize;
217 serde_json::Value::Object(out)
218 .serialize(&serde_wasm_bindgen::Serializer::json_compatible())
219 .map_err(|e| JsValue::from_str(&format!("serialize failed: {e}")))
220}
221
222fn value_to_json(value: Option<Value>) -> Result<serde_json::Value, JsValue> {
224 match value {
225 Some(v) => serde_json::to_value(v)
226 .map_err(|e| JsValue::from_str(&format!("serialize value failed: {e}"))),
227 None => Ok(serde_json::Value::Null),
228 }
229}
230
231#[wasm_bindgen]
242pub struct AroraRuntime {
243 inner: BrowserRuntime,
244}
245
246#[wasm_bindgen]
247impl AroraRuntime {
248 pub async fn start() -> Result<AroraRuntime, JsValue> {
252 let interpreter = BehaviorTreeInterpreter::new(Rc::new(HashMap::new()));
256 let inner = BrowserRuntime::start(
257 Box::new(FakeHal::new()),
258 Box::new(FakeBridge::new()),
259 Box::new(SimpleDataStore::new()),
260 Box::new(interpreter),
261 )
262 .await?;
263 Ok(AroraRuntime { inner })
264 }
265
266 pub fn step(&mut self, dt_ms: f64) -> Result<bool, JsValue> {
272 self.inner.step(Duration::from_secs_f64(dt_ms / 1_000.0))
273 }
274
275 #[wasm_bindgen(js_name = setValue)]
277 pub fn set_value(&self, path: &str, value_json: &str) -> Result<(), JsValue> {
278 self.inner.set_value(path, value_json)
279 }
280
281 #[wasm_bindgen(js_name = readValues)]
283 pub fn read_values(&self, paths: JsValue) -> Result<JsValue, JsValue> {
284 self.inner.read_values(paths)
285 }
286
287 pub fn snapshot(&self) -> Result<JsValue, JsValue> {
289 self.inner.snapshot()
290 }
291}
292
293#[wasm_bindgen]
295pub struct Engine {
296 inner: std::pin::Pin<Box<arora_engine::engine::Engine>>,
297 loader: SharedLoaderRc,
298 function_module: HashMap<Uuid, Uuid>,
299 module_headers: HashMap<Uuid, String>,
300}
301
302#[wasm_bindgen]
303impl Engine {
304 #[wasm_bindgen(constructor)]
305 pub fn new() -> Engine {
306 install_panic_hook();
307 let executor = BrowserExecutor::new();
308 let loader = executor.shared();
309 let inner = EngineBuilder::new().add_executor(executor).build();
310 Engine {
311 inner,
312 loader,
313 function_module: HashMap::new(),
314 module_headers: HashMap::new(),
315 }
316 }
317
318 #[wasm_bindgen(js_name = loadModule)]
325 pub fn load_module(&mut self, header_json: &str, executable: &[u8]) -> Result<String, JsValue> {
326 self.load_module_inner(header_json, executable.to_vec().into_boxed_slice())
327 }
328
329 #[wasm_bindgen(js_name = prepareModule)]
333 pub fn prepare_module(&self, header_json: &str, executable: Vec<u8>) -> js_sys::Promise {
334 prepare_module_impl(self.loader.clone(), header_json, executable)
335 }
336
337 #[wasm_bindgen(js_name = loadPreparedModule)]
340 pub fn load_prepared_module(&mut self, header_json: &str) -> Result<String, JsValue> {
341 self.load_module_inner(header_json, Box::new([]))
342 }
343
344 fn load_module_inner(
345 &mut self,
346 header_json: &str,
347 executable: Box<[u8]>,
348 ) -> Result<String, JsValue> {
349 let header: Header = serde_json::from_str(header_json)
350 .map_err(|e| JsValue::from_str(&format!("invalid header json: {e}")))?;
351 let header_json_str = header_json.to_string();
352 let loaded = load_module_from_parts(&mut *self.inner, header, executable)
353 .map_err(|e| JsValue::from_str(&format!("load_module failed: {e}")))?;
354 for fn_id in &loaded.function_ids {
355 self.function_module.insert(*fn_id, loaded.id);
356 }
357 self.module_headers.insert(loaded.id, header_json_str);
358 Ok(loaded.id.to_string())
359 }
360
361 #[wasm_bindgen(js_name = listModules)]
363 pub fn list_modules(&self) -> String {
364 let headers: Vec<serde_json::Value> = self
365 .module_headers
366 .values()
367 .filter_map(|s| serde_json::from_str(s).ok())
368 .collect();
369 serde_json::to_string(&headers).unwrap_or_else(|_| "[]".to_string())
370 }
371
372 #[wasm_bindgen]
375 pub fn call(&mut self, call_json: &str) -> Result<String, JsValue> {
376 let call: Call = serde_json::from_str(call_json)
377 .map_err(|e| JsValue::from_str(&format!("invalid call json: {e}")))?;
378 let module_id = if let Some(m) = call.module_id {
379 m
380 } else {
381 *self
382 .function_module
383 .get(&call.id)
384 .ok_or_else(|| JsValue::from_str("no module known for function"))?
385 };
386 let result = self
387 .inner
388 .arora_call(&module_id, call)
389 .map_err(|e| JsValue::from_str(&format!("call failed: {e}")))?;
390 serde_json::to_string(&result)
391 .map_err(|e| JsValue::from_str(&format!("serialize failed: {e}")))
392 }
393}
394
395impl Default for Engine {
396 fn default() -> Self {
397 Self::new()
398 }
399}
400
401fn prepare_module_impl(
405 loader: SharedLoaderRc,
406 header_json: &str,
407 executable: Vec<u8>,
408) -> js_sys::Promise {
409 let header: Result<Header, _> = serde_json::from_str(header_json);
410 wasm_bindgen_futures::future_to_promise(async move {
411 let header = header.map_err(|e| JsValue::from_str(&format!("invalid header json: {e}")))?;
412 loader.prepare(header.id, executable).await?;
413 Ok(JsValue::UNDEFINED)
414 })
415}
416
417const TICK_ID_STRUCT_BYTES: [u8; 16] = [
427 0x6f, 0x49, 0xe6, 0x50, 0x84, 0xca, 0x48, 0x99, 0xa9, 0xbd, 0x1f, 0x3b, 0xf1, 0x7f, 0xab, 0x51,
428];
429const TICK_ID_CALLABLE_FIELD_BYTES: [u8; 16] = [
431 0x23, 0x79, 0x92, 0xd2, 0x17, 0xd1, 0x45, 0x9f, 0xbc, 0xa1, 0x71, 0x85, 0xfa, 0x6a, 0x69, 0xd7,
432];
433const STATUS_SUCCESS_BYTES: [u8; 16] = [
435 0x76, 0x6e, 0x9e, 0x9a, 0x44, 0x6d, 0x4e, 0x46, 0x83, 0xe6, 0x14, 0xb7, 0xca, 0x10, 0x11, 0x69,
436];
437const STATUS_FAILURE_BYTES: [u8; 16] = [
439 0x24, 0x68, 0xf4, 0x6c, 0xbb, 0x60, 0x42, 0x5c, 0x9a, 0x4d, 0x9a, 0xd3, 0x26, 0xcc, 0xc7, 0xe2,
440];
441const STATUS_ENUM_BYTES: [u8; 16] = [
443 0x32, 0x5a, 0x57, 0x67, 0xe3, 0x44, 0x45, 0x32, 0x86, 0x0e, 0x07, 0x49, 0xbc, 0xf2, 0xe4, 0x28,
444];
445const RET_PARAM_BYTES: [u8; 16] = [
447 0x5f, 0x72, 0x65, 0x74, 0x00, 0x00, 0x40, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
448];
449
450fn value_to_status(v: &Value) -> &'static str {
451 if let Value::Enumeration(e) = v {
452 if *e.variant_id.as_bytes() == STATUS_SUCCESS_BYTES {
453 return "success";
454 }
455 if *e.variant_id.as_bytes() == STATUS_FAILURE_BYTES {
456 return "failure";
457 }
458 }
459 "running"
460}
461
462#[derive(serde::Deserialize, Clone, Debug)]
465#[serde(rename_all = "snake_case")]
466enum BtExpression {
467 VariableId(Uuid),
468 Value(Value),
469}
470
471#[derive(serde::Deserialize, Debug)]
473struct BtNode {
474 id: Uuid,
475 function: Uuid,
476 #[serde(default)]
477 children: Option<Vec<Uuid>>,
478 #[serde(default)]
482 arguments: HashMap<Uuid, BtExpression>,
483}
484
485struct FnMeta {
487 module_id: Uuid,
488 children_param_id: Option<Uuid>,
491}
492
493struct NodeCallable {
495 node_id: Uuid,
496 fn_id: Uuid,
497 module_id: Uuid,
498 children_param_id: Option<Uuid>,
499 children_callable_ids: Vec<u64>,
500 arguments: HashMap<Uuid, BtExpression>,
501 variables: Rc<RefCell<HashMap<Uuid, Value>>>,
502 trace: Rc<RefCell<Vec<(Uuid, &'static str)>>>,
503}
504
505impl Callable for NodeCallable {
506 fn call(&self, caller: &mut dyn CallBridge) -> Result<Value, arora_engine::call::CallError> {
507 let tick_id_type = Uuid::from_bytes(TICK_ID_STRUCT_BYTES);
508 let callable_field = Uuid::from_bytes(TICK_ID_CALLABLE_FIELD_BYTES);
509 let ret_param_id = Uuid::from_bytes(RET_PARAM_BYTES);
510
511 let mut args = Vec::new();
512
513 if let Some(children_param_id) = self.children_param_id {
514 let elements: Vec<StructureWithoutId> = self
515 .children_callable_ids
516 .iter()
517 .map(|&id| StructureWithoutId {
518 fields: vec![StructureField {
519 id: callable_field,
520 value: Box::new(Value::U64(id)),
521 }],
522 })
523 .collect();
524 args.push(StructureField {
525 id: children_param_id,
526 value: Box::new(Value::ArrayStructure {
527 id: tick_id_type,
528 elements,
529 }),
530 });
531 }
532
533 for (¶m_id, expr) in &self.arguments {
534 if param_id == ret_param_id {
535 continue;
536 }
537 let value = match expr {
538 BtExpression::Value(v) => v.clone(),
539 BtExpression::VariableId(var_id) => self
540 .variables
541 .borrow()
542 .get(var_id)
543 .cloned()
544 .unwrap_or(Value::Unit),
545 };
546 args.push(StructureField {
547 id: param_id,
548 value: Box::new(value),
549 });
550 }
551
552 let mutable_param_vars: HashMap<Uuid, Uuid> = self
555 .arguments
556 .iter()
557 .filter_map(|(¶m_id, expr)| {
558 if param_id == ret_param_id {
559 return None;
560 }
561 if let BtExpression::VariableId(var_id) = expr {
562 Some((param_id, *var_id))
563 } else {
564 None
565 }
566 })
567 .collect();
568
569 let result = caller.arora_call(
570 &self.module_id,
571 Call {
572 module_id: None,
573 id: self.fn_id,
574 args,
575 },
576 )?;
577
578 for mutated in &result.mutated {
580 if let Some(&var_id) = mutable_param_vars.get(&mutated.id) {
581 self.variables
582 .borrow_mut()
583 .insert(var_id, *mutated.value.clone());
584 }
585 }
586
587 let has_ret = self.arguments.contains_key(&ret_param_id);
588 if has_ret {
589 if let Some(BtExpression::VariableId(var_id)) = self.arguments.get(&ret_param_id) {
590 self.variables
591 .borrow_mut()
592 .insert(*var_id, result.ret.clone());
593 }
594 }
595
596 let s = if has_ret {
597 "success"
598 } else {
599 value_to_status(&result.ret)
600 };
601 self.trace.borrow_mut().push((self.node_id, s));
602
603 if has_ret {
604 Ok(Value::Enumeration(Enumeration {
605 id: Uuid::from_bytes(STATUS_ENUM_BYTES),
606 variant_id: Uuid::from_bytes(STATUS_SUCCESS_BYTES),
607 value: Box::new(Value::Unit),
608 }))
609 } else {
610 Ok(result.ret)
611 }
612 }
613}
614
615fn register_node(
618 engine: &mut dyn CallBridge,
619 node_id: Uuid,
620 node_index: &HashMap<Uuid, BtNode>,
621 fn_meta: &HashMap<Uuid, FnMeta>,
622 trace: &Rc<RefCell<Vec<(Uuid, &'static str)>>>,
623 variables: &Rc<RefCell<HashMap<Uuid, Value>>>,
624) -> Result<u64, String> {
625 let node = node_index
626 .get(&node_id)
627 .ok_or_else(|| format!("node {node_id} not found in tree"))?;
628 let meta = fn_meta
629 .get(&node.function)
630 .ok_or_else(|| format!("function {} not registered in fn_meta", node.function))?;
631
632 let children_callable_ids = match &node.children {
633 None => vec![],
634 Some(ids) => ids
635 .iter()
636 .map(|&child_id| register_node(engine, child_id, node_index, fn_meta, trace, variables))
637 .collect::<Result<Vec<_>, _>>()?,
638 };
639
640 let callable: Rc<dyn Callable> = Rc::new(NodeCallable {
641 node_id,
642 fn_id: node.function,
643 module_id: meta.module_id,
644 children_param_id: meta.children_param_id,
645 children_callable_ids,
646 arguments: node.arguments.clone(),
647 variables: variables.clone(),
648 trace: trace.clone(),
649 });
650 let id = engine.arora_register_callable(callable);
651 Ok(id.id)
652}
653
654#[wasm_bindgen]
663pub struct BehaviorTreeRunner {
664 inner: Pin<Box<arora_engine::engine::Engine>>,
665 loader: SharedLoaderRc,
666 fn_meta: HashMap<Uuid, FnMeta>,
667 variables: Rc<RefCell<HashMap<Uuid, Value>>>,
668 module_headers: HashMap<Uuid, String>,
669}
670
671#[wasm_bindgen]
672impl BehaviorTreeRunner {
673 #[wasm_bindgen(constructor)]
674 pub fn new() -> BehaviorTreeRunner {
675 install_panic_hook();
676 let executor = BrowserExecutor::new();
677 let loader = executor.shared();
678 let inner = EngineBuilder::new().add_executor(executor).build();
679 BehaviorTreeRunner {
680 inner,
681 loader,
682 fn_meta: HashMap::new(),
683 variables: Rc::new(RefCell::new(HashMap::new())),
684 module_headers: HashMap::new(),
685 }
686 }
687
688 #[wasm_bindgen(js_name = loadModule)]
696 pub fn load_module(&mut self, header_json: &str, executable: &[u8]) -> Result<String, JsValue> {
697 self.load_module_inner(header_json, executable.to_vec().into_boxed_slice())
698 }
699
700 #[wasm_bindgen(js_name = prepareModule)]
704 pub fn prepare_module(&self, header_json: &str, executable: Vec<u8>) -> js_sys::Promise {
705 prepare_module_impl(self.loader.clone(), header_json, executable)
706 }
707
708 #[wasm_bindgen(js_name = loadPreparedModule)]
711 pub fn load_prepared_module(&mut self, header_json: &str) -> Result<String, JsValue> {
712 self.load_module_inner(header_json, Box::new([]))
713 }
714
715 fn load_module_inner(
716 &mut self,
717 header_json: &str,
718 executable: Box<[u8]>,
719 ) -> Result<String, JsValue> {
720 let header: Header = serde_json::from_str(header_json)
721 .map_err(|e| JsValue::from_str(&format!("invalid header: {e}")))?;
722 let module_id = header.id;
723 let header_json_str = header_json.to_string();
724
725 for export in &header.exports {
726 let arora_types::module::low::ExportSymbol::Function(f) = export;
727 let children_param_id = f.parameters.first().and_then(|p| {
728 if let arora_types::module::low::TypeRef::Array { id } = &p.ty {
729 if id == &Uuid::from_bytes(TICK_ID_STRUCT_BYTES) {
730 Some(p.id)
731 } else {
732 None
733 }
734 } else {
735 None
736 }
737 });
738
739 self.fn_meta.insert(
740 f.id,
741 FnMeta {
742 module_id,
743 children_param_id,
744 },
745 );
746 }
747
748 let result = load_module_from_parts(&mut *self.inner, header, executable)
749 .map_err(|e| JsValue::from_str(&format!("load failed: {e}")))?;
750 self.module_headers.insert(result.id, header_json_str);
751 Ok(result.id.to_string())
752 }
753
754 #[wasm_bindgen(js_name = listModules)]
756 pub fn list_modules(&self) -> String {
757 let headers: Vec<serde_json::Value> = self
758 .module_headers
759 .values()
760 .filter_map(|s| serde_json::from_str(s).ok())
761 .collect();
762 serde_json::to_string(&headers).unwrap_or_else(|_| "[]".to_string())
763 }
764
765 #[wasm_bindgen(js_name = setVariable)]
768 pub fn set_variable(&mut self, var_id: &str, value_json: &str) -> Result<(), JsValue> {
769 let var_id: Uuid = var_id
770 .parse()
771 .map_err(|_| JsValue::from_str("bad var_id: not a valid UUID"))?;
772 let value: Value = serde_json::from_str(value_json)
773 .map_err(|e| JsValue::from_str(&format!("bad value JSON: {e}")))?;
774 self.variables.borrow_mut().insert(var_id, value);
775 Ok(())
776 }
777
778 pub fn tick(&mut self, nodes_json: &str) -> Result<String, JsValue> {
785 let nodes: Vec<BtNode> = serde_json::from_str(nodes_json)
786 .map_err(|e| JsValue::from_str(&format!("bad nodes JSON: {e}")))?;
787 if nodes.is_empty() {
788 return Err(JsValue::from_str("tree has no nodes"));
789 }
790
791 let root_id = nodes[0].id;
792 let node_index: HashMap<Uuid, BtNode> = nodes.into_iter().map(|n| (n.id, n)).collect();
793 let trace: Rc<RefCell<Vec<(Uuid, &'static str)>>> = Rc::new(RefCell::new(Vec::new()));
794
795 let fn_meta = &self.fn_meta;
796 let root_callable_id = register_node(
797 &mut *self.inner,
798 root_id,
799 &node_index,
800 fn_meta,
801 &trace,
802 &self.variables,
803 )
804 .map_err(|e| JsValue::from_str(&format!("setup error: {e}")))?;
805
806 let callable_id = CallableId {
807 id: root_callable_id,
808 };
809 let result = Callable::call(&callable_id, &mut *self.inner)
810 .map_err(|e| JsValue::from_str(&format!("tick error: {e}")))?;
811 let status = value_to_status(&result);
812
813 let trace_json: Vec<serde_json::Value> = trace
814 .borrow()
815 .iter()
816 .map(|(id, s)| serde_json::json!({ "nodeId": id.to_string(), "status": s }))
817 .collect();
818
819 let vars_json: serde_json::Map<String, serde_json::Value> = self
820 .variables
821 .borrow()
822 .iter()
823 .filter_map(|(id, v)| serde_json::to_value(v).ok().map(|jv| (id.to_string(), jv)))
824 .collect();
825
826 Ok(serde_json::json!({
827 "status": status,
828 "trace": trace_json,
829 "variables": vars_json,
830 })
831 .to_string())
832 }
833
834 pub fn run(&mut self, nodes_json: &str) -> Result<String, JsValue> {
844 let nodes: Vec<BtNode> = serde_json::from_str(nodes_json)
845 .map_err(|e| JsValue::from_str(&format!("bad nodes JSON: {e}")))?;
846 if nodes.is_empty() {
847 return Err(JsValue::from_str("tree has no nodes"));
848 }
849
850 let root_id = nodes[0].id;
851 let node_index: HashMap<Uuid, BtNode> = nodes.into_iter().map(|n| (n.id, n)).collect();
852 let trace: Rc<RefCell<Vec<(Uuid, &'static str)>>> = Rc::new(RefCell::new(Vec::new()));
853
854 let fn_meta = &self.fn_meta;
855 let root_callable_id = register_node(
856 &mut *self.inner,
857 root_id,
858 &node_index,
859 fn_meta,
860 &trace,
861 &self.variables,
862 )
863 .map_err(|e| JsValue::from_str(&format!("setup error: {e}")))?;
864
865 let callable_id = CallableId {
866 id: root_callable_id,
867 };
868
869 let mut last_status = "running";
870 for _ in 0..10_000 {
871 let result = Callable::call(&callable_id, &mut *self.inner)
872 .map_err(|e| JsValue::from_str(&format!("tick error: {e}")))?;
873 last_status = value_to_status(&result);
874 if last_status != "running" {
875 break;
876 }
877 }
878
879 let trace_json: Vec<serde_json::Value> = trace
880 .borrow()
881 .iter()
882 .map(|(id, s)| serde_json::json!({ "nodeId": id.to_string(), "status": s }))
883 .collect();
884
885 let out = serde_json::json!({
886 "status": last_status,
887 "trace": trace_json,
888 });
889 Ok(out.to_string())
890 }
891}
892
893impl Default for BehaviorTreeRunner {
894 fn default() -> Self {
895 Self::new()
896 }
897}
898
899#[wasm_bindgen]
908pub struct Registry {
909 entries: HashMap<Uuid, (String, Option<Uuid>)>,
910}
911
912#[derive(serde::Deserialize)]
913struct RecordEntry {
914 id: Uuid,
915 name: String,
916 parent: Option<Uuid>,
917}
918
919#[wasm_bindgen]
920impl Registry {
921 #[wasm_bindgen(constructor)]
922 pub fn new() -> Registry {
923 Registry {
924 entries: HashMap::new(),
925 }
926 }
927
928 #[wasm_bindgen(js_name = loadRecordsJson)]
930 pub fn load_records_json(&mut self, json: &str) -> Result<(), JsValue> {
931 let records: Vec<RecordEntry> = serde_json::from_str(json)
932 .map_err(|e| JsValue::from_str(&format!("invalid records JSON: {e}")))?;
933 for r in records {
934 self.entries.insert(r.id, (r.name, r.parent));
935 }
936 Ok(())
937 }
938
939 #[wasm_bindgen(js_name = resolveId)]
942 pub fn resolve_id(&self, id: &str) -> Option<String> {
943 let uuid: Uuid = id.parse().ok()?;
944 self.compute_path(&uuid)
945 }
946
947 fn compute_path(&self, id: &Uuid) -> Option<String> {
948 let (name, parent) = self.entries.get(id)?;
949 match parent {
950 None => Some(name.clone()),
951 Some(parent_id) if !self.entries.contains_key(parent_id) => Some(name.clone()),
952 Some(parent_id) => Some(format!("{}.{}", self.compute_path(parent_id)?, name)),
953 }
954 }
955}
956
957impl Default for Registry {
958 fn default() -> Self {
959 Self::new()
960 }
961}