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
41#[wasm_bindgen(start)]
42pub fn _start() {
43 console_error_panic_hook::set_once();
44}
45
46use arora::runtime::{Runtime, StepOutcome};
64use arora_behavior::Behavior;
65use arora_bridge::{Bridge, FakeBridge};
66use arora_hal::{FakeHal, Hal};
67use arora_simple_data_store::SimpleDataStore;
68use arora_types::data::{DataStore, Key, StateChange, Subscription};
69use std::sync::Arc;
70
71pub struct BrowserRuntime {
80 runtime: Runtime,
81 store: Arc<dyn DataStore>,
82 changes: Subscription,
83}
84
85impl BrowserRuntime {
86 pub async fn start(
91 hal: Arc<dyn Hal>,
92 bridge: Arc<dyn Bridge>,
93 store: Arc<dyn DataStore>,
94 ) -> Result<BrowserRuntime, JsValue> {
95 let arora = arora::Arora::start()
96 .await
97 .map_err(|e| JsValue::from_str(&format!("arora start failed: {e:?}")))?;
98 let changes = store.subscribe();
99 let (runtime, io) = Runtime::with_io_in(arora, hal, bridge, store.clone());
100 wasm_bindgen_futures::spawn_local(io);
101 Ok(BrowserRuntime {
102 runtime,
103 store,
104 changes,
105 })
106 }
107
108 pub fn store(&self) -> &Arc<dyn DataStore> {
110 &self.store
111 }
112
113 pub fn queue_behavior(&mut self, behavior: Box<dyn Behavior>) {
115 self.runtime.queue_behavior(behavior);
116 }
117
118 pub fn queue_groot_xml(&mut self, xml: &str) -> Result<(), JsValue> {
120 self.runtime
121 .queue_groot_xml(xml)
122 .map_err(|e| JsValue::from_str(&format!("queue failed: {e}")))
123 }
124
125 pub fn step(&mut self) -> Result<bool, JsValue> {
128 match self.runtime.step() {
129 Ok(StepOutcome::Live) => Ok(true),
130 Ok(StepOutcome::Unregistered) => Ok(false),
131 Err(e) => Err(JsValue::from_str(&format!("step failed: {e}"))),
132 }
133 }
134
135 pub fn set_value(&self, path: &str, value_json: &str) -> Result<(), JsValue> {
138 let value: Value = serde_json::from_str(value_json)
139 .map_err(|e| JsValue::from_str(&format!("invalid value json for {path}: {e}")))?;
140 self.store
141 .write(StateChange::set(path, value))
142 .map_err(|e| JsValue::from_str(&format!("write {path} failed: {e}")))
143 }
144
145 pub fn write_values(&self, values_json: &str) -> Result<(), JsValue> {
149 let map: serde_json::Map<String, serde_json::Value> = serde_json::from_str(values_json)
150 .map_err(|e| JsValue::from_str(&format!("invalid values json: {e}")))?;
151 let mut change = StateChange::new();
152 for (path, raw) in map {
153 let value: Value = serde_json::from_value(raw)
154 .map_err(|e| JsValue::from_str(&format!("invalid value for {path}: {e}")))?;
155 change.set.insert(Key::new(path), Some(value));
156 }
157 self.store
158 .write(change)
159 .map_err(|e| JsValue::from_str(&format!("write failed: {e}")))
160 }
161
162 pub fn read_values(&self, paths: JsValue) -> Result<JsValue, JsValue> {
165 let paths: Vec<String> = serde_wasm_bindgen::from_value(paths)
166 .map_err(|e| JsValue::from_str(&format!("paths must be a string[]: {e}")))?;
167 let keys: Vec<Key> = paths.iter().map(Key::new).collect();
168 let values = self.store.read(&keys);
169 let mut out = serde_json::Map::with_capacity(paths.len());
170 for (path, value) in paths.into_iter().zip(values) {
171 out.insert(path, value_to_json(value)?);
172 }
173 serde_wasm_bindgen::to_value(&serde_json::Value::Object(out))
174 .map_err(|e| JsValue::from_str(&format!("serialize failed: {e}")))
175 }
176
177 pub fn snapshot(&self) -> Result<JsValue, JsValue> {
180 let state = self.store.snapshot();
181 let mut out = serde_json::Map::with_capacity(state.storage.len());
182 for (key, value) in state.storage {
183 out.insert(key.path, value_to_json(value)?);
184 }
185 serde_wasm_bindgen::to_value(&serde_json::Value::Object(out))
186 .map_err(|e| JsValue::from_str(&format!("serialize failed: {e}")))
187 }
188
189 pub fn drain_changes(&self) -> Result<JsValue, JsValue> {
195 let mut out = serde_json::Map::new();
196 while let Some(change) = self.changes.try_recv() {
197 for (key, value) in change.set {
198 out.insert(key.path, value_to_json(value)?);
199 }
200 for key in change.unset {
201 out.insert(key.path, serde_json::Value::Null);
202 }
203 }
204 serde_wasm_bindgen::to_value(&serde_json::Value::Object(out))
205 .map_err(|e| JsValue::from_str(&format!("serialize failed: {e}")))
206 }
207}
208
209fn value_to_json(value: Option<Value>) -> Result<serde_json::Value, JsValue> {
211 match value {
212 Some(v) => serde_json::to_value(v)
213 .map_err(|e| JsValue::from_str(&format!("serialize value failed: {e}"))),
214 None => Ok(serde_json::Value::Null),
215 }
216}
217
218#[wasm_bindgen]
229pub struct AroraRuntime {
230 inner: BrowserRuntime,
231}
232
233#[wasm_bindgen]
234impl AroraRuntime {
235 pub async fn start() -> Result<AroraRuntime, JsValue> {
239 let inner = BrowserRuntime::start(
240 Arc::new(FakeHal::new()),
241 Arc::new(FakeBridge::new()),
242 Arc::new(SimpleDataStore::new()),
243 )
244 .await?;
245 Ok(AroraRuntime { inner })
246 }
247
248 pub fn step(&mut self) -> Result<bool, JsValue> {
251 self.inner.step()
252 }
253
254 #[wasm_bindgen(js_name = queueGrootXml)]
256 pub fn queue_groot_xml(&mut self, xml: &str) -> Result<(), JsValue> {
257 self.inner.queue_groot_xml(xml)
258 }
259
260 #[wasm_bindgen(js_name = setValue)]
262 pub fn set_value(&self, path: &str, value_json: &str) -> Result<(), JsValue> {
263 self.inner.set_value(path, value_json)
264 }
265
266 #[wasm_bindgen(js_name = readValues)]
268 pub fn read_values(&self, paths: JsValue) -> Result<JsValue, JsValue> {
269 self.inner.read_values(paths)
270 }
271
272 pub fn snapshot(&self) -> Result<JsValue, JsValue> {
274 self.inner.snapshot()
275 }
276}
277
278#[wasm_bindgen]
280pub struct Engine {
281 inner: std::pin::Pin<Box<arora_engine::engine::Engine>>,
282 loader: SharedLoaderRc,
283 function_module: HashMap<Uuid, Uuid>,
284 module_headers: HashMap<Uuid, String>,
285}
286
287#[wasm_bindgen]
288impl Engine {
289 #[wasm_bindgen(constructor)]
290 pub fn new() -> Engine {
291 let executor = BrowserExecutor::new();
292 let loader = executor.shared();
293 let inner = EngineBuilder::new().add_executor(executor).build();
294 Engine {
295 inner,
296 loader,
297 function_module: HashMap::new(),
298 module_headers: HashMap::new(),
299 }
300 }
301
302 #[wasm_bindgen(js_name = loadModule)]
309 pub fn load_module(&mut self, header_json: &str, executable: &[u8]) -> Result<String, JsValue> {
310 self.load_module_inner(header_json, executable.to_vec().into_boxed_slice())
311 }
312
313 #[wasm_bindgen(js_name = prepareModule)]
317 pub fn prepare_module(&self, header_json: &str, executable: Vec<u8>) -> js_sys::Promise {
318 prepare_module_impl(self.loader.clone(), header_json, executable)
319 }
320
321 #[wasm_bindgen(js_name = loadPreparedModule)]
324 pub fn load_prepared_module(&mut self, header_json: &str) -> Result<String, JsValue> {
325 self.load_module_inner(header_json, Box::new([]))
326 }
327
328 fn load_module_inner(
329 &mut self,
330 header_json: &str,
331 executable: Box<[u8]>,
332 ) -> Result<String, JsValue> {
333 let header: Header = serde_json::from_str(header_json)
334 .map_err(|e| JsValue::from_str(&format!("invalid header json: {e}")))?;
335 let header_json_str = header_json.to_string();
336 let loaded = load_module_from_parts(&mut *self.inner, header, executable)
337 .map_err(|e| JsValue::from_str(&format!("load_module failed: {e}")))?;
338 for fn_id in &loaded.function_ids {
339 self.function_module.insert(*fn_id, loaded.id);
340 }
341 self.module_headers.insert(loaded.id, header_json_str);
342 Ok(loaded.id.to_string())
343 }
344
345 #[wasm_bindgen(js_name = listModules)]
347 pub fn list_modules(&self) -> String {
348 let headers: Vec<serde_json::Value> = self
349 .module_headers
350 .values()
351 .filter_map(|s| serde_json::from_str(s).ok())
352 .collect();
353 serde_json::to_string(&headers).unwrap_or_else(|_| "[]".to_string())
354 }
355
356 #[wasm_bindgen]
359 pub fn call(&mut self, call_json: &str) -> Result<String, JsValue> {
360 let call: Call = serde_json::from_str(call_json)
361 .map_err(|e| JsValue::from_str(&format!("invalid call json: {e}")))?;
362 let module_id = if let Some(m) = call.module_id {
363 m
364 } else {
365 *self
366 .function_module
367 .get(&call.id)
368 .ok_or_else(|| JsValue::from_str("no module known for function"))?
369 };
370 let result = self
371 .inner
372 .arora_call(&module_id, call)
373 .map_err(|e| JsValue::from_str(&format!("call failed: {e}")))?;
374 serde_json::to_string(&result)
375 .map_err(|e| JsValue::from_str(&format!("serialize failed: {e}")))
376 }
377}
378
379impl Default for Engine {
380 fn default() -> Self {
381 Self::new()
382 }
383}
384
385fn prepare_module_impl(
389 loader: SharedLoaderRc,
390 header_json: &str,
391 executable: Vec<u8>,
392) -> js_sys::Promise {
393 let header: Result<Header, _> = serde_json::from_str(header_json);
394 wasm_bindgen_futures::future_to_promise(async move {
395 let header = header.map_err(|e| JsValue::from_str(&format!("invalid header json: {e}")))?;
396 loader.prepare(header.id, executable).await?;
397 Ok(JsValue::UNDEFINED)
398 })
399}
400
401const TICK_ID_STRUCT_BYTES: [u8; 16] = [
411 0x6f, 0x49, 0xe6, 0x50, 0x84, 0xca, 0x48, 0x99, 0xa9, 0xbd, 0x1f, 0x3b, 0xf1, 0x7f, 0xab, 0x51,
412];
413const TICK_ID_CALLABLE_FIELD_BYTES: [u8; 16] = [
415 0x23, 0x79, 0x92, 0xd2, 0x17, 0xd1, 0x45, 0x9f, 0xbc, 0xa1, 0x71, 0x85, 0xfa, 0x6a, 0x69, 0xd7,
416];
417const STATUS_SUCCESS_BYTES: [u8; 16] = [
419 0x76, 0x6e, 0x9e, 0x9a, 0x44, 0x6d, 0x4e, 0x46, 0x83, 0xe6, 0x14, 0xb7, 0xca, 0x10, 0x11, 0x69,
420];
421const STATUS_FAILURE_BYTES: [u8; 16] = [
423 0x24, 0x68, 0xf4, 0x6c, 0xbb, 0x60, 0x42, 0x5c, 0x9a, 0x4d, 0x9a, 0xd3, 0x26, 0xcc, 0xc7, 0xe2,
424];
425const STATUS_ENUM_BYTES: [u8; 16] = [
427 0x32, 0x5a, 0x57, 0x67, 0xe3, 0x44, 0x45, 0x32, 0x86, 0x0e, 0x07, 0x49, 0xbc, 0xf2, 0xe4, 0x28,
428];
429const RET_PARAM_BYTES: [u8; 16] = [
431 0x5f, 0x72, 0x65, 0x74, 0x00, 0x00, 0x40, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
432];
433
434fn value_to_status(v: &Value) -> &'static str {
435 if let Value::Enumeration(e) = v {
436 if *e.variant_id.as_bytes() == STATUS_SUCCESS_BYTES {
437 return "success";
438 }
439 if *e.variant_id.as_bytes() == STATUS_FAILURE_BYTES {
440 return "failure";
441 }
442 }
443 "running"
444}
445
446#[derive(serde::Deserialize, Clone, Debug)]
449#[serde(rename_all = "snake_case")]
450enum BtExpression {
451 VariableId(Uuid),
452 Value(Value),
453}
454
455#[derive(serde::Deserialize, Debug)]
457struct BtNode {
458 id: Uuid,
459 function: Uuid,
460 #[serde(default)]
461 children: Option<Vec<Uuid>>,
462 #[serde(default)]
466 arguments: HashMap<Uuid, BtExpression>,
467}
468
469struct FnMeta {
471 module_id: Uuid,
472 children_param_id: Option<Uuid>,
475}
476
477struct NodeCallable {
479 node_id: Uuid,
480 fn_id: Uuid,
481 module_id: Uuid,
482 children_param_id: Option<Uuid>,
483 children_callable_ids: Vec<u64>,
484 arguments: HashMap<Uuid, BtExpression>,
485 variables: Rc<RefCell<HashMap<Uuid, Value>>>,
486 trace: Rc<RefCell<Vec<(Uuid, &'static str)>>>,
487}
488
489impl Callable for NodeCallable {
490 fn call(&self, caller: &mut dyn CallBridge) -> Result<Value, arora_engine::call::CallError> {
491 let tick_id_type = Uuid::from_bytes(TICK_ID_STRUCT_BYTES);
492 let callable_field = Uuid::from_bytes(TICK_ID_CALLABLE_FIELD_BYTES);
493 let ret_param_id = Uuid::from_bytes(RET_PARAM_BYTES);
494
495 let mut args = Vec::new();
496
497 if let Some(children_param_id) = self.children_param_id {
498 let elements: Vec<StructureWithoutId> = self
499 .children_callable_ids
500 .iter()
501 .map(|&id| StructureWithoutId {
502 fields: vec![StructureField {
503 id: callable_field,
504 value: Box::new(Value::U64(id)),
505 }],
506 })
507 .collect();
508 args.push(StructureField {
509 id: children_param_id,
510 value: Box::new(Value::ArrayStructure {
511 id: tick_id_type,
512 elements,
513 }),
514 });
515 }
516
517 for (¶m_id, expr) in &self.arguments {
518 if param_id == ret_param_id {
519 continue;
520 }
521 let value = match expr {
522 BtExpression::Value(v) => v.clone(),
523 BtExpression::VariableId(var_id) => self
524 .variables
525 .borrow()
526 .get(var_id)
527 .cloned()
528 .unwrap_or(Value::Unit),
529 };
530 args.push(StructureField {
531 id: param_id,
532 value: Box::new(value),
533 });
534 }
535
536 let mutable_param_vars: HashMap<Uuid, Uuid> = self
539 .arguments
540 .iter()
541 .filter_map(|(¶m_id, expr)| {
542 if param_id == ret_param_id {
543 return None;
544 }
545 if let BtExpression::VariableId(var_id) = expr {
546 Some((param_id, *var_id))
547 } else {
548 None
549 }
550 })
551 .collect();
552
553 let result = caller.arora_call(
554 &self.module_id,
555 Call {
556 module_id: None,
557 id: self.fn_id,
558 args,
559 },
560 )?;
561
562 for mutated in &result.mutated {
564 if let Some(&var_id) = mutable_param_vars.get(&mutated.id) {
565 self.variables
566 .borrow_mut()
567 .insert(var_id, *mutated.value.clone());
568 }
569 }
570
571 let has_ret = self.arguments.contains_key(&ret_param_id);
572 if has_ret {
573 if let Some(BtExpression::VariableId(var_id)) = self.arguments.get(&ret_param_id) {
574 self.variables
575 .borrow_mut()
576 .insert(*var_id, result.ret.clone());
577 }
578 }
579
580 let s = if has_ret {
581 "success"
582 } else {
583 value_to_status(&result.ret)
584 };
585 self.trace.borrow_mut().push((self.node_id, s));
586
587 if has_ret {
588 Ok(Value::Enumeration(Enumeration {
589 id: Uuid::from_bytes(STATUS_ENUM_BYTES),
590 variant_id: Uuid::from_bytes(STATUS_SUCCESS_BYTES),
591 value: Box::new(Value::Unit),
592 }))
593 } else {
594 Ok(result.ret)
595 }
596 }
597}
598
599fn register_node(
602 engine: &mut dyn CallBridge,
603 node_id: Uuid,
604 node_index: &HashMap<Uuid, BtNode>,
605 fn_meta: &HashMap<Uuid, FnMeta>,
606 trace: &Rc<RefCell<Vec<(Uuid, &'static str)>>>,
607 variables: &Rc<RefCell<HashMap<Uuid, Value>>>,
608) -> Result<u64, String> {
609 let node = node_index
610 .get(&node_id)
611 .ok_or_else(|| format!("node {node_id} not found in tree"))?;
612 let meta = fn_meta
613 .get(&node.function)
614 .ok_or_else(|| format!("function {} not registered in fn_meta", node.function))?;
615
616 let children_callable_ids = match &node.children {
617 None => vec![],
618 Some(ids) => ids
619 .iter()
620 .map(|&child_id| register_node(engine, child_id, node_index, fn_meta, trace, variables))
621 .collect::<Result<Vec<_>, _>>()?,
622 };
623
624 let callable: Rc<dyn Callable> = Rc::new(NodeCallable {
625 node_id,
626 fn_id: node.function,
627 module_id: meta.module_id,
628 children_param_id: meta.children_param_id,
629 children_callable_ids,
630 arguments: node.arguments.clone(),
631 variables: variables.clone(),
632 trace: trace.clone(),
633 });
634 let id = engine.arora_register_callable(callable);
635 Ok(id.id)
636}
637
638#[wasm_bindgen]
647pub struct BehaviorTreeRunner {
648 inner: Pin<Box<arora_engine::engine::Engine>>,
649 loader: SharedLoaderRc,
650 fn_meta: HashMap<Uuid, FnMeta>,
651 variables: Rc<RefCell<HashMap<Uuid, Value>>>,
652 module_headers: HashMap<Uuid, String>,
653}
654
655#[wasm_bindgen]
656impl BehaviorTreeRunner {
657 #[wasm_bindgen(constructor)]
658 pub fn new() -> BehaviorTreeRunner {
659 let executor = BrowserExecutor::new();
660 let loader = executor.shared();
661 let inner = EngineBuilder::new().add_executor(executor).build();
662 BehaviorTreeRunner {
663 inner,
664 loader,
665 fn_meta: HashMap::new(),
666 variables: Rc::new(RefCell::new(HashMap::new())),
667 module_headers: HashMap::new(),
668 }
669 }
670
671 #[wasm_bindgen(js_name = loadModule)]
679 pub fn load_module(&mut self, header_json: &str, executable: &[u8]) -> Result<String, JsValue> {
680 self.load_module_inner(header_json, executable.to_vec().into_boxed_slice())
681 }
682
683 #[wasm_bindgen(js_name = prepareModule)]
687 pub fn prepare_module(&self, header_json: &str, executable: Vec<u8>) -> js_sys::Promise {
688 prepare_module_impl(self.loader.clone(), header_json, executable)
689 }
690
691 #[wasm_bindgen(js_name = loadPreparedModule)]
694 pub fn load_prepared_module(&mut self, header_json: &str) -> Result<String, JsValue> {
695 self.load_module_inner(header_json, Box::new([]))
696 }
697
698 fn load_module_inner(
699 &mut self,
700 header_json: &str,
701 executable: Box<[u8]>,
702 ) -> Result<String, JsValue> {
703 let header: Header = serde_json::from_str(header_json)
704 .map_err(|e| JsValue::from_str(&format!("invalid header: {e}")))?;
705 let module_id = header.id;
706 let header_json_str = header_json.to_string();
707
708 for export in &header.exports {
709 let arora_types::module::low::ExportSymbol::Function(f) = export;
710 let children_param_id = f.parameters.first().and_then(|p| {
711 if let arora_types::module::low::TypeRef::Array { id } = &p.ty {
712 if id == &Uuid::from_bytes(TICK_ID_STRUCT_BYTES) {
713 Some(p.id)
714 } else {
715 None
716 }
717 } else {
718 None
719 }
720 });
721
722 self.fn_meta.insert(
723 f.id,
724 FnMeta {
725 module_id,
726 children_param_id,
727 },
728 );
729 }
730
731 let result = load_module_from_parts(&mut *self.inner, header, executable)
732 .map_err(|e| JsValue::from_str(&format!("load failed: {e}")))?;
733 self.module_headers.insert(result.id, header_json_str);
734 Ok(result.id.to_string())
735 }
736
737 #[wasm_bindgen(js_name = listModules)]
739 pub fn list_modules(&self) -> String {
740 let headers: Vec<serde_json::Value> = self
741 .module_headers
742 .values()
743 .filter_map(|s| serde_json::from_str(s).ok())
744 .collect();
745 serde_json::to_string(&headers).unwrap_or_else(|_| "[]".to_string())
746 }
747
748 #[wasm_bindgen(js_name = setVariable)]
751 pub fn set_variable(&mut self, var_id: &str, value_json: &str) -> Result<(), JsValue> {
752 let var_id: Uuid = var_id
753 .parse()
754 .map_err(|_| JsValue::from_str("bad var_id: not a valid UUID"))?;
755 let value: Value = serde_json::from_str(value_json)
756 .map_err(|e| JsValue::from_str(&format!("bad value JSON: {e}")))?;
757 self.variables.borrow_mut().insert(var_id, value);
758 Ok(())
759 }
760
761 pub fn tick(&mut self, nodes_json: &str) -> Result<String, JsValue> {
768 let nodes: Vec<BtNode> = serde_json::from_str(nodes_json)
769 .map_err(|e| JsValue::from_str(&format!("bad nodes JSON: {e}")))?;
770 if nodes.is_empty() {
771 return Err(JsValue::from_str("tree has no nodes"));
772 }
773
774 let root_id = nodes[0].id;
775 let node_index: HashMap<Uuid, BtNode> = nodes.into_iter().map(|n| (n.id, n)).collect();
776 let trace: Rc<RefCell<Vec<(Uuid, &'static str)>>> = Rc::new(RefCell::new(Vec::new()));
777
778 let fn_meta = &self.fn_meta;
779 let root_callable_id = register_node(
780 &mut *self.inner,
781 root_id,
782 &node_index,
783 fn_meta,
784 &trace,
785 &self.variables,
786 )
787 .map_err(|e| JsValue::from_str(&format!("setup error: {e}")))?;
788
789 let callable_id = CallableId {
790 id: root_callable_id,
791 };
792 let result = Callable::call(&callable_id, &mut *self.inner)
793 .map_err(|e| JsValue::from_str(&format!("tick error: {e}")))?;
794 let status = value_to_status(&result);
795
796 let trace_json: Vec<serde_json::Value> = trace
797 .borrow()
798 .iter()
799 .map(|(id, s)| serde_json::json!({ "nodeId": id.to_string(), "status": s }))
800 .collect();
801
802 let vars_json: serde_json::Map<String, serde_json::Value> = self
803 .variables
804 .borrow()
805 .iter()
806 .filter_map(|(id, v)| serde_json::to_value(v).ok().map(|jv| (id.to_string(), jv)))
807 .collect();
808
809 Ok(serde_json::json!({
810 "status": status,
811 "trace": trace_json,
812 "variables": vars_json,
813 })
814 .to_string())
815 }
816
817 pub fn run(&mut self, nodes_json: &str) -> Result<String, JsValue> {
827 let nodes: Vec<BtNode> = serde_json::from_str(nodes_json)
828 .map_err(|e| JsValue::from_str(&format!("bad nodes JSON: {e}")))?;
829 if nodes.is_empty() {
830 return Err(JsValue::from_str("tree has no nodes"));
831 }
832
833 let root_id = nodes[0].id;
834 let node_index: HashMap<Uuid, BtNode> = nodes.into_iter().map(|n| (n.id, n)).collect();
835 let trace: Rc<RefCell<Vec<(Uuid, &'static str)>>> = Rc::new(RefCell::new(Vec::new()));
836
837 let fn_meta = &self.fn_meta;
838 let root_callable_id = register_node(
839 &mut *self.inner,
840 root_id,
841 &node_index,
842 fn_meta,
843 &trace,
844 &self.variables,
845 )
846 .map_err(|e| JsValue::from_str(&format!("setup error: {e}")))?;
847
848 let callable_id = CallableId {
849 id: root_callable_id,
850 };
851
852 let mut last_status = "running";
853 for _ in 0..10_000 {
854 let result = Callable::call(&callable_id, &mut *self.inner)
855 .map_err(|e| JsValue::from_str(&format!("tick error: {e}")))?;
856 last_status = value_to_status(&result);
857 if last_status != "running" {
858 break;
859 }
860 }
861
862 let trace_json: Vec<serde_json::Value> = trace
863 .borrow()
864 .iter()
865 .map(|(id, s)| serde_json::json!({ "nodeId": id.to_string(), "status": s }))
866 .collect();
867
868 let out = serde_json::json!({
869 "status": last_status,
870 "trace": trace_json,
871 });
872 Ok(out.to_string())
873 }
874}
875
876impl Default for BehaviorTreeRunner {
877 fn default() -> Self {
878 Self::new()
879 }
880}
881
882#[wasm_bindgen]
891pub struct Registry {
892 entries: HashMap<Uuid, (String, Option<Uuid>)>,
893}
894
895#[derive(serde::Deserialize)]
896struct RecordEntry {
897 id: Uuid,
898 name: String,
899 parent: Option<Uuid>,
900}
901
902#[wasm_bindgen]
903impl Registry {
904 #[wasm_bindgen(constructor)]
905 pub fn new() -> Registry {
906 Registry {
907 entries: HashMap::new(),
908 }
909 }
910
911 #[wasm_bindgen(js_name = loadRecordsJson)]
913 pub fn load_records_json(&mut self, json: &str) -> Result<(), JsValue> {
914 let records: Vec<RecordEntry> = serde_json::from_str(json)
915 .map_err(|e| JsValue::from_str(&format!("invalid records JSON: {e}")))?;
916 for r in records {
917 self.entries.insert(r.id, (r.name, r.parent));
918 }
919 Ok(())
920 }
921
922 #[wasm_bindgen(js_name = resolveId)]
925 pub fn resolve_id(&self, id: &str) -> Option<String> {
926 let uuid: Uuid = id.parse().ok()?;
927 self.compute_path(&uuid)
928 }
929
930 fn compute_path(&self, id: &Uuid) -> Option<String> {
931 let (name, parent) = self.entries.get(id)?;
932 match parent {
933 None => Some(name.clone()),
934 Some(parent_id) if !self.entries.contains_key(parent_id) => Some(name.clone()),
935 Some(parent_id) => Some(format!("{}.{}", self.compute_path(parent_id)?, name)),
936 }
937 }
938}
939
940impl Default for Registry {
941 fn default() -> Self {
942 Self::new()
943 }
944}