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)]
181pub struct AroraWeb {
182 device: Rc<RefCell<Option<Arora>>>,
186 stop: RefCell<Option<futures::channel::oneshot::Sender<()>>>,
189 caller: LocalCaller,
190 store: Box<dyn DataStore>,
191 changes: Subscription,
192 behavior_error: tokio::sync::watch::Receiver<Option<String>>,
195 behavior_error_changes: Rc<RefCell<Option<tokio::sync::watch::Receiver<Option<String>>>>>,
199}
200
201impl From<Arora> for AroraWeb {
202 fn from(arora: Arora) -> Self {
206 install_panic_hook();
207 let caller = arora.caller();
208 let store = arora.store().clone_box();
209 let changes = store.subscribe();
210 let behavior_error = arora.behavior_error();
211 let behavior_error_changes = Rc::new(RefCell::new(Some(arora.behavior_error())));
212 Self {
213 device: Rc::new(RefCell::new(Some(arora))),
214 stop: RefCell::new(None),
215 caller,
216 store,
217 changes,
218 behavior_error,
219 behavior_error_changes,
220 }
221 }
222}
223
224#[wasm_bindgen(js_class = "AroraRuntime")]
225impl AroraWeb {
226 #[wasm_bindgen(constructor)]
229 pub fn new() -> Result<AroraWeb, JsValue> {
230 Ok(AroraWeb::from(Arora::builder().build().map_err(|e| {
231 JsValue::from_str(&format!("arora build failed: {e:?}"))
232 })?))
233 }
234
235 pub fn step(&self, dt_ms: f64) -> Result<(), JsValue> {
241 let mut slot = self
242 .device
243 .try_borrow_mut()
244 .map_err(|_| JsValue::from_str("the device is mid-step; call between steps"))?;
245 let device = slot
246 .as_mut()
247 .ok_or_else(|| JsValue::from_str("run() drives the device; step() is unavailable"))?;
248 device
249 .step(Duration::from_secs_f64(dt_ms / 1_000.0))
250 .map_err(|e| JsValue::from_str(&format!("step failed: {e}")))
251 }
252
253 pub fn run(&self, period_ms: Option<f64>) -> js_sys::Promise {
263 let Some(mut device) = self.device.borrow_mut().take() else {
264 return js_sys::Promise::reject(&JsValue::from_str("the device is already running"));
265 };
266 let slot = self.device.clone();
267 let (stop_tx, stop_rx) = futures::channel::oneshot::channel::<()>();
268 *self.stop.borrow_mut() = Some(stop_tx);
269 wasm_bindgen_futures::future_to_promise(async move {
270 let period = period_ms
271 .map(|ms| Duration::from_secs_f64(ms / 1_000.0))
272 .unwrap_or(Arora::DEFAULT_STEP_PERIOD);
273 let outcome = {
274 use futures::FutureExt;
275 let run = device.run(period).fuse();
276 let mut stop_rx = stop_rx.fuse();
277 futures::pin_mut!(run);
278 futures::select_biased! {
279 result = run => Some(result),
280 _ = stop_rx => None,
281 }
282 };
283 slot.borrow_mut().replace(device);
287 match outcome {
288 Some(result) => {
290 result.map_err(|e| JsValue::from_str(&format!("run failed: {e}")))?;
291 Ok(JsValue::UNDEFINED)
292 }
293 None => Ok(JsValue::UNDEFINED),
295 }
296 })
297 }
298
299 pub fn stop(&self) {
303 if let Some(stop) = self.stop.borrow_mut().take() {
304 let _ = stop.send(());
305 }
306 }
307
308 #[wasm_bindgen(getter)]
310 pub fn running(&self) -> bool {
311 self.device.borrow().is_none()
312 }
313
314 #[wasm_bindgen(getter, js_name = behaviorError)]
319 pub fn behavior_error(&self) -> Option<String> {
320 self.behavior_error.borrow().clone()
321 }
322
323 #[wasm_bindgen(js_name = behaviorErrorChanged)]
329 pub fn behavior_error_changed(&self) -> js_sys::Promise {
330 let Some(mut errors) = self.behavior_error_changes.borrow_mut().take() else {
331 return js_sys::Promise::reject(&JsValue::from_str(
332 "a behaviorErrorChanged await is already pending",
333 ));
334 };
335 let slot = self.behavior_error_changes.clone();
336 wasm_bindgen_futures::future_to_promise(async move {
337 let outcome = errors.changed().await;
338 let value = errors.borrow_and_update().clone();
339 slot.borrow_mut().replace(errors);
340 outcome.map_err(|_| JsValue::from_str("the device is gone"))?;
341 Ok(match value {
342 Some(message) => JsValue::from_str(&message),
343 None => JsValue::UNDEFINED,
344 })
345 })
346 }
347
348 pub fn call(&self, call_json: &str) -> js_sys::Promise {
354 let parsed: Result<Call, _> = serde_json::from_str(call_json)
355 .map_err(|e| JsValue::from_str(&format!("invalid call json: {e}")));
356 let caller = self.caller.clone();
357 wasm_bindgen_futures::future_to_promise(async move {
358 let result = caller
359 .call(parsed?)
360 .await
361 .map_err(|e| JsValue::from_str(&format!("call failed: {e}")))?;
362 let json = serde_json::to_string(&result)
363 .map_err(|e| JsValue::from_str(&format!("serialize failed: {e}")))?;
364 Ok(JsValue::from_str(&json))
365 })
366 }
367
368 #[wasm_bindgen(js_name = setValue)]
370 pub fn set_value(&self, path: &str, value_json: &str) -> Result<(), JsValue> {
371 store_json::set_value(&*self.store, path, value_json)
372 }
373
374 #[wasm_bindgen(js_name = writeValues)]
377 pub fn write_values(&self, values_json: &str) -> Result<(), JsValue> {
378 store_json::write_values(&*self.store, values_json)
379 }
380
381 #[wasm_bindgen(js_name = readValues)]
383 pub fn read_values(&self, paths: JsValue) -> Result<JsValue, JsValue> {
384 store_json::read_values(&*self.store, paths)
385 }
386
387 pub fn snapshot(&self) -> Result<JsValue, JsValue> {
389 store_json::snapshot(&*self.store)
390 }
391
392 #[wasm_bindgen(js_name = drainChanges)]
396 pub fn drain_changes(&self) -> Result<JsValue, JsValue> {
397 store_json::drain_changes(&self.changes)
398 }
399}
400
401#[wasm_bindgen(js_name = AroraRuntimeBuilder)]
408#[derive(Default)]
409pub struct AroraWebBuilder {
410 inner: AroraBuilder,
411}
412
413#[wasm_bindgen(js_class = "AroraRuntimeBuilder")]
414impl AroraWebBuilder {
415 #[wasm_bindgen(constructor)]
416 pub fn new() -> AroraWebBuilder {
417 AroraWebBuilder::default()
418 }
419
420 #[wasm_bindgen(js_name = withModule)]
425 pub fn with_module(&mut self, header_json: &str, executable: &[u8]) -> Result<(), JsValue> {
426 let header: Header = serde_json::from_str(header_json)
427 .map_err(|e| JsValue::from_str(&format!("invalid header json: {e}")))?;
428 let inner = std::mem::take(&mut self.inner);
429 self.inner = inner.with_module(header, executable.to_vec());
430 Ok(())
431 }
432
433 pub fn build(&mut self) -> Result<AroraWeb, JsValue> {
436 let arora = std::mem::take(&mut self.inner)
437 .build()
438 .map_err(|e| JsValue::from_str(&format!("arora build failed: {e:?}")))?;
439 Ok(AroraWeb::from(arora))
440 }
441}
442
443#[wasm_bindgen]
445pub struct Engine {
446 inner: std::pin::Pin<Box<arora_engine::engine::Engine>>,
447 loader: SharedLoaderRc,
448 function_module: HashMap<Uuid, Uuid>,
449 module_headers: HashMap<Uuid, String>,
450}
451
452#[wasm_bindgen]
453impl Engine {
454 #[wasm_bindgen(constructor)]
455 pub fn new() -> Engine {
456 install_panic_hook();
457 let executor = BrowserExecutor::new();
458 let loader = executor.shared();
459 let inner = EngineBuilder::new().add_executor(executor).build();
460 Engine {
461 inner,
462 loader,
463 function_module: HashMap::new(),
464 module_headers: HashMap::new(),
465 }
466 }
467
468 #[wasm_bindgen(js_name = loadModule)]
475 pub fn load_module(&mut self, header_json: &str, executable: &[u8]) -> Result<String, JsValue> {
476 self.load_module_inner(header_json, executable.to_vec().into_boxed_slice())
477 }
478
479 #[wasm_bindgen(js_name = prepareModule)]
483 pub fn prepare_module(&self, header_json: &str, executable: Vec<u8>) -> js_sys::Promise {
484 prepare_module_impl(self.loader.clone(), header_json, executable)
485 }
486
487 #[wasm_bindgen(js_name = loadPreparedModule)]
490 pub fn load_prepared_module(&mut self, header_json: &str) -> Result<String, JsValue> {
491 self.load_module_inner(header_json, Box::new([]))
492 }
493
494 fn load_module_inner(
495 &mut self,
496 header_json: &str,
497 executable: Box<[u8]>,
498 ) -> Result<String, JsValue> {
499 let header: Header = serde_json::from_str(header_json)
500 .map_err(|e| JsValue::from_str(&format!("invalid header json: {e}")))?;
501 let header_json_str = header_json.to_string();
502 let loaded = load_module_from_parts(&mut *self.inner, header, executable)
503 .map_err(|e| JsValue::from_str(&format!("load_module failed: {e}")))?;
504 for fn_id in &loaded.function_ids {
505 self.function_module.insert(*fn_id, loaded.id);
506 }
507 self.module_headers.insert(loaded.id, header_json_str);
508 Ok(loaded.id.to_string())
509 }
510
511 #[wasm_bindgen(js_name = listModules)]
513 pub fn list_modules(&self) -> String {
514 let headers: Vec<serde_json::Value> = self
515 .module_headers
516 .values()
517 .filter_map(|s| serde_json::from_str(s).ok())
518 .collect();
519 serde_json::to_string(&headers).unwrap_or_else(|_| "[]".to_string())
520 }
521
522 #[wasm_bindgen]
525 pub fn call(&mut self, call_json: &str) -> Result<String, JsValue> {
526 let mut call: Call = serde_json::from_str(call_json)
527 .map_err(|e| JsValue::from_str(&format!("invalid call json: {e}")))?;
528 if call.module_id.is_none() {
529 call.module_id = Some(
531 *self
532 .function_module
533 .get(&call.id)
534 .ok_or_else(|| JsValue::from_str("no module known for function"))?,
535 );
536 }
537 let result = self
538 .inner
539 .arora_call(call)
540 .map_err(|e| JsValue::from_str(&format!("call failed: {e}")))?;
541 serde_json::to_string(&result)
542 .map_err(|e| JsValue::from_str(&format!("serialize failed: {e}")))
543 }
544}
545
546impl Default for Engine {
547 fn default() -> Self {
548 Self::new()
549 }
550}
551
552fn prepare_module_impl(
556 loader: SharedLoaderRc,
557 header_json: &str,
558 executable: Vec<u8>,
559) -> js_sys::Promise {
560 let header: Result<Header, _> = serde_json::from_str(header_json);
561 wasm_bindgen_futures::future_to_promise(async move {
562 let header = header.map_err(|e| JsValue::from_str(&format!("invalid header json: {e}")))?;
563 loader.prepare(header.id, executable).await?;
564 Ok(JsValue::UNDEFINED)
565 })
566}
567
568const TICK_ID_STRUCT_BYTES: [u8; 16] = [
578 0x6f, 0x49, 0xe6, 0x50, 0x84, 0xca, 0x48, 0x99, 0xa9, 0xbd, 0x1f, 0x3b, 0xf1, 0x7f, 0xab, 0x51,
579];
580const TICK_ID_CALLABLE_FIELD_BYTES: [u8; 16] = [
582 0x23, 0x79, 0x92, 0xd2, 0x17, 0xd1, 0x45, 0x9f, 0xbc, 0xa1, 0x71, 0x85, 0xfa, 0x6a, 0x69, 0xd7,
583];
584const STATUS_SUCCESS_BYTES: [u8; 16] = [
586 0x76, 0x6e, 0x9e, 0x9a, 0x44, 0x6d, 0x4e, 0x46, 0x83, 0xe6, 0x14, 0xb7, 0xca, 0x10, 0x11, 0x69,
587];
588const STATUS_FAILURE_BYTES: [u8; 16] = [
590 0x24, 0x68, 0xf4, 0x6c, 0xbb, 0x60, 0x42, 0x5c, 0x9a, 0x4d, 0x9a, 0xd3, 0x26, 0xcc, 0xc7, 0xe2,
591];
592const STATUS_ENUM_BYTES: [u8; 16] = [
594 0x32, 0x5a, 0x57, 0x67, 0xe3, 0x44, 0x45, 0x32, 0x86, 0x0e, 0x07, 0x49, 0xbc, 0xf2, 0xe4, 0x28,
595];
596const RET_PARAM_BYTES: [u8; 16] = [
598 0x5f, 0x72, 0x65, 0x74, 0x00, 0x00, 0x40, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
599];
600
601fn value_to_status(v: &Value) -> &'static str {
602 if let Value::Enumeration(e) = v {
603 if *e.variant_id.as_bytes() == STATUS_SUCCESS_BYTES {
604 return "success";
605 }
606 if *e.variant_id.as_bytes() == STATUS_FAILURE_BYTES {
607 return "failure";
608 }
609 }
610 "running"
611}
612
613#[derive(serde::Deserialize, Clone, Debug)]
616#[serde(rename_all = "snake_case")]
617enum BtExpression {
618 VariableId(Uuid),
619 Value(Value),
620}
621
622#[derive(serde::Deserialize, Debug)]
624struct BtNode {
625 id: Uuid,
626 function: Uuid,
627 #[serde(default)]
628 children: Option<Vec<Uuid>>,
629 #[serde(default)]
633 arguments: HashMap<Uuid, BtExpression>,
634}
635
636struct FnMeta {
638 module_id: Uuid,
639 children_param_id: Option<Uuid>,
642}
643
644struct NodeCallable {
646 node_id: Uuid,
647 fn_id: Uuid,
648 module_id: Uuid,
649 children_param_id: Option<Uuid>,
650 children_callable_ids: Vec<u64>,
651 arguments: HashMap<Uuid, BtExpression>,
652 variables: Rc<RefCell<HashMap<Uuid, Value>>>,
653 trace: Rc<RefCell<Vec<(Uuid, &'static str)>>>,
654}
655
656impl Callable for NodeCallable {
657 fn call(&self, caller: &mut dyn CallBridge) -> Result<Value, arora_engine::call::CallError> {
658 let tick_id_type = Uuid::from_bytes(TICK_ID_STRUCT_BYTES);
659 let callable_field = Uuid::from_bytes(TICK_ID_CALLABLE_FIELD_BYTES);
660 let ret_param_id = Uuid::from_bytes(RET_PARAM_BYTES);
661
662 let mut args = Vec::new();
663
664 if let Some(children_param_id) = self.children_param_id {
665 let elements: Vec<StructureWithoutId> = self
666 .children_callable_ids
667 .iter()
668 .map(|&id| StructureWithoutId {
669 fields: vec![StructureField {
670 id: callable_field,
671 value: Box::new(Value::U64(id)),
672 }],
673 })
674 .collect();
675 args.push(StructureField {
676 id: children_param_id,
677 value: Box::new(Value::ArrayStructure {
678 id: tick_id_type,
679 elements,
680 }),
681 });
682 }
683
684 for (¶m_id, expr) in &self.arguments {
685 if param_id == ret_param_id {
686 continue;
687 }
688 let value = match expr {
689 BtExpression::Value(v) => v.clone(),
690 BtExpression::VariableId(var_id) => self
691 .variables
692 .borrow()
693 .get(var_id)
694 .cloned()
695 .unwrap_or(Value::Unit),
696 };
697 args.push(StructureField {
698 id: param_id,
699 value: Box::new(value),
700 });
701 }
702
703 let mutable_param_vars: HashMap<Uuid, Uuid> = self
706 .arguments
707 .iter()
708 .filter_map(|(¶m_id, expr)| {
709 if param_id == ret_param_id {
710 return None;
711 }
712 if let BtExpression::VariableId(var_id) = expr {
713 Some((param_id, *var_id))
714 } else {
715 None
716 }
717 })
718 .collect();
719
720 let result = caller.arora_call(Call {
721 module_id: Some(self.module_id),
722 id: self.fn_id,
723 args,
724 })?;
725
726 for mutated in &result.mutated {
728 if let Some(&var_id) = mutable_param_vars.get(&mutated.id) {
729 self.variables
730 .borrow_mut()
731 .insert(var_id, *mutated.value.clone());
732 }
733 }
734
735 let has_ret = self.arguments.contains_key(&ret_param_id);
736 if has_ret {
737 if let Some(BtExpression::VariableId(var_id)) = self.arguments.get(&ret_param_id) {
738 self.variables
739 .borrow_mut()
740 .insert(*var_id, result.ret.clone());
741 }
742 }
743
744 let s = if has_ret {
745 "success"
746 } else {
747 value_to_status(&result.ret)
748 };
749 self.trace.borrow_mut().push((self.node_id, s));
750
751 if has_ret {
752 Ok(Value::Enumeration(Enumeration {
753 id: Uuid::from_bytes(STATUS_ENUM_BYTES),
754 variant_id: Uuid::from_bytes(STATUS_SUCCESS_BYTES),
755 value: Box::new(Value::Unit),
756 }))
757 } else {
758 Ok(result.ret)
759 }
760 }
761}
762
763fn register_node(
766 engine: &mut dyn CallBridge,
767 node_id: Uuid,
768 node_index: &HashMap<Uuid, BtNode>,
769 fn_meta: &HashMap<Uuid, FnMeta>,
770 trace: &Rc<RefCell<Vec<(Uuid, &'static str)>>>,
771 variables: &Rc<RefCell<HashMap<Uuid, Value>>>,
772) -> Result<u64, String> {
773 let node = node_index
774 .get(&node_id)
775 .ok_or_else(|| format!("node {node_id} not found in tree"))?;
776 let meta = fn_meta
777 .get(&node.function)
778 .ok_or_else(|| format!("function {} not registered in fn_meta", node.function))?;
779
780 let children_callable_ids = match &node.children {
781 None => vec![],
782 Some(ids) => ids
783 .iter()
784 .map(|&child_id| register_node(engine, child_id, node_index, fn_meta, trace, variables))
785 .collect::<Result<Vec<_>, _>>()?,
786 };
787
788 let callable: Rc<dyn Callable> = Rc::new(NodeCallable {
789 node_id,
790 fn_id: node.function,
791 module_id: meta.module_id,
792 children_param_id: meta.children_param_id,
793 children_callable_ids,
794 arguments: node.arguments.clone(),
795 variables: variables.clone(),
796 trace: trace.clone(),
797 });
798 let id = engine.arora_register_callable(callable);
799 Ok(id.id)
800}
801
802#[wasm_bindgen]
811pub struct BehaviorTreeRunner {
812 inner: Pin<Box<arora_engine::engine::Engine>>,
813 loader: SharedLoaderRc,
814 fn_meta: HashMap<Uuid, FnMeta>,
815 variables: Rc<RefCell<HashMap<Uuid, Value>>>,
816 module_headers: HashMap<Uuid, String>,
817}
818
819#[wasm_bindgen]
820impl BehaviorTreeRunner {
821 #[wasm_bindgen(constructor)]
822 pub fn new() -> BehaviorTreeRunner {
823 install_panic_hook();
824 let executor = BrowserExecutor::new();
825 let loader = executor.shared();
826 let inner = EngineBuilder::new().add_executor(executor).build();
827 BehaviorTreeRunner {
828 inner,
829 loader,
830 fn_meta: HashMap::new(),
831 variables: Rc::new(RefCell::new(HashMap::new())),
832 module_headers: HashMap::new(),
833 }
834 }
835
836 #[wasm_bindgen(js_name = loadModule)]
844 pub fn load_module(&mut self, header_json: &str, executable: &[u8]) -> Result<String, JsValue> {
845 self.load_module_inner(header_json, executable.to_vec().into_boxed_slice())
846 }
847
848 #[wasm_bindgen(js_name = prepareModule)]
852 pub fn prepare_module(&self, header_json: &str, executable: Vec<u8>) -> js_sys::Promise {
853 prepare_module_impl(self.loader.clone(), header_json, executable)
854 }
855
856 #[wasm_bindgen(js_name = loadPreparedModule)]
859 pub fn load_prepared_module(&mut self, header_json: &str) -> Result<String, JsValue> {
860 self.load_module_inner(header_json, Box::new([]))
861 }
862
863 fn load_module_inner(
864 &mut self,
865 header_json: &str,
866 executable: Box<[u8]>,
867 ) -> Result<String, JsValue> {
868 let header: Header = serde_json::from_str(header_json)
869 .map_err(|e| JsValue::from_str(&format!("invalid header: {e}")))?;
870 let module_id = header.id;
871 let header_json_str = header_json.to_string();
872
873 for export in &header.exports {
874 let arora_types::module::low::ExportSymbol::Function(f) = export;
875 let children_param_id = f.parameters.first().and_then(|p| {
876 if let arora_types::module::low::TypeRef::Array { id } = &p.ty {
877 if id == &Uuid::from_bytes(TICK_ID_STRUCT_BYTES) {
878 Some(p.id)
879 } else {
880 None
881 }
882 } else {
883 None
884 }
885 });
886
887 self.fn_meta.insert(
888 f.id,
889 FnMeta {
890 module_id,
891 children_param_id,
892 },
893 );
894 }
895
896 let result = load_module_from_parts(&mut *self.inner, header, executable)
897 .map_err(|e| JsValue::from_str(&format!("load failed: {e}")))?;
898 self.module_headers.insert(result.id, header_json_str);
899 Ok(result.id.to_string())
900 }
901
902 #[wasm_bindgen(js_name = listModules)]
904 pub fn list_modules(&self) -> String {
905 let headers: Vec<serde_json::Value> = self
906 .module_headers
907 .values()
908 .filter_map(|s| serde_json::from_str(s).ok())
909 .collect();
910 serde_json::to_string(&headers).unwrap_or_else(|_| "[]".to_string())
911 }
912
913 #[wasm_bindgen(js_name = setVariable)]
916 pub fn set_variable(&mut self, var_id: &str, value_json: &str) -> Result<(), JsValue> {
917 let var_id: Uuid = var_id
918 .parse()
919 .map_err(|_| JsValue::from_str("bad var_id: not a valid UUID"))?;
920 let value: Value = serde_json::from_str(value_json)
921 .map_err(|e| JsValue::from_str(&format!("bad value JSON: {e}")))?;
922 self.variables.borrow_mut().insert(var_id, value);
923 Ok(())
924 }
925
926 pub fn tick(&mut self, nodes_json: &str) -> Result<String, JsValue> {
933 let nodes: Vec<BtNode> = serde_json::from_str(nodes_json)
934 .map_err(|e| JsValue::from_str(&format!("bad nodes JSON: {e}")))?;
935 if nodes.is_empty() {
936 return Err(JsValue::from_str("tree has no nodes"));
937 }
938
939 let root_id = nodes[0].id;
940 let node_index: HashMap<Uuid, BtNode> = nodes.into_iter().map(|n| (n.id, n)).collect();
941 let trace: Rc<RefCell<Vec<(Uuid, &'static str)>>> = Rc::new(RefCell::new(Vec::new()));
942
943 let fn_meta = &self.fn_meta;
944 let root_callable_id = register_node(
945 &mut *self.inner,
946 root_id,
947 &node_index,
948 fn_meta,
949 &trace,
950 &self.variables,
951 )
952 .map_err(|e| JsValue::from_str(&format!("setup error: {e}")))?;
953
954 let callable_id = CallableId {
955 id: root_callable_id,
956 };
957 let result = Callable::call(&callable_id, &mut *self.inner)
958 .map_err(|e| JsValue::from_str(&format!("tick error: {e}")))?;
959 let status = value_to_status(&result);
960
961 let trace_json: Vec<serde_json::Value> = trace
962 .borrow()
963 .iter()
964 .map(|(id, s)| serde_json::json!({ "nodeId": id.to_string(), "status": s }))
965 .collect();
966
967 let vars_json: serde_json::Map<String, serde_json::Value> = self
968 .variables
969 .borrow()
970 .iter()
971 .filter_map(|(id, v)| serde_json::to_value(v).ok().map(|jv| (id.to_string(), jv)))
972 .collect();
973
974 Ok(serde_json::json!({
975 "status": status,
976 "trace": trace_json,
977 "variables": vars_json,
978 })
979 .to_string())
980 }
981
982 pub fn run(&mut self, nodes_json: &str) -> Result<String, JsValue> {
992 let nodes: Vec<BtNode> = serde_json::from_str(nodes_json)
993 .map_err(|e| JsValue::from_str(&format!("bad nodes JSON: {e}")))?;
994 if nodes.is_empty() {
995 return Err(JsValue::from_str("tree has no nodes"));
996 }
997
998 let root_id = nodes[0].id;
999 let node_index: HashMap<Uuid, BtNode> = nodes.into_iter().map(|n| (n.id, n)).collect();
1000 let trace: Rc<RefCell<Vec<(Uuid, &'static str)>>> = Rc::new(RefCell::new(Vec::new()));
1001
1002 let fn_meta = &self.fn_meta;
1003 let root_callable_id = register_node(
1004 &mut *self.inner,
1005 root_id,
1006 &node_index,
1007 fn_meta,
1008 &trace,
1009 &self.variables,
1010 )
1011 .map_err(|e| JsValue::from_str(&format!("setup error: {e}")))?;
1012
1013 let callable_id = CallableId {
1014 id: root_callable_id,
1015 };
1016
1017 let mut last_status = "running";
1018 for _ in 0..10_000 {
1019 let result = Callable::call(&callable_id, &mut *self.inner)
1020 .map_err(|e| JsValue::from_str(&format!("tick error: {e}")))?;
1021 last_status = value_to_status(&result);
1022 if last_status != "running" {
1023 break;
1024 }
1025 }
1026
1027 let trace_json: Vec<serde_json::Value> = trace
1028 .borrow()
1029 .iter()
1030 .map(|(id, s)| serde_json::json!({ "nodeId": id.to_string(), "status": s }))
1031 .collect();
1032
1033 let out = serde_json::json!({
1034 "status": last_status,
1035 "trace": trace_json,
1036 });
1037 Ok(out.to_string())
1038 }
1039}
1040
1041impl Default for BehaviorTreeRunner {
1042 fn default() -> Self {
1043 Self::new()
1044 }
1045}
1046
1047#[wasm_bindgen]
1056pub struct Registry {
1057 entries: HashMap<Uuid, (String, Option<Uuid>)>,
1058}
1059
1060#[derive(serde::Deserialize)]
1061struct RecordEntry {
1062 id: Uuid,
1063 name: String,
1064 parent: Option<Uuid>,
1065}
1066
1067#[wasm_bindgen]
1068impl Registry {
1069 #[wasm_bindgen(constructor)]
1070 pub fn new() -> Registry {
1071 Registry {
1072 entries: HashMap::new(),
1073 }
1074 }
1075
1076 #[wasm_bindgen(js_name = loadRecordsJson)]
1078 pub fn load_records_json(&mut self, json: &str) -> Result<(), JsValue> {
1079 let records: Vec<RecordEntry> = serde_json::from_str(json)
1080 .map_err(|e| JsValue::from_str(&format!("invalid records JSON: {e}")))?;
1081 for r in records {
1082 self.entries.insert(r.id, (r.name, r.parent));
1083 }
1084 Ok(())
1085 }
1086
1087 #[wasm_bindgen(js_name = resolveId)]
1090 pub fn resolve_id(&self, id: &str) -> Option<String> {
1091 let uuid: Uuid = id.parse().ok()?;
1092 self.compute_path(&uuid)
1093 }
1094
1095 fn compute_path(&self, id: &Uuid) -> Option<String> {
1096 let (name, parent) = self.entries.get(id)?;
1097 match parent {
1098 None => Some(name.clone()),
1099 Some(parent_id) if !self.entries.contains_key(parent_id) => Some(name.clone()),
1100 Some(parent_id) => Some(format!("{}.{}", self.compute_path(parent_id)?, name)),
1101 }
1102 }
1103}
1104
1105impl Default for Registry {
1106 fn default() -> Self {
1107 Self::new()
1108 }
1109}