1#![cfg(target_arch = "wasm32")]
26
27use std::cell::RefCell;
28use std::collections::HashMap;
29use std::pin::Pin;
30use std::rc::Rc;
31
32use arora_engine::{
33 call::{CallBridge, Callable, CallableId},
34 engine::EngineBuilder,
35 executor::browser::{BrowserExecutor, SharedLoaderRc},
36 load::load_module_from_parts,
37};
38use arora_types::module::low::Header;
39use arora_types::{
40 call::Call,
41 value::{Enumeration, StructureField, StructureWithoutId, Value},
42};
43use uuid::Uuid;
44use wasm_bindgen::prelude::*;
45
46fn install_panic_hook() {
53 console_error_panic_hook::set_once();
54}
55
56use arora::{Arora, AroraBuilder, Caller, LocalCaller};
61use arora_types::data::{DataStore, Key, StateChange, Subscription};
62use std::time::Duration;
63
64pub mod store_json {
70 use super::*;
71
72 pub fn set_value(store: &dyn DataStore, path: &str, value_json: &str) -> Result<(), JsValue> {
75 let value: Value = serde_json::from_str(value_json)
76 .map_err(|e| JsValue::from_str(&format!("invalid value json for {path}: {e}")))?;
77 store
78 .write(StateChange::set(path, value))
79 .map_err(|e| JsValue::from_str(&format!("write {path} failed: {e}")))
80 }
81
82 pub fn write_values(store: &dyn DataStore, values_json: &str) -> Result<(), JsValue> {
86 let map: serde_json::Map<String, serde_json::Value> = serde_json::from_str(values_json)
87 .map_err(|e| JsValue::from_str(&format!("invalid values json: {e}")))?;
88 let mut change = StateChange::new();
89 for (path, raw) in map {
90 let value: Value = serde_json::from_value(raw)
91 .map_err(|e| JsValue::from_str(&format!("invalid value for {path}: {e}")))?;
92 change.set.insert(Key::new(path), Some(value));
93 }
94 store
95 .write(change)
96 .map_err(|e| JsValue::from_str(&format!("write failed: {e}")))
97 }
98
99 pub fn read_values(store: &dyn DataStore, paths: JsValue) -> Result<JsValue, JsValue> {
102 let paths: Vec<String> = serde_wasm_bindgen::from_value(paths)
103 .map_err(|e| JsValue::from_str(&format!("paths must be a string[]: {e}")))?;
104 let keys: Vec<Key> = paths.iter().map(Key::new).collect();
105 let values = store.read(&keys);
106 let mut out = serde_json::Map::with_capacity(paths.len());
107 for (path, value) in paths.into_iter().zip(values) {
108 out.insert(path, value_to_json(value)?);
109 }
110 to_js_object(out)
111 }
112
113 pub fn snapshot(store: &dyn DataStore) -> Result<JsValue, JsValue> {
116 let state = store.snapshot();
117 let mut out = serde_json::Map::with_capacity(state.storage.len());
118 for (key, value) in state.storage {
119 out.insert(key.path, value_to_json(value)?);
120 }
121 to_js_object(out)
122 }
123
124 pub fn drain_changes(changes: &Subscription) -> Result<JsValue, JsValue> {
131 let mut out = serde_json::Map::new();
132 while let Some(change) = changes.try_recv() {
133 for (key, value) in change.set {
134 out.insert(key.path, value_to_json(value)?);
135 }
136 for key in change.unset {
137 out.insert(key.path, serde_json::Value::Null);
138 }
139 }
140 to_js_object(out)
141 }
142
143 fn to_js_object(out: serde_json::Map<String, serde_json::Value>) -> Result<JsValue, JsValue> {
148 use serde::Serialize;
149 serde_json::Value::Object(out)
150 .serialize(&serde_wasm_bindgen::Serializer::json_compatible())
151 .map_err(|e| JsValue::from_str(&format!("serialize failed: {e}")))
152 }
153
154 fn value_to_json(value: Option<Value>) -> Result<serde_json::Value, JsValue> {
156 match value {
157 Some(v) => serde_json::to_value(v)
158 .map_err(|e| JsValue::from_str(&format!("serialize value failed: {e}"))),
159 None => Ok(serde_json::Value::Null),
160 }
161 }
162}
163
164#[wasm_bindgen(js_name = AroraRuntime)]
180pub struct AroraWeb {
181 device: RefCell<Option<Arora>>,
184 caller: LocalCaller,
185 store: Box<dyn DataStore>,
186 changes: Subscription,
187}
188
189impl From<Arora> for AroraWeb {
190 fn from(arora: Arora) -> Self {
194 install_panic_hook();
195 let caller = arora.caller();
196 let store = arora.store().clone_box();
197 let changes = store.subscribe();
198 Self {
199 device: RefCell::new(Some(arora)),
200 caller,
201 store,
202 changes,
203 }
204 }
205}
206
207#[wasm_bindgen(js_class = "AroraRuntime")]
208impl AroraWeb {
209 #[wasm_bindgen(constructor)]
212 pub fn new() -> Result<AroraWeb, JsValue> {
213 Ok(AroraWeb::from(Arora::builder().build().map_err(|e| {
214 JsValue::from_str(&format!("arora build failed: {e:?}"))
215 })?))
216 }
217
218 pub fn step(&self, dt_ms: f64) -> Result<(), JsValue> {
224 let mut slot = self
225 .device
226 .try_borrow_mut()
227 .map_err(|_| JsValue::from_str("the device is mid-step; call between steps"))?;
228 let device = slot
229 .as_mut()
230 .ok_or_else(|| JsValue::from_str("run() drives the device; step() is unavailable"))?;
231 device
232 .step(Duration::from_secs_f64(dt_ms / 1_000.0))
233 .map_err(|e| JsValue::from_str(&format!("step failed: {e}")))
234 }
235
236 pub fn run(&self, period_ms: Option<f64>) -> js_sys::Promise {
246 let device = self.device.borrow_mut().take();
247 wasm_bindgen_futures::future_to_promise(async move {
248 let mut device =
249 device.ok_or_else(|| JsValue::from_str("the device is already running"))?;
250 let period = period_ms
251 .map(|ms| Duration::from_secs_f64(ms / 1_000.0))
252 .unwrap_or(Arora::DEFAULT_STEP_PERIOD);
253 device
254 .run(period)
255 .await
256 .map_err(|e| JsValue::from_str(&format!("run failed: {e}")))?;
257 Ok(JsValue::UNDEFINED)
258 })
259 }
260
261 pub fn call(&self, call_json: &str) -> js_sys::Promise {
267 let parsed: Result<Call, _> = serde_json::from_str(call_json)
268 .map_err(|e| JsValue::from_str(&format!("invalid call json: {e}")));
269 let caller = self.caller.clone();
270 wasm_bindgen_futures::future_to_promise(async move {
271 let result = caller
272 .call(parsed?)
273 .await
274 .map_err(|e| JsValue::from_str(&format!("call failed: {e}")))?;
275 let json = serde_json::to_string(&result)
276 .map_err(|e| JsValue::from_str(&format!("serialize failed: {e}")))?;
277 Ok(JsValue::from_str(&json))
278 })
279 }
280
281 #[wasm_bindgen(js_name = setValue)]
283 pub fn set_value(&self, path: &str, value_json: &str) -> Result<(), JsValue> {
284 store_json::set_value(&*self.store, path, value_json)
285 }
286
287 #[wasm_bindgen(js_name = writeValues)]
290 pub fn write_values(&self, values_json: &str) -> Result<(), JsValue> {
291 store_json::write_values(&*self.store, values_json)
292 }
293
294 #[wasm_bindgen(js_name = readValues)]
296 pub fn read_values(&self, paths: JsValue) -> Result<JsValue, JsValue> {
297 store_json::read_values(&*self.store, paths)
298 }
299
300 pub fn snapshot(&self) -> Result<JsValue, JsValue> {
302 store_json::snapshot(&*self.store)
303 }
304
305 #[wasm_bindgen(js_name = drainChanges)]
309 pub fn drain_changes(&self) -> Result<JsValue, JsValue> {
310 store_json::drain_changes(&self.changes)
311 }
312}
313
314#[wasm_bindgen(js_name = AroraRuntimeBuilder)]
321#[derive(Default)]
322pub struct AroraWebBuilder {
323 inner: AroraBuilder,
324}
325
326#[wasm_bindgen(js_class = "AroraRuntimeBuilder")]
327impl AroraWebBuilder {
328 #[wasm_bindgen(constructor)]
329 pub fn new() -> AroraWebBuilder {
330 AroraWebBuilder::default()
331 }
332
333 #[wasm_bindgen(js_name = withModule)]
338 pub fn with_module(&mut self, header_json: &str, executable: &[u8]) -> Result<(), JsValue> {
339 let header: Header = serde_json::from_str(header_json)
340 .map_err(|e| JsValue::from_str(&format!("invalid header json: {e}")))?;
341 let inner = std::mem::take(&mut self.inner);
342 self.inner = inner.with_module(header, executable.to_vec());
343 Ok(())
344 }
345
346 pub fn build(&mut self) -> Result<AroraWeb, JsValue> {
349 let arora = std::mem::take(&mut self.inner)
350 .build()
351 .map_err(|e| JsValue::from_str(&format!("arora build failed: {e:?}")))?;
352 Ok(AroraWeb::from(arora))
353 }
354}
355
356#[wasm_bindgen]
358pub struct Engine {
359 inner: std::pin::Pin<Box<arora_engine::engine::Engine>>,
360 loader: SharedLoaderRc,
361 function_module: HashMap<Uuid, Uuid>,
362 module_headers: HashMap<Uuid, String>,
363}
364
365#[wasm_bindgen]
366impl Engine {
367 #[wasm_bindgen(constructor)]
368 pub fn new() -> Engine {
369 install_panic_hook();
370 let executor = BrowserExecutor::new();
371 let loader = executor.shared();
372 let inner = EngineBuilder::new().add_executor(executor).build();
373 Engine {
374 inner,
375 loader,
376 function_module: HashMap::new(),
377 module_headers: HashMap::new(),
378 }
379 }
380
381 #[wasm_bindgen(js_name = loadModule)]
388 pub fn load_module(&mut self, header_json: &str, executable: &[u8]) -> Result<String, JsValue> {
389 self.load_module_inner(header_json, executable.to_vec().into_boxed_slice())
390 }
391
392 #[wasm_bindgen(js_name = prepareModule)]
396 pub fn prepare_module(&self, header_json: &str, executable: Vec<u8>) -> js_sys::Promise {
397 prepare_module_impl(self.loader.clone(), header_json, executable)
398 }
399
400 #[wasm_bindgen(js_name = loadPreparedModule)]
403 pub fn load_prepared_module(&mut self, header_json: &str) -> Result<String, JsValue> {
404 self.load_module_inner(header_json, Box::new([]))
405 }
406
407 fn load_module_inner(
408 &mut self,
409 header_json: &str,
410 executable: Box<[u8]>,
411 ) -> Result<String, JsValue> {
412 let header: Header = serde_json::from_str(header_json)
413 .map_err(|e| JsValue::from_str(&format!("invalid header json: {e}")))?;
414 let header_json_str = header_json.to_string();
415 let loaded = load_module_from_parts(&mut *self.inner, header, executable)
416 .map_err(|e| JsValue::from_str(&format!("load_module failed: {e}")))?;
417 for fn_id in &loaded.function_ids {
418 self.function_module.insert(*fn_id, loaded.id);
419 }
420 self.module_headers.insert(loaded.id, header_json_str);
421 Ok(loaded.id.to_string())
422 }
423
424 #[wasm_bindgen(js_name = listModules)]
426 pub fn list_modules(&self) -> String {
427 let headers: Vec<serde_json::Value> = self
428 .module_headers
429 .values()
430 .filter_map(|s| serde_json::from_str(s).ok())
431 .collect();
432 serde_json::to_string(&headers).unwrap_or_else(|_| "[]".to_string())
433 }
434
435 #[wasm_bindgen]
438 pub fn call(&mut self, call_json: &str) -> Result<String, JsValue> {
439 let mut call: Call = serde_json::from_str(call_json)
440 .map_err(|e| JsValue::from_str(&format!("invalid call json: {e}")))?;
441 if call.module_id.is_none() {
442 call.module_id = Some(
444 *self
445 .function_module
446 .get(&call.id)
447 .ok_or_else(|| JsValue::from_str("no module known for function"))?,
448 );
449 }
450 let result = self
451 .inner
452 .arora_call(call)
453 .map_err(|e| JsValue::from_str(&format!("call failed: {e}")))?;
454 serde_json::to_string(&result)
455 .map_err(|e| JsValue::from_str(&format!("serialize failed: {e}")))
456 }
457}
458
459impl Default for Engine {
460 fn default() -> Self {
461 Self::new()
462 }
463}
464
465fn prepare_module_impl(
469 loader: SharedLoaderRc,
470 header_json: &str,
471 executable: Vec<u8>,
472) -> js_sys::Promise {
473 let header: Result<Header, _> = serde_json::from_str(header_json);
474 wasm_bindgen_futures::future_to_promise(async move {
475 let header = header.map_err(|e| JsValue::from_str(&format!("invalid header json: {e}")))?;
476 loader.prepare(header.id, executable).await?;
477 Ok(JsValue::UNDEFINED)
478 })
479}
480
481const TICK_ID_STRUCT_BYTES: [u8; 16] = [
491 0x6f, 0x49, 0xe6, 0x50, 0x84, 0xca, 0x48, 0x99, 0xa9, 0xbd, 0x1f, 0x3b, 0xf1, 0x7f, 0xab, 0x51,
492];
493const TICK_ID_CALLABLE_FIELD_BYTES: [u8; 16] = [
495 0x23, 0x79, 0x92, 0xd2, 0x17, 0xd1, 0x45, 0x9f, 0xbc, 0xa1, 0x71, 0x85, 0xfa, 0x6a, 0x69, 0xd7,
496];
497const STATUS_SUCCESS_BYTES: [u8; 16] = [
499 0x76, 0x6e, 0x9e, 0x9a, 0x44, 0x6d, 0x4e, 0x46, 0x83, 0xe6, 0x14, 0xb7, 0xca, 0x10, 0x11, 0x69,
500];
501const STATUS_FAILURE_BYTES: [u8; 16] = [
503 0x24, 0x68, 0xf4, 0x6c, 0xbb, 0x60, 0x42, 0x5c, 0x9a, 0x4d, 0x9a, 0xd3, 0x26, 0xcc, 0xc7, 0xe2,
504];
505const STATUS_ENUM_BYTES: [u8; 16] = [
507 0x32, 0x5a, 0x57, 0x67, 0xe3, 0x44, 0x45, 0x32, 0x86, 0x0e, 0x07, 0x49, 0xbc, 0xf2, 0xe4, 0x28,
508];
509const RET_PARAM_BYTES: [u8; 16] = [
511 0x5f, 0x72, 0x65, 0x74, 0x00, 0x00, 0x40, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
512];
513
514fn value_to_status(v: &Value) -> &'static str {
515 if let Value::Enumeration(e) = v {
516 if *e.variant_id.as_bytes() == STATUS_SUCCESS_BYTES {
517 return "success";
518 }
519 if *e.variant_id.as_bytes() == STATUS_FAILURE_BYTES {
520 return "failure";
521 }
522 }
523 "running"
524}
525
526#[derive(serde::Deserialize, Clone, Debug)]
529#[serde(rename_all = "snake_case")]
530enum BtExpression {
531 VariableId(Uuid),
532 Value(Value),
533}
534
535#[derive(serde::Deserialize, Debug)]
537struct BtNode {
538 id: Uuid,
539 function: Uuid,
540 #[serde(default)]
541 children: Option<Vec<Uuid>>,
542 #[serde(default)]
546 arguments: HashMap<Uuid, BtExpression>,
547}
548
549struct FnMeta {
551 module_id: Uuid,
552 children_param_id: Option<Uuid>,
555}
556
557struct NodeCallable {
559 node_id: Uuid,
560 fn_id: Uuid,
561 module_id: Uuid,
562 children_param_id: Option<Uuid>,
563 children_callable_ids: Vec<u64>,
564 arguments: HashMap<Uuid, BtExpression>,
565 variables: Rc<RefCell<HashMap<Uuid, Value>>>,
566 trace: Rc<RefCell<Vec<(Uuid, &'static str)>>>,
567}
568
569impl Callable for NodeCallable {
570 fn call(&self, caller: &mut dyn CallBridge) -> Result<Value, arora_engine::call::CallError> {
571 let tick_id_type = Uuid::from_bytes(TICK_ID_STRUCT_BYTES);
572 let callable_field = Uuid::from_bytes(TICK_ID_CALLABLE_FIELD_BYTES);
573 let ret_param_id = Uuid::from_bytes(RET_PARAM_BYTES);
574
575 let mut args = Vec::new();
576
577 if let Some(children_param_id) = self.children_param_id {
578 let elements: Vec<StructureWithoutId> = self
579 .children_callable_ids
580 .iter()
581 .map(|&id| StructureWithoutId {
582 fields: vec![StructureField {
583 id: callable_field,
584 value: Box::new(Value::U64(id)),
585 }],
586 })
587 .collect();
588 args.push(StructureField {
589 id: children_param_id,
590 value: Box::new(Value::ArrayStructure {
591 id: tick_id_type,
592 elements,
593 }),
594 });
595 }
596
597 for (¶m_id, expr) in &self.arguments {
598 if param_id == ret_param_id {
599 continue;
600 }
601 let value = match expr {
602 BtExpression::Value(v) => v.clone(),
603 BtExpression::VariableId(var_id) => self
604 .variables
605 .borrow()
606 .get(var_id)
607 .cloned()
608 .unwrap_or(Value::Unit),
609 };
610 args.push(StructureField {
611 id: param_id,
612 value: Box::new(value),
613 });
614 }
615
616 let mutable_param_vars: HashMap<Uuid, Uuid> = self
619 .arguments
620 .iter()
621 .filter_map(|(¶m_id, expr)| {
622 if param_id == ret_param_id {
623 return None;
624 }
625 if let BtExpression::VariableId(var_id) = expr {
626 Some((param_id, *var_id))
627 } else {
628 None
629 }
630 })
631 .collect();
632
633 let result = caller.arora_call(Call {
634 module_id: Some(self.module_id),
635 id: self.fn_id,
636 args,
637 })?;
638
639 for mutated in &result.mutated {
641 if let Some(&var_id) = mutable_param_vars.get(&mutated.id) {
642 self.variables
643 .borrow_mut()
644 .insert(var_id, *mutated.value.clone());
645 }
646 }
647
648 let has_ret = self.arguments.contains_key(&ret_param_id);
649 if has_ret {
650 if let Some(BtExpression::VariableId(var_id)) = self.arguments.get(&ret_param_id) {
651 self.variables
652 .borrow_mut()
653 .insert(*var_id, result.ret.clone());
654 }
655 }
656
657 let s = if has_ret {
658 "success"
659 } else {
660 value_to_status(&result.ret)
661 };
662 self.trace.borrow_mut().push((self.node_id, s));
663
664 if has_ret {
665 Ok(Value::Enumeration(Enumeration {
666 id: Uuid::from_bytes(STATUS_ENUM_BYTES),
667 variant_id: Uuid::from_bytes(STATUS_SUCCESS_BYTES),
668 value: Box::new(Value::Unit),
669 }))
670 } else {
671 Ok(result.ret)
672 }
673 }
674}
675
676fn register_node(
679 engine: &mut dyn CallBridge,
680 node_id: Uuid,
681 node_index: &HashMap<Uuid, BtNode>,
682 fn_meta: &HashMap<Uuid, FnMeta>,
683 trace: &Rc<RefCell<Vec<(Uuid, &'static str)>>>,
684 variables: &Rc<RefCell<HashMap<Uuid, Value>>>,
685) -> Result<u64, String> {
686 let node = node_index
687 .get(&node_id)
688 .ok_or_else(|| format!("node {node_id} not found in tree"))?;
689 let meta = fn_meta
690 .get(&node.function)
691 .ok_or_else(|| format!("function {} not registered in fn_meta", node.function))?;
692
693 let children_callable_ids = match &node.children {
694 None => vec![],
695 Some(ids) => ids
696 .iter()
697 .map(|&child_id| register_node(engine, child_id, node_index, fn_meta, trace, variables))
698 .collect::<Result<Vec<_>, _>>()?,
699 };
700
701 let callable: Rc<dyn Callable> = Rc::new(NodeCallable {
702 node_id,
703 fn_id: node.function,
704 module_id: meta.module_id,
705 children_param_id: meta.children_param_id,
706 children_callable_ids,
707 arguments: node.arguments.clone(),
708 variables: variables.clone(),
709 trace: trace.clone(),
710 });
711 let id = engine.arora_register_callable(callable);
712 Ok(id.id)
713}
714
715#[wasm_bindgen]
724pub struct BehaviorTreeRunner {
725 inner: Pin<Box<arora_engine::engine::Engine>>,
726 loader: SharedLoaderRc,
727 fn_meta: HashMap<Uuid, FnMeta>,
728 variables: Rc<RefCell<HashMap<Uuid, Value>>>,
729 module_headers: HashMap<Uuid, String>,
730}
731
732#[wasm_bindgen]
733impl BehaviorTreeRunner {
734 #[wasm_bindgen(constructor)]
735 pub fn new() -> BehaviorTreeRunner {
736 install_panic_hook();
737 let executor = BrowserExecutor::new();
738 let loader = executor.shared();
739 let inner = EngineBuilder::new().add_executor(executor).build();
740 BehaviorTreeRunner {
741 inner,
742 loader,
743 fn_meta: HashMap::new(),
744 variables: Rc::new(RefCell::new(HashMap::new())),
745 module_headers: HashMap::new(),
746 }
747 }
748
749 #[wasm_bindgen(js_name = loadModule)]
757 pub fn load_module(&mut self, header_json: &str, executable: &[u8]) -> Result<String, JsValue> {
758 self.load_module_inner(header_json, executable.to_vec().into_boxed_slice())
759 }
760
761 #[wasm_bindgen(js_name = prepareModule)]
765 pub fn prepare_module(&self, header_json: &str, executable: Vec<u8>) -> js_sys::Promise {
766 prepare_module_impl(self.loader.clone(), header_json, executable)
767 }
768
769 #[wasm_bindgen(js_name = loadPreparedModule)]
772 pub fn load_prepared_module(&mut self, header_json: &str) -> Result<String, JsValue> {
773 self.load_module_inner(header_json, Box::new([]))
774 }
775
776 fn load_module_inner(
777 &mut self,
778 header_json: &str,
779 executable: Box<[u8]>,
780 ) -> Result<String, JsValue> {
781 let header: Header = serde_json::from_str(header_json)
782 .map_err(|e| JsValue::from_str(&format!("invalid header: {e}")))?;
783 let module_id = header.id;
784 let header_json_str = header_json.to_string();
785
786 for export in &header.exports {
787 let arora_types::module::low::ExportSymbol::Function(f) = export;
788 let children_param_id = f.parameters.first().and_then(|p| {
789 if let arora_types::module::low::TypeRef::Array { id } = &p.ty {
790 if id == &Uuid::from_bytes(TICK_ID_STRUCT_BYTES) {
791 Some(p.id)
792 } else {
793 None
794 }
795 } else {
796 None
797 }
798 });
799
800 self.fn_meta.insert(
801 f.id,
802 FnMeta {
803 module_id,
804 children_param_id,
805 },
806 );
807 }
808
809 let result = load_module_from_parts(&mut *self.inner, header, executable)
810 .map_err(|e| JsValue::from_str(&format!("load failed: {e}")))?;
811 self.module_headers.insert(result.id, header_json_str);
812 Ok(result.id.to_string())
813 }
814
815 #[wasm_bindgen(js_name = listModules)]
817 pub fn list_modules(&self) -> String {
818 let headers: Vec<serde_json::Value> = self
819 .module_headers
820 .values()
821 .filter_map(|s| serde_json::from_str(s).ok())
822 .collect();
823 serde_json::to_string(&headers).unwrap_or_else(|_| "[]".to_string())
824 }
825
826 #[wasm_bindgen(js_name = setVariable)]
829 pub fn set_variable(&mut self, var_id: &str, value_json: &str) -> Result<(), JsValue> {
830 let var_id: Uuid = var_id
831 .parse()
832 .map_err(|_| JsValue::from_str("bad var_id: not a valid UUID"))?;
833 let value: Value = serde_json::from_str(value_json)
834 .map_err(|e| JsValue::from_str(&format!("bad value JSON: {e}")))?;
835 self.variables.borrow_mut().insert(var_id, value);
836 Ok(())
837 }
838
839 pub fn tick(&mut self, nodes_json: &str) -> Result<String, JsValue> {
846 let nodes: Vec<BtNode> = serde_json::from_str(nodes_json)
847 .map_err(|e| JsValue::from_str(&format!("bad nodes JSON: {e}")))?;
848 if nodes.is_empty() {
849 return Err(JsValue::from_str("tree has no nodes"));
850 }
851
852 let root_id = nodes[0].id;
853 let node_index: HashMap<Uuid, BtNode> = nodes.into_iter().map(|n| (n.id, n)).collect();
854 let trace: Rc<RefCell<Vec<(Uuid, &'static str)>>> = Rc::new(RefCell::new(Vec::new()));
855
856 let fn_meta = &self.fn_meta;
857 let root_callable_id = register_node(
858 &mut *self.inner,
859 root_id,
860 &node_index,
861 fn_meta,
862 &trace,
863 &self.variables,
864 )
865 .map_err(|e| JsValue::from_str(&format!("setup error: {e}")))?;
866
867 let callable_id = CallableId {
868 id: root_callable_id,
869 };
870 let result = Callable::call(&callable_id, &mut *self.inner)
871 .map_err(|e| JsValue::from_str(&format!("tick error: {e}")))?;
872 let status = value_to_status(&result);
873
874 let trace_json: Vec<serde_json::Value> = trace
875 .borrow()
876 .iter()
877 .map(|(id, s)| serde_json::json!({ "nodeId": id.to_string(), "status": s }))
878 .collect();
879
880 let vars_json: serde_json::Map<String, serde_json::Value> = self
881 .variables
882 .borrow()
883 .iter()
884 .filter_map(|(id, v)| serde_json::to_value(v).ok().map(|jv| (id.to_string(), jv)))
885 .collect();
886
887 Ok(serde_json::json!({
888 "status": status,
889 "trace": trace_json,
890 "variables": vars_json,
891 })
892 .to_string())
893 }
894
895 pub fn run(&mut self, nodes_json: &str) -> Result<String, JsValue> {
905 let nodes: Vec<BtNode> = serde_json::from_str(nodes_json)
906 .map_err(|e| JsValue::from_str(&format!("bad nodes JSON: {e}")))?;
907 if nodes.is_empty() {
908 return Err(JsValue::from_str("tree has no nodes"));
909 }
910
911 let root_id = nodes[0].id;
912 let node_index: HashMap<Uuid, BtNode> = nodes.into_iter().map(|n| (n.id, n)).collect();
913 let trace: Rc<RefCell<Vec<(Uuid, &'static str)>>> = Rc::new(RefCell::new(Vec::new()));
914
915 let fn_meta = &self.fn_meta;
916 let root_callable_id = register_node(
917 &mut *self.inner,
918 root_id,
919 &node_index,
920 fn_meta,
921 &trace,
922 &self.variables,
923 )
924 .map_err(|e| JsValue::from_str(&format!("setup error: {e}")))?;
925
926 let callable_id = CallableId {
927 id: root_callable_id,
928 };
929
930 let mut last_status = "running";
931 for _ in 0..10_000 {
932 let result = Callable::call(&callable_id, &mut *self.inner)
933 .map_err(|e| JsValue::from_str(&format!("tick error: {e}")))?;
934 last_status = value_to_status(&result);
935 if last_status != "running" {
936 break;
937 }
938 }
939
940 let trace_json: Vec<serde_json::Value> = trace
941 .borrow()
942 .iter()
943 .map(|(id, s)| serde_json::json!({ "nodeId": id.to_string(), "status": s }))
944 .collect();
945
946 let out = serde_json::json!({
947 "status": last_status,
948 "trace": trace_json,
949 });
950 Ok(out.to_string())
951 }
952}
953
954impl Default for BehaviorTreeRunner {
955 fn default() -> Self {
956 Self::new()
957 }
958}
959
960#[wasm_bindgen]
969pub struct Registry {
970 entries: HashMap<Uuid, (String, Option<Uuid>)>,
971}
972
973#[derive(serde::Deserialize)]
974struct RecordEntry {
975 id: Uuid,
976 name: String,
977 parent: Option<Uuid>,
978}
979
980#[wasm_bindgen]
981impl Registry {
982 #[wasm_bindgen(constructor)]
983 pub fn new() -> Registry {
984 Registry {
985 entries: HashMap::new(),
986 }
987 }
988
989 #[wasm_bindgen(js_name = loadRecordsJson)]
991 pub fn load_records_json(&mut self, json: &str) -> Result<(), JsValue> {
992 let records: Vec<RecordEntry> = serde_json::from_str(json)
993 .map_err(|e| JsValue::from_str(&format!("invalid records JSON: {e}")))?;
994 for r in records {
995 self.entries.insert(r.id, (r.name, r.parent));
996 }
997 Ok(())
998 }
999
1000 #[wasm_bindgen(js_name = resolveId)]
1003 pub fn resolve_id(&self, id: &str) -> Option<String> {
1004 let uuid: Uuid = id.parse().ok()?;
1005 self.compute_path(&uuid)
1006 }
1007
1008 fn compute_path(&self, id: &Uuid) -> Option<String> {
1009 let (name, parent) = self.entries.get(id)?;
1010 match parent {
1011 None => Some(name.clone()),
1012 Some(parent_id) if !self.entries.contains_key(parent_id) => Some(name.clone()),
1013 Some(parent_id) => Some(format!("{}.{}", self.compute_path(parent_id)?, name)),
1014 }
1015 }
1016}
1017
1018impl Default for Registry {
1019 fn default() -> Self {
1020 Self::new()
1021 }
1022}