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 serde_wasm_bindgen::to_value(&serde_json::Value::Object(out))
180 .map_err(|e| JsValue::from_str(&format!("serialize failed: {e}")))
181 }
182
183 pub fn snapshot(&self) -> Result<JsValue, JsValue> {
186 let state = self.store().snapshot();
187 let mut out = serde_json::Map::with_capacity(state.storage.len());
188 for (key, value) in state.storage {
189 out.insert(key.path, value_to_json(value)?);
190 }
191 serde_wasm_bindgen::to_value(&serde_json::Value::Object(out))
192 .map_err(|e| JsValue::from_str(&format!("serialize failed: {e}")))
193 }
194
195 pub fn drain_changes(&self) -> Result<JsValue, JsValue> {
201 let mut out = serde_json::Map::new();
202 while let Some(change) = self.changes.try_recv() {
203 for (key, value) in change.set {
204 out.insert(key.path, value_to_json(value)?);
205 }
206 for key in change.unset {
207 out.insert(key.path, serde_json::Value::Null);
208 }
209 }
210 serde_wasm_bindgen::to_value(&serde_json::Value::Object(out))
211 .map_err(|e| JsValue::from_str(&format!("serialize failed: {e}")))
212 }
213}
214
215fn value_to_json(value: Option<Value>) -> Result<serde_json::Value, JsValue> {
217 match value {
218 Some(v) => serde_json::to_value(v)
219 .map_err(|e| JsValue::from_str(&format!("serialize value failed: {e}"))),
220 None => Ok(serde_json::Value::Null),
221 }
222}
223
224#[wasm_bindgen]
235pub struct AroraRuntime {
236 inner: BrowserRuntime,
237}
238
239#[wasm_bindgen]
240impl AroraRuntime {
241 pub async fn start() -> Result<AroraRuntime, JsValue> {
245 let interpreter = BehaviorTreeInterpreter::new(Rc::new(HashMap::new()));
249 let inner = BrowserRuntime::start(
250 Box::new(FakeHal::new()),
251 Box::new(FakeBridge::new()),
252 Box::new(SimpleDataStore::new()),
253 Box::new(interpreter),
254 )
255 .await?;
256 Ok(AroraRuntime { inner })
257 }
258
259 pub fn step(&mut self, dt_ms: f64) -> Result<bool, JsValue> {
265 self.inner.step(Duration::from_secs_f64(dt_ms / 1_000.0))
266 }
267
268 #[wasm_bindgen(js_name = setValue)]
270 pub fn set_value(&self, path: &str, value_json: &str) -> Result<(), JsValue> {
271 self.inner.set_value(path, value_json)
272 }
273
274 #[wasm_bindgen(js_name = readValues)]
276 pub fn read_values(&self, paths: JsValue) -> Result<JsValue, JsValue> {
277 self.inner.read_values(paths)
278 }
279
280 pub fn snapshot(&self) -> Result<JsValue, JsValue> {
282 self.inner.snapshot()
283 }
284}
285
286#[wasm_bindgen]
288pub struct Engine {
289 inner: std::pin::Pin<Box<arora_engine::engine::Engine>>,
290 loader: SharedLoaderRc,
291 function_module: HashMap<Uuid, Uuid>,
292 module_headers: HashMap<Uuid, String>,
293}
294
295#[wasm_bindgen]
296impl Engine {
297 #[wasm_bindgen(constructor)]
298 pub fn new() -> Engine {
299 install_panic_hook();
300 let executor = BrowserExecutor::new();
301 let loader = executor.shared();
302 let inner = EngineBuilder::new().add_executor(executor).build();
303 Engine {
304 inner,
305 loader,
306 function_module: HashMap::new(),
307 module_headers: HashMap::new(),
308 }
309 }
310
311 #[wasm_bindgen(js_name = loadModule)]
318 pub fn load_module(&mut self, header_json: &str, executable: &[u8]) -> Result<String, JsValue> {
319 self.load_module_inner(header_json, executable.to_vec().into_boxed_slice())
320 }
321
322 #[wasm_bindgen(js_name = prepareModule)]
326 pub fn prepare_module(&self, header_json: &str, executable: Vec<u8>) -> js_sys::Promise {
327 prepare_module_impl(self.loader.clone(), header_json, executable)
328 }
329
330 #[wasm_bindgen(js_name = loadPreparedModule)]
333 pub fn load_prepared_module(&mut self, header_json: &str) -> Result<String, JsValue> {
334 self.load_module_inner(header_json, Box::new([]))
335 }
336
337 fn load_module_inner(
338 &mut self,
339 header_json: &str,
340 executable: Box<[u8]>,
341 ) -> Result<String, JsValue> {
342 let header: Header = serde_json::from_str(header_json)
343 .map_err(|e| JsValue::from_str(&format!("invalid header json: {e}")))?;
344 let header_json_str = header_json.to_string();
345 let loaded = load_module_from_parts(&mut *self.inner, header, executable)
346 .map_err(|e| JsValue::from_str(&format!("load_module failed: {e}")))?;
347 for fn_id in &loaded.function_ids {
348 self.function_module.insert(*fn_id, loaded.id);
349 }
350 self.module_headers.insert(loaded.id, header_json_str);
351 Ok(loaded.id.to_string())
352 }
353
354 #[wasm_bindgen(js_name = listModules)]
356 pub fn list_modules(&self) -> String {
357 let headers: Vec<serde_json::Value> = self
358 .module_headers
359 .values()
360 .filter_map(|s| serde_json::from_str(s).ok())
361 .collect();
362 serde_json::to_string(&headers).unwrap_or_else(|_| "[]".to_string())
363 }
364
365 #[wasm_bindgen]
368 pub fn call(&mut self, call_json: &str) -> Result<String, JsValue> {
369 let call: Call = serde_json::from_str(call_json)
370 .map_err(|e| JsValue::from_str(&format!("invalid call json: {e}")))?;
371 let module_id = if let Some(m) = call.module_id {
372 m
373 } else {
374 *self
375 .function_module
376 .get(&call.id)
377 .ok_or_else(|| JsValue::from_str("no module known for function"))?
378 };
379 let result = self
380 .inner
381 .arora_call(&module_id, call)
382 .map_err(|e| JsValue::from_str(&format!("call failed: {e}")))?;
383 serde_json::to_string(&result)
384 .map_err(|e| JsValue::from_str(&format!("serialize failed: {e}")))
385 }
386}
387
388impl Default for Engine {
389 fn default() -> Self {
390 Self::new()
391 }
392}
393
394fn prepare_module_impl(
398 loader: SharedLoaderRc,
399 header_json: &str,
400 executable: Vec<u8>,
401) -> js_sys::Promise {
402 let header: Result<Header, _> = serde_json::from_str(header_json);
403 wasm_bindgen_futures::future_to_promise(async move {
404 let header = header.map_err(|e| JsValue::from_str(&format!("invalid header json: {e}")))?;
405 loader.prepare(header.id, executable).await?;
406 Ok(JsValue::UNDEFINED)
407 })
408}
409
410const TICK_ID_STRUCT_BYTES: [u8; 16] = [
420 0x6f, 0x49, 0xe6, 0x50, 0x84, 0xca, 0x48, 0x99, 0xa9, 0xbd, 0x1f, 0x3b, 0xf1, 0x7f, 0xab, 0x51,
421];
422const TICK_ID_CALLABLE_FIELD_BYTES: [u8; 16] = [
424 0x23, 0x79, 0x92, 0xd2, 0x17, 0xd1, 0x45, 0x9f, 0xbc, 0xa1, 0x71, 0x85, 0xfa, 0x6a, 0x69, 0xd7,
425];
426const STATUS_SUCCESS_BYTES: [u8; 16] = [
428 0x76, 0x6e, 0x9e, 0x9a, 0x44, 0x6d, 0x4e, 0x46, 0x83, 0xe6, 0x14, 0xb7, 0xca, 0x10, 0x11, 0x69,
429];
430const STATUS_FAILURE_BYTES: [u8; 16] = [
432 0x24, 0x68, 0xf4, 0x6c, 0xbb, 0x60, 0x42, 0x5c, 0x9a, 0x4d, 0x9a, 0xd3, 0x26, 0xcc, 0xc7, 0xe2,
433];
434const STATUS_ENUM_BYTES: [u8; 16] = [
436 0x32, 0x5a, 0x57, 0x67, 0xe3, 0x44, 0x45, 0x32, 0x86, 0x0e, 0x07, 0x49, 0xbc, 0xf2, 0xe4, 0x28,
437];
438const RET_PARAM_BYTES: [u8; 16] = [
440 0x5f, 0x72, 0x65, 0x74, 0x00, 0x00, 0x40, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
441];
442
443fn value_to_status(v: &Value) -> &'static str {
444 if let Value::Enumeration(e) = v {
445 if *e.variant_id.as_bytes() == STATUS_SUCCESS_BYTES {
446 return "success";
447 }
448 if *e.variant_id.as_bytes() == STATUS_FAILURE_BYTES {
449 return "failure";
450 }
451 }
452 "running"
453}
454
455#[derive(serde::Deserialize, Clone, Debug)]
458#[serde(rename_all = "snake_case")]
459enum BtExpression {
460 VariableId(Uuid),
461 Value(Value),
462}
463
464#[derive(serde::Deserialize, Debug)]
466struct BtNode {
467 id: Uuid,
468 function: Uuid,
469 #[serde(default)]
470 children: Option<Vec<Uuid>>,
471 #[serde(default)]
475 arguments: HashMap<Uuid, BtExpression>,
476}
477
478struct FnMeta {
480 module_id: Uuid,
481 children_param_id: Option<Uuid>,
484}
485
486struct NodeCallable {
488 node_id: Uuid,
489 fn_id: Uuid,
490 module_id: Uuid,
491 children_param_id: Option<Uuid>,
492 children_callable_ids: Vec<u64>,
493 arguments: HashMap<Uuid, BtExpression>,
494 variables: Rc<RefCell<HashMap<Uuid, Value>>>,
495 trace: Rc<RefCell<Vec<(Uuid, &'static str)>>>,
496}
497
498impl Callable for NodeCallable {
499 fn call(&self, caller: &mut dyn CallBridge) -> Result<Value, arora_engine::call::CallError> {
500 let tick_id_type = Uuid::from_bytes(TICK_ID_STRUCT_BYTES);
501 let callable_field = Uuid::from_bytes(TICK_ID_CALLABLE_FIELD_BYTES);
502 let ret_param_id = Uuid::from_bytes(RET_PARAM_BYTES);
503
504 let mut args = Vec::new();
505
506 if let Some(children_param_id) = self.children_param_id {
507 let elements: Vec<StructureWithoutId> = self
508 .children_callable_ids
509 .iter()
510 .map(|&id| StructureWithoutId {
511 fields: vec![StructureField {
512 id: callable_field,
513 value: Box::new(Value::U64(id)),
514 }],
515 })
516 .collect();
517 args.push(StructureField {
518 id: children_param_id,
519 value: Box::new(Value::ArrayStructure {
520 id: tick_id_type,
521 elements,
522 }),
523 });
524 }
525
526 for (¶m_id, expr) in &self.arguments {
527 if param_id == ret_param_id {
528 continue;
529 }
530 let value = match expr {
531 BtExpression::Value(v) => v.clone(),
532 BtExpression::VariableId(var_id) => self
533 .variables
534 .borrow()
535 .get(var_id)
536 .cloned()
537 .unwrap_or(Value::Unit),
538 };
539 args.push(StructureField {
540 id: param_id,
541 value: Box::new(value),
542 });
543 }
544
545 let mutable_param_vars: HashMap<Uuid, Uuid> = self
548 .arguments
549 .iter()
550 .filter_map(|(¶m_id, expr)| {
551 if param_id == ret_param_id {
552 return None;
553 }
554 if let BtExpression::VariableId(var_id) = expr {
555 Some((param_id, *var_id))
556 } else {
557 None
558 }
559 })
560 .collect();
561
562 let result = caller.arora_call(
563 &self.module_id,
564 Call {
565 module_id: None,
566 id: self.fn_id,
567 args,
568 },
569 )?;
570
571 for mutated in &result.mutated {
573 if let Some(&var_id) = mutable_param_vars.get(&mutated.id) {
574 self.variables
575 .borrow_mut()
576 .insert(var_id, *mutated.value.clone());
577 }
578 }
579
580 let has_ret = self.arguments.contains_key(&ret_param_id);
581 if has_ret {
582 if let Some(BtExpression::VariableId(var_id)) = self.arguments.get(&ret_param_id) {
583 self.variables
584 .borrow_mut()
585 .insert(*var_id, result.ret.clone());
586 }
587 }
588
589 let s = if has_ret {
590 "success"
591 } else {
592 value_to_status(&result.ret)
593 };
594 self.trace.borrow_mut().push((self.node_id, s));
595
596 if has_ret {
597 Ok(Value::Enumeration(Enumeration {
598 id: Uuid::from_bytes(STATUS_ENUM_BYTES),
599 variant_id: Uuid::from_bytes(STATUS_SUCCESS_BYTES),
600 value: Box::new(Value::Unit),
601 }))
602 } else {
603 Ok(result.ret)
604 }
605 }
606}
607
608fn register_node(
611 engine: &mut dyn CallBridge,
612 node_id: Uuid,
613 node_index: &HashMap<Uuid, BtNode>,
614 fn_meta: &HashMap<Uuid, FnMeta>,
615 trace: &Rc<RefCell<Vec<(Uuid, &'static str)>>>,
616 variables: &Rc<RefCell<HashMap<Uuid, Value>>>,
617) -> Result<u64, String> {
618 let node = node_index
619 .get(&node_id)
620 .ok_or_else(|| format!("node {node_id} not found in tree"))?;
621 let meta = fn_meta
622 .get(&node.function)
623 .ok_or_else(|| format!("function {} not registered in fn_meta", node.function))?;
624
625 let children_callable_ids = match &node.children {
626 None => vec![],
627 Some(ids) => ids
628 .iter()
629 .map(|&child_id| register_node(engine, child_id, node_index, fn_meta, trace, variables))
630 .collect::<Result<Vec<_>, _>>()?,
631 };
632
633 let callable: Rc<dyn Callable> = Rc::new(NodeCallable {
634 node_id,
635 fn_id: node.function,
636 module_id: meta.module_id,
637 children_param_id: meta.children_param_id,
638 children_callable_ids,
639 arguments: node.arguments.clone(),
640 variables: variables.clone(),
641 trace: trace.clone(),
642 });
643 let id = engine.arora_register_callable(callable);
644 Ok(id.id)
645}
646
647#[wasm_bindgen]
656pub struct BehaviorTreeRunner {
657 inner: Pin<Box<arora_engine::engine::Engine>>,
658 loader: SharedLoaderRc,
659 fn_meta: HashMap<Uuid, FnMeta>,
660 variables: Rc<RefCell<HashMap<Uuid, Value>>>,
661 module_headers: HashMap<Uuid, String>,
662}
663
664#[wasm_bindgen]
665impl BehaviorTreeRunner {
666 #[wasm_bindgen(constructor)]
667 pub fn new() -> BehaviorTreeRunner {
668 install_panic_hook();
669 let executor = BrowserExecutor::new();
670 let loader = executor.shared();
671 let inner = EngineBuilder::new().add_executor(executor).build();
672 BehaviorTreeRunner {
673 inner,
674 loader,
675 fn_meta: HashMap::new(),
676 variables: Rc::new(RefCell::new(HashMap::new())),
677 module_headers: HashMap::new(),
678 }
679 }
680
681 #[wasm_bindgen(js_name = loadModule)]
689 pub fn load_module(&mut self, header_json: &str, executable: &[u8]) -> Result<String, JsValue> {
690 self.load_module_inner(header_json, executable.to_vec().into_boxed_slice())
691 }
692
693 #[wasm_bindgen(js_name = prepareModule)]
697 pub fn prepare_module(&self, header_json: &str, executable: Vec<u8>) -> js_sys::Promise {
698 prepare_module_impl(self.loader.clone(), header_json, executable)
699 }
700
701 #[wasm_bindgen(js_name = loadPreparedModule)]
704 pub fn load_prepared_module(&mut self, header_json: &str) -> Result<String, JsValue> {
705 self.load_module_inner(header_json, Box::new([]))
706 }
707
708 fn load_module_inner(
709 &mut self,
710 header_json: &str,
711 executable: Box<[u8]>,
712 ) -> Result<String, JsValue> {
713 let header: Header = serde_json::from_str(header_json)
714 .map_err(|e| JsValue::from_str(&format!("invalid header: {e}")))?;
715 let module_id = header.id;
716 let header_json_str = header_json.to_string();
717
718 for export in &header.exports {
719 let arora_types::module::low::ExportSymbol::Function(f) = export;
720 let children_param_id = f.parameters.first().and_then(|p| {
721 if let arora_types::module::low::TypeRef::Array { id } = &p.ty {
722 if id == &Uuid::from_bytes(TICK_ID_STRUCT_BYTES) {
723 Some(p.id)
724 } else {
725 None
726 }
727 } else {
728 None
729 }
730 });
731
732 self.fn_meta.insert(
733 f.id,
734 FnMeta {
735 module_id,
736 children_param_id,
737 },
738 );
739 }
740
741 let result = load_module_from_parts(&mut *self.inner, header, executable)
742 .map_err(|e| JsValue::from_str(&format!("load failed: {e}")))?;
743 self.module_headers.insert(result.id, header_json_str);
744 Ok(result.id.to_string())
745 }
746
747 #[wasm_bindgen(js_name = listModules)]
749 pub fn list_modules(&self) -> String {
750 let headers: Vec<serde_json::Value> = self
751 .module_headers
752 .values()
753 .filter_map(|s| serde_json::from_str(s).ok())
754 .collect();
755 serde_json::to_string(&headers).unwrap_or_else(|_| "[]".to_string())
756 }
757
758 #[wasm_bindgen(js_name = setVariable)]
761 pub fn set_variable(&mut self, var_id: &str, value_json: &str) -> Result<(), JsValue> {
762 let var_id: Uuid = var_id
763 .parse()
764 .map_err(|_| JsValue::from_str("bad var_id: not a valid UUID"))?;
765 let value: Value = serde_json::from_str(value_json)
766 .map_err(|e| JsValue::from_str(&format!("bad value JSON: {e}")))?;
767 self.variables.borrow_mut().insert(var_id, value);
768 Ok(())
769 }
770
771 pub fn tick(&mut self, nodes_json: &str) -> Result<String, JsValue> {
778 let nodes: Vec<BtNode> = serde_json::from_str(nodes_json)
779 .map_err(|e| JsValue::from_str(&format!("bad nodes JSON: {e}")))?;
780 if nodes.is_empty() {
781 return Err(JsValue::from_str("tree has no nodes"));
782 }
783
784 let root_id = nodes[0].id;
785 let node_index: HashMap<Uuid, BtNode> = nodes.into_iter().map(|n| (n.id, n)).collect();
786 let trace: Rc<RefCell<Vec<(Uuid, &'static str)>>> = Rc::new(RefCell::new(Vec::new()));
787
788 let fn_meta = &self.fn_meta;
789 let root_callable_id = register_node(
790 &mut *self.inner,
791 root_id,
792 &node_index,
793 fn_meta,
794 &trace,
795 &self.variables,
796 )
797 .map_err(|e| JsValue::from_str(&format!("setup error: {e}")))?;
798
799 let callable_id = CallableId {
800 id: root_callable_id,
801 };
802 let result = Callable::call(&callable_id, &mut *self.inner)
803 .map_err(|e| JsValue::from_str(&format!("tick error: {e}")))?;
804 let status = value_to_status(&result);
805
806 let trace_json: Vec<serde_json::Value> = trace
807 .borrow()
808 .iter()
809 .map(|(id, s)| serde_json::json!({ "nodeId": id.to_string(), "status": s }))
810 .collect();
811
812 let vars_json: serde_json::Map<String, serde_json::Value> = self
813 .variables
814 .borrow()
815 .iter()
816 .filter_map(|(id, v)| serde_json::to_value(v).ok().map(|jv| (id.to_string(), jv)))
817 .collect();
818
819 Ok(serde_json::json!({
820 "status": status,
821 "trace": trace_json,
822 "variables": vars_json,
823 })
824 .to_string())
825 }
826
827 pub fn run(&mut self, nodes_json: &str) -> Result<String, JsValue> {
837 let nodes: Vec<BtNode> = serde_json::from_str(nodes_json)
838 .map_err(|e| JsValue::from_str(&format!("bad nodes JSON: {e}")))?;
839 if nodes.is_empty() {
840 return Err(JsValue::from_str("tree has no nodes"));
841 }
842
843 let root_id = nodes[0].id;
844 let node_index: HashMap<Uuid, BtNode> = nodes.into_iter().map(|n| (n.id, n)).collect();
845 let trace: Rc<RefCell<Vec<(Uuid, &'static str)>>> = Rc::new(RefCell::new(Vec::new()));
846
847 let fn_meta = &self.fn_meta;
848 let root_callable_id = register_node(
849 &mut *self.inner,
850 root_id,
851 &node_index,
852 fn_meta,
853 &trace,
854 &self.variables,
855 )
856 .map_err(|e| JsValue::from_str(&format!("setup error: {e}")))?;
857
858 let callable_id = CallableId {
859 id: root_callable_id,
860 };
861
862 let mut last_status = "running";
863 for _ in 0..10_000 {
864 let result = Callable::call(&callable_id, &mut *self.inner)
865 .map_err(|e| JsValue::from_str(&format!("tick error: {e}")))?;
866 last_status = value_to_status(&result);
867 if last_status != "running" {
868 break;
869 }
870 }
871
872 let trace_json: Vec<serde_json::Value> = trace
873 .borrow()
874 .iter()
875 .map(|(id, s)| serde_json::json!({ "nodeId": id.to_string(), "status": s }))
876 .collect();
877
878 let out = serde_json::json!({
879 "status": last_status,
880 "trace": trace_json,
881 });
882 Ok(out.to_string())
883 }
884}
885
886impl Default for BehaviorTreeRunner {
887 fn default() -> Self {
888 Self::new()
889 }
890}
891
892#[wasm_bindgen]
901pub struct Registry {
902 entries: HashMap<Uuid, (String, Option<Uuid>)>,
903}
904
905#[derive(serde::Deserialize)]
906struct RecordEntry {
907 id: Uuid,
908 name: String,
909 parent: Option<Uuid>,
910}
911
912#[wasm_bindgen]
913impl Registry {
914 #[wasm_bindgen(constructor)]
915 pub fn new() -> Registry {
916 Registry {
917 entries: HashMap::new(),
918 }
919 }
920
921 #[wasm_bindgen(js_name = loadRecordsJson)]
923 pub fn load_records_json(&mut self, json: &str) -> Result<(), JsValue> {
924 let records: Vec<RecordEntry> = serde_json::from_str(json)
925 .map_err(|e| JsValue::from_str(&format!("invalid records JSON: {e}")))?;
926 for r in records {
927 self.entries.insert(r.id, (r.name, r.parent));
928 }
929 Ok(())
930 }
931
932 #[wasm_bindgen(js_name = resolveId)]
935 pub fn resolve_id(&self, id: &str) -> Option<String> {
936 let uuid: Uuid = id.parse().ok()?;
937 self.compute_path(&uuid)
938 }
939
940 fn compute_path(&self, id: &Uuid) -> Option<String> {
941 let (name, parent) = self.entries.get(id)?;
942 match parent {
943 None => Some(name.clone()),
944 Some(parent_id) if !self.entries.contains_key(parent_id) => Some(name.clone()),
945 Some(parent_id) => Some(format!("{}.{}", self.compute_path(parent_id)?, name)),
946 }
947 }
948}
949
950impl Default for Registry {
951 fn default() -> Self {
952 Self::new()
953 }
954}