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, Invoked, LocalCaller, TaskId};
61use arora_bridge::client::{self, KeyInfo};
62use arora_types::data::{DataStore, Key, StateChange, Subscription};
63use std::future::Future;
64use std::time::Duration;
65
66fn promise(future: impl Future<Output = Result<JsValue, JsValue>> + 'static) -> js_sys::Promise {
75 let mut future = Box::pin(future);
76 let mut cx = std::task::Context::from_waker(futures::task::noop_waker_ref());
77 match future.as_mut().poll(&mut cx) {
78 std::task::Poll::Ready(Ok(value)) => js_sys::Promise::resolve(&value),
79 std::task::Poll::Ready(Err(error)) => js_sys::Promise::reject(&error),
80 std::task::Poll::Pending => wasm_bindgen_futures::future_to_promise(future),
81 }
82}
83
84fn to_js(value: &impl serde::Serialize) -> Result<JsValue, JsValue> {
87 value
88 .serialize(&serde_wasm_bindgen::Serializer::json_compatible())
89 .map_err(|e| JsValue::from_str(&format!("serialize failed: {e}")))
90}
91
92fn value_to_js(value: &Value) -> Result<JsValue, JsValue> {
95 let json = serde_json::to_value(value)
96 .map_err(|e| JsValue::from_str(&format!("serialize value failed: {e}")))?;
97 to_js(&json)
98}
99
100fn run_to_js(handle: &arora::TaskHandle) -> Result<JsValue, JsValue> {
103 let spawned = arora_behavior::interpreter_module::encode_spawn_result(handle);
104 let run = client::run_of(&spawned).map_err(|e| JsValue::from_str(&e))?;
105 to_js(&run)
106}
107
108pub mod store_json {
114 use super::*;
115
116 pub fn set_value(store: &dyn DataStore, path: &str, value_json: &str) -> Result<(), JsValue> {
119 let value: Value = serde_json::from_str(value_json)
120 .map_err(|e| JsValue::from_str(&format!("invalid value json for {path}: {e}")))?;
121 store
122 .write(StateChange::set(path, value))
123 .map_err(|e| JsValue::from_str(&format!("write {path} failed: {e}")))
124 }
125
126 pub fn write_values(store: &dyn DataStore, values_json: &str) -> Result<(), JsValue> {
130 let map: serde_json::Map<String, serde_json::Value> = serde_json::from_str(values_json)
131 .map_err(|e| JsValue::from_str(&format!("invalid values json: {e}")))?;
132 let mut change = StateChange::new();
133 for (path, raw) in map {
134 let value: Value = serde_json::from_value(raw)
135 .map_err(|e| JsValue::from_str(&format!("invalid value for {path}: {e}")))?;
136 change.set.insert(Key::new(path), Some(value));
137 }
138 store
139 .write(change)
140 .map_err(|e| JsValue::from_str(&format!("write failed: {e}")))
141 }
142
143 pub fn read_values(store: &dyn DataStore, paths: JsValue) -> Result<JsValue, JsValue> {
146 let paths: Vec<String> = serde_wasm_bindgen::from_value(paths)
147 .map_err(|e| JsValue::from_str(&format!("paths must be a string[]: {e}")))?;
148 let keys: Vec<Key> = paths.iter().map(Key::new).collect();
149 let values = store.read(&keys);
150 let mut out = serde_json::Map::with_capacity(paths.len());
151 for (path, value) in paths.into_iter().zip(values) {
152 out.insert(path, value_to_json(value)?);
153 }
154 to_js_object(out)
155 }
156
157 pub fn snapshot(store: &dyn DataStore) -> Result<JsValue, JsValue> {
160 let state = store.snapshot();
161 let mut out = serde_json::Map::with_capacity(state.storage.len());
162 for (key, value) in state.storage {
163 out.insert(key.path, value_to_json(value)?);
164 }
165 to_js_object(out)
166 }
167
168 pub fn drain_changes(changes: &Subscription) -> Result<JsValue, JsValue> {
175 let mut out = serde_json::Map::new();
176 while let Some(change) = changes.try_recv() {
177 for (key, value) in change.set {
178 out.insert(key.path, value_to_json(value)?);
179 }
180 for key in change.unset {
181 out.insert(key.path, serde_json::Value::Null);
182 }
183 }
184 to_js_object(out)
185 }
186
187 fn to_js_object(out: serde_json::Map<String, serde_json::Value>) -> Result<JsValue, JsValue> {
192 use serde::Serialize;
193 serde_json::Value::Object(out)
194 .serialize(&serde_wasm_bindgen::Serializer::json_compatible())
195 .map_err(|e| JsValue::from_str(&format!("serialize failed: {e}")))
196 }
197
198 fn value_to_json(value: Option<Value>) -> Result<serde_json::Value, JsValue> {
200 match value {
201 Some(v) => serde_json::to_value(v)
202 .map_err(|e| JsValue::from_str(&format!("serialize value failed: {e}"))),
203 None => Ok(serde_json::Value::Null),
204 }
205 }
206}
207
208#[wasm_bindgen(js_name = AroraRuntime)]
234pub struct AroraWeb {
235 device: Rc<RefCell<Option<Arora>>>,
239 stop: RefCell<Option<futures::channel::oneshot::Sender<()>>>,
242 caller: LocalCaller,
243 store: Box<dyn DataStore>,
244 changes: Subscription,
245 behavior_error: tokio::sync::watch::Receiver<Option<String>>,
248 behavior_error_changes: Rc<RefCell<Option<tokio::sync::watch::Receiver<Option<String>>>>>,
252}
253
254impl From<Arora> for AroraWeb {
255 fn from(arora: Arora) -> Self {
259 install_panic_hook();
260 let caller = arora.caller();
261 let store = arora.store().clone_box();
262 let changes = store.subscribe();
263 let behavior_error = arora.behavior_error();
264 let behavior_error_changes = Rc::new(RefCell::new(Some(arora.behavior_error())));
265 Self {
266 device: Rc::new(RefCell::new(Some(arora))),
267 stop: RefCell::new(None),
268 caller,
269 store,
270 changes,
271 behavior_error,
272 behavior_error_changes,
273 }
274 }
275}
276
277#[wasm_bindgen(js_class = "AroraRuntime")]
278impl AroraWeb {
279 #[wasm_bindgen(constructor)]
282 pub fn new() -> Result<AroraWeb, JsValue> {
283 Ok(AroraWeb::from(Arora::builder().build().map_err(|e| {
284 JsValue::from_str(&format!("arora build failed: {e:?}"))
285 })?))
286 }
287
288 pub fn step(&self, dt_ms: f64) -> Result<(), JsValue> {
294 let mut slot = self
295 .device
296 .try_borrow_mut()
297 .map_err(|_| JsValue::from_str("the device is mid-step; call between steps"))?;
298 let device = slot
299 .as_mut()
300 .ok_or_else(|| JsValue::from_str("run() drives the device; step() is unavailable"))?;
301 device
302 .step(Duration::from_secs_f64(dt_ms / 1_000.0))
303 .map_err(|e| JsValue::from_str(&format!("step failed: {e}")))
304 }
305
306 pub fn run(&self, period_ms: Option<f64>) -> js_sys::Promise {
316 let Some(mut device) = self.device.borrow_mut().take() else {
317 return js_sys::Promise::reject(&JsValue::from_str("the device is already running"));
318 };
319 let slot = self.device.clone();
320 let (stop_tx, stop_rx) = futures::channel::oneshot::channel::<()>();
321 *self.stop.borrow_mut() = Some(stop_tx);
322 wasm_bindgen_futures::future_to_promise(async move {
323 let period = period_ms
324 .map(|ms| Duration::from_secs_f64(ms / 1_000.0))
325 .unwrap_or(Arora::DEFAULT_STEP_PERIOD);
326 let outcome = {
327 use futures::FutureExt;
328 let run = device.run(period).fuse();
329 let mut stop_rx = stop_rx.fuse();
330 futures::pin_mut!(run);
331 futures::select_biased! {
332 result = run => Some(result),
333 _ = stop_rx => None,
334 }
335 };
336 slot.borrow_mut().replace(device);
340 match outcome {
341 Some(result) => {
343 result.map_err(|e| JsValue::from_str(&format!("run failed: {e}")))?;
344 Ok(JsValue::UNDEFINED)
345 }
346 None => Ok(JsValue::UNDEFINED),
348 }
349 })
350 }
351
352 pub fn stop(&self) {
356 if let Some(stop) = self.stop.borrow_mut().take() {
357 let _ = stop.send(());
358 }
359 }
360
361 #[wasm_bindgen(getter)]
363 pub fn running(&self) -> bool {
364 self.device.borrow().is_none()
365 }
366
367 #[wasm_bindgen(getter, js_name = behaviorError)]
372 pub fn behavior_error(&self) -> Option<String> {
373 self.behavior_error.borrow().clone()
374 }
375
376 #[wasm_bindgen(js_name = behaviorErrorChanged)]
382 pub fn behavior_error_changed(&self) -> js_sys::Promise {
383 let Some(mut errors) = self.behavior_error_changes.borrow_mut().take() else {
384 return js_sys::Promise::reject(&JsValue::from_str(
385 "a behaviorErrorChanged await is already pending",
386 ));
387 };
388 let slot = self.behavior_error_changes.clone();
389 wasm_bindgen_futures::future_to_promise(async move {
390 let outcome = errors.changed().await;
391 let value = errors.borrow_and_update().clone();
392 slot.borrow_mut().replace(errors);
393 outcome.map_err(|_| JsValue::from_str("the device is gone"))?;
394 Ok(match value {
395 Some(message) => JsValue::from_str(&message),
396 None => JsValue::UNDEFINED,
397 })
398 })
399 }
400
401 pub fn call(&self, call_json: &str) -> js_sys::Promise {
408 let parsed: Result<Call, _> = serde_json::from_str(call_json)
409 .map_err(|e| JsValue::from_str(&format!("invalid call json: {e}")));
410 let caller = self.caller.clone();
411 promise(async move {
412 let result = caller
413 .call(parsed?)
414 .await
415 .map_err(|e| JsValue::from_str(&format!("call failed: {e}")))?;
416 let json = serde_json::to_string(&result)
417 .map_err(|e| JsValue::from_str(&format!("serialize failed: {e}")))?;
418 Ok(JsValue::from_str(&json))
419 })
420 }
421
422 pub fn invoke(
440 &self,
441 method: &str,
442 args_json: &str,
443 module_id: Option<String>,
444 ) -> js_sys::Promise {
445 let args: Result<HashMap<String, Value>, _> = serde_json::from_str(args_json)
446 .map_err(|e| JsValue::from_str(&format!("invalid args json: {e}")));
447 let module = module_id
448 .map(|id| {
449 Uuid::parse_str(&id)
450 .map_err(|e| JsValue::from_str(&format!("'{id}' is not a module id: {e}")))
451 })
452 .transpose();
453 let caller = self.caller.clone();
454 let method = method.to_string();
455 promise(async move {
456 let invoked = caller
457 .invoke(&method, args?, module?)
458 .await
459 .map_err(|e| JsValue::from_str(&format!("invoke {method} failed: {e}")))?;
460 match invoked {
461 Invoked::Returned(value) => value_to_js(&value),
462 Invoked::Started(handle) => run_to_js(&handle),
463 }
464 })
465 }
466
467 pub fn spawn(&self, call_json: &str) -> js_sys::Promise {
476 let parsed: Result<Call, _> = serde_json::from_str(call_json)
477 .map_err(|e| JsValue::from_str(&format!("invalid call json: {e}")));
478 let caller = self.caller.clone();
479 promise(async move {
480 let handle = caller
481 .spawn(parsed?)
482 .await
483 .map_err(|e| JsValue::from_str(&format!("spawn failed: {e}")))?;
484 run_to_js(&handle)
485 })
486 }
487
488 pub fn halt(&self, run_id: &str) -> js_sys::Promise {
493 let run = Uuid::parse_str(run_id)
494 .map_err(|e| JsValue::from_str(&format!("'{run_id}' is not a run id: {e}")));
495 let caller = self.caller.clone();
496 promise(async move {
497 caller
498 .halt(TaskId(run?))
499 .await
500 .map_err(|e| JsValue::from_str(&format!("halt failed: {e}")))?;
501 Ok(JsValue::UNDEFINED)
502 })
503 }
504
505 #[wasm_bindgen(js_name = listKeys)]
512 pub fn list_keys(&self, prefix: Option<String>) -> js_sys::Promise {
513 let listed = self.caller.list_keys(prefix);
514 promise(async move {
515 let keys: Vec<KeyInfo> = listed
516 .await
517 .map_err(|e| JsValue::from_str(&format!("listKeys failed: {e}")))?
518 .into_iter()
519 .map(|(path, meta)| KeyInfo { path, meta })
520 .collect();
521 to_js(&keys)
522 })
523 }
524
525 #[wasm_bindgen(js_name = describeMethods)]
534 pub fn describe_methods(&self, prefix: Option<String>) -> js_sys::Promise {
535 let described = self.caller.describe_methods(prefix);
536 promise(async move {
537 let signatures = described
538 .await
539 .map_err(|e| JsValue::from_str(&format!("describeMethods failed: {e}")))?;
540 let mut methods = Vec::with_capacity(signatures.len());
541 for signature in &signatures {
542 let mut method = serde_json::to_value(client::method_info(signature))
543 .map_err(|e| JsValue::from_str(&format!("serialize failed: {e}")))?;
544 method["module"] = serde_json::Value::String(signature.module_id.to_string());
545 methods.push(method);
546 }
547 to_js(&methods)
548 })
549 }
550
551 #[wasm_bindgen(js_name = setValue)]
553 pub fn set_value(&self, path: &str, value_json: &str) -> Result<(), JsValue> {
554 store_json::set_value(&*self.store, path, value_json)
555 }
556
557 #[wasm_bindgen(js_name = writeValues)]
560 pub fn write_values(&self, values_json: &str) -> Result<(), JsValue> {
561 store_json::write_values(&*self.store, values_json)
562 }
563
564 #[wasm_bindgen(js_name = readValues)]
566 pub fn read_values(&self, paths: JsValue) -> Result<JsValue, JsValue> {
567 store_json::read_values(&*self.store, paths)
568 }
569
570 pub fn snapshot(&self) -> Result<JsValue, JsValue> {
572 store_json::snapshot(&*self.store)
573 }
574
575 #[wasm_bindgen(js_name = drainChanges)]
579 pub fn drain_changes(&self) -> Result<JsValue, JsValue> {
580 store_json::drain_changes(&self.changes)
581 }
582}
583
584impl AroraWeb {
585 pub fn caller(&self) -> LocalCaller {
589 self.caller.clone()
590 }
591}
592
593#[wasm_bindgen(js_name = AroraRuntimeBuilder)]
600#[derive(Default)]
601pub struct AroraWebBuilder {
602 inner: AroraBuilder,
603}
604
605#[wasm_bindgen(js_class = "AroraRuntimeBuilder")]
606impl AroraWebBuilder {
607 #[wasm_bindgen(constructor)]
608 pub fn new() -> AroraWebBuilder {
609 AroraWebBuilder::default()
610 }
611
612 #[wasm_bindgen(js_name = withModule)]
617 pub fn with_module(&mut self, header_json: &str, executable: &[u8]) -> Result<(), JsValue> {
618 let header: Header = serde_json::from_str(header_json)
619 .map_err(|e| JsValue::from_str(&format!("invalid header json: {e}")))?;
620 let inner = std::mem::take(&mut self.inner);
621 self.inner = inner.with_module(header, executable.to_vec());
622 Ok(())
623 }
624
625 pub fn build(&mut self) -> Result<AroraWeb, JsValue> {
628 let arora = std::mem::take(&mut self.inner)
629 .build()
630 .map_err(|e| JsValue::from_str(&format!("arora build failed: {e:?}")))?;
631 Ok(AroraWeb::from(arora))
632 }
633}
634
635#[wasm_bindgen]
637pub struct Engine {
638 inner: std::pin::Pin<Box<arora_engine::engine::Engine>>,
639 loader: SharedLoaderRc,
640 function_module: HashMap<Uuid, Uuid>,
641 module_headers: HashMap<Uuid, String>,
642}
643
644#[wasm_bindgen]
645impl Engine {
646 #[wasm_bindgen(constructor)]
647 pub fn new() -> Engine {
648 install_panic_hook();
649 let executor = BrowserExecutor::new();
650 let loader = executor.shared();
651 let inner = EngineBuilder::new().add_executor(executor).build();
652 Engine {
653 inner,
654 loader,
655 function_module: HashMap::new(),
656 module_headers: HashMap::new(),
657 }
658 }
659
660 #[wasm_bindgen(js_name = loadModule)]
667 pub fn load_module(&mut self, header_json: &str, executable: &[u8]) -> Result<String, JsValue> {
668 self.load_module_inner(header_json, executable.to_vec().into_boxed_slice())
669 }
670
671 #[wasm_bindgen(js_name = prepareModule)]
675 pub fn prepare_module(&self, header_json: &str, executable: Vec<u8>) -> js_sys::Promise {
676 prepare_module_impl(self.loader.clone(), header_json, executable)
677 }
678
679 #[wasm_bindgen(js_name = loadPreparedModule)]
682 pub fn load_prepared_module(&mut self, header_json: &str) -> Result<String, JsValue> {
683 self.load_module_inner(header_json, Box::new([]))
684 }
685
686 fn load_module_inner(
687 &mut self,
688 header_json: &str,
689 executable: Box<[u8]>,
690 ) -> Result<String, JsValue> {
691 let header: Header = serde_json::from_str(header_json)
692 .map_err(|e| JsValue::from_str(&format!("invalid header json: {e}")))?;
693 let header_json_str = header_json.to_string();
694 let loaded = load_module_from_parts(&mut self.inner, header, executable)
695 .map_err(|e| JsValue::from_str(&format!("load_module failed: {e}")))?;
696 for fn_id in &loaded.function_ids {
697 self.function_module.insert(*fn_id, loaded.id);
698 }
699 self.module_headers.insert(loaded.id, header_json_str);
700 Ok(loaded.id.to_string())
701 }
702
703 #[wasm_bindgen(js_name = listModules)]
705 pub fn list_modules(&self) -> String {
706 let headers: Vec<serde_json::Value> = self
707 .module_headers
708 .values()
709 .filter_map(|s| serde_json::from_str(s).ok())
710 .collect();
711 serde_json::to_string(&headers).unwrap_or_else(|_| "[]".to_string())
712 }
713
714 #[wasm_bindgen]
717 pub fn call(&mut self, call_json: &str) -> Result<String, JsValue> {
718 let mut call: Call = serde_json::from_str(call_json)
719 .map_err(|e| JsValue::from_str(&format!("invalid call json: {e}")))?;
720 if call.module_id.is_none() {
721 call.module_id = Some(
723 *self
724 .function_module
725 .get(&call.id)
726 .ok_or_else(|| JsValue::from_str("no module known for function"))?,
727 );
728 }
729 let result = self
730 .inner
731 .arora_call(call)
732 .map_err(|e| JsValue::from_str(&format!("call failed: {e}")))?;
733 serde_json::to_string(&result)
734 .map_err(|e| JsValue::from_str(&format!("serialize failed: {e}")))
735 }
736}
737
738impl Default for Engine {
739 fn default() -> Self {
740 Self::new()
741 }
742}
743
744fn prepare_module_impl(
748 loader: SharedLoaderRc,
749 header_json: &str,
750 executable: Vec<u8>,
751) -> js_sys::Promise {
752 let header: Result<Header, _> = serde_json::from_str(header_json);
753 wasm_bindgen_futures::future_to_promise(async move {
754 let header = header.map_err(|e| JsValue::from_str(&format!("invalid header json: {e}")))?;
755 loader.prepare(header.id, executable).await?;
756 Ok(JsValue::UNDEFINED)
757 })
758}
759
760const TICK_ID_STRUCT_BYTES: [u8; 16] = [
770 0x6f, 0x49, 0xe6, 0x50, 0x84, 0xca, 0x48, 0x99, 0xa9, 0xbd, 0x1f, 0x3b, 0xf1, 0x7f, 0xab, 0x51,
771];
772const TICK_ID_CALLABLE_FIELD_BYTES: [u8; 16] = [
774 0x23, 0x79, 0x92, 0xd2, 0x17, 0xd1, 0x45, 0x9f, 0xbc, 0xa1, 0x71, 0x85, 0xfa, 0x6a, 0x69, 0xd7,
775];
776const STATUS_SUCCESS_BYTES: [u8; 16] = [
778 0x76, 0x6e, 0x9e, 0x9a, 0x44, 0x6d, 0x4e, 0x46, 0x83, 0xe6, 0x14, 0xb7, 0xca, 0x10, 0x11, 0x69,
779];
780const STATUS_FAILURE_BYTES: [u8; 16] = [
782 0x24, 0x68, 0xf4, 0x6c, 0xbb, 0x60, 0x42, 0x5c, 0x9a, 0x4d, 0x9a, 0xd3, 0x26, 0xcc, 0xc7, 0xe2,
783];
784const STATUS_ENUM_BYTES: [u8; 16] = [
786 0x32, 0x5a, 0x57, 0x67, 0xe3, 0x44, 0x45, 0x32, 0x86, 0x0e, 0x07, 0x49, 0xbc, 0xf2, 0xe4, 0x28,
787];
788const RET_PARAM_BYTES: [u8; 16] = [
790 0x5f, 0x72, 0x65, 0x74, 0x00, 0x00, 0x40, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
791];
792
793fn value_to_status(v: &Value) -> &'static str {
794 if let Value::Enumeration(e) = v {
795 if *e.variant_id.as_bytes() == STATUS_SUCCESS_BYTES {
796 return "success";
797 }
798 if *e.variant_id.as_bytes() == STATUS_FAILURE_BYTES {
799 return "failure";
800 }
801 }
802 "running"
803}
804
805#[derive(serde::Deserialize, Clone, Debug)]
808#[serde(rename_all = "snake_case")]
809enum BtExpression {
810 VariableId(Uuid),
811 Value(Value),
812}
813
814#[derive(serde::Deserialize, Debug)]
816struct BtNode {
817 id: Uuid,
818 function: Uuid,
819 #[serde(default)]
820 children: Option<Vec<Uuid>>,
821 #[serde(default)]
825 arguments: HashMap<Uuid, BtExpression>,
826}
827
828struct FnMeta {
830 module_id: Uuid,
831 children_param_id: Option<Uuid>,
834}
835
836struct NodeCallable {
838 node_id: Uuid,
839 fn_id: Uuid,
840 module_id: Uuid,
841 children_param_id: Option<Uuid>,
842 children_callable_ids: Vec<u64>,
843 arguments: HashMap<Uuid, BtExpression>,
844 variables: Rc<RefCell<HashMap<Uuid, Value>>>,
845 trace: Rc<RefCell<Vec<(Uuid, &'static str)>>>,
846}
847
848impl Callable for NodeCallable {
849 fn call(&self, caller: &mut dyn CallBridge) -> Result<Value, arora_engine::call::CallError> {
850 let tick_id_type = Uuid::from_bytes(TICK_ID_STRUCT_BYTES);
851 let callable_field = Uuid::from_bytes(TICK_ID_CALLABLE_FIELD_BYTES);
852 let ret_param_id = Uuid::from_bytes(RET_PARAM_BYTES);
853
854 let mut args = Vec::new();
855
856 if let Some(children_param_id) = self.children_param_id {
857 let elements: Vec<StructureWithoutId> = self
858 .children_callable_ids
859 .iter()
860 .map(|&id| StructureWithoutId {
861 fields: vec![StructureField {
862 id: callable_field,
863 value: Box::new(Value::U64(id)),
864 }],
865 })
866 .collect();
867 args.push(StructureField {
868 id: children_param_id,
869 value: Box::new(Value::ArrayStructure {
870 id: tick_id_type,
871 elements,
872 }),
873 });
874 }
875
876 for (¶m_id, expr) in &self.arguments {
877 if param_id == ret_param_id {
878 continue;
879 }
880 let value = match expr {
881 BtExpression::Value(v) => v.clone(),
882 BtExpression::VariableId(var_id) => self
883 .variables
884 .borrow()
885 .get(var_id)
886 .cloned()
887 .unwrap_or(Value::Unit),
888 };
889 args.push(StructureField {
890 id: param_id,
891 value: Box::new(value),
892 });
893 }
894
895 let mutable_param_vars: HashMap<Uuid, Uuid> = self
898 .arguments
899 .iter()
900 .filter_map(|(¶m_id, expr)| {
901 if param_id == ret_param_id {
902 return None;
903 }
904 if let BtExpression::VariableId(var_id) = expr {
905 Some((param_id, *var_id))
906 } else {
907 None
908 }
909 })
910 .collect();
911
912 let result = caller.arora_call(Call {
913 module_id: Some(self.module_id),
914 id: self.fn_id,
915 args,
916 })?;
917
918 for mutated in &result.mutated {
920 if let Some(&var_id) = mutable_param_vars.get(&mutated.id) {
921 self.variables
922 .borrow_mut()
923 .insert(var_id, *mutated.value.clone());
924 }
925 }
926
927 let has_ret = self.arguments.contains_key(&ret_param_id);
928 if has_ret {
929 if let Some(BtExpression::VariableId(var_id)) = self.arguments.get(&ret_param_id) {
930 self.variables
931 .borrow_mut()
932 .insert(*var_id, result.ret.clone());
933 }
934 }
935
936 let s = if has_ret {
937 "success"
938 } else {
939 value_to_status(&result.ret)
940 };
941 self.trace.borrow_mut().push((self.node_id, s));
942
943 if has_ret {
944 Ok(Value::Enumeration(Enumeration {
945 id: Uuid::from_bytes(STATUS_ENUM_BYTES),
946 variant_id: Uuid::from_bytes(STATUS_SUCCESS_BYTES),
947 value: Box::new(Value::Unit),
948 }))
949 } else {
950 Ok(result.ret)
951 }
952 }
953}
954
955fn register_node(
958 engine: &mut dyn CallBridge,
959 node_id: Uuid,
960 node_index: &HashMap<Uuid, BtNode>,
961 fn_meta: &HashMap<Uuid, FnMeta>,
962 trace: &Rc<RefCell<Vec<(Uuid, &'static str)>>>,
963 variables: &Rc<RefCell<HashMap<Uuid, Value>>>,
964) -> Result<u64, String> {
965 let node = node_index
966 .get(&node_id)
967 .ok_or_else(|| format!("node {node_id} not found in tree"))?;
968 let meta = fn_meta
969 .get(&node.function)
970 .ok_or_else(|| format!("function {} not registered in fn_meta", node.function))?;
971
972 let children_callable_ids = match &node.children {
973 None => vec![],
974 Some(ids) => ids
975 .iter()
976 .map(|&child_id| register_node(engine, child_id, node_index, fn_meta, trace, variables))
977 .collect::<Result<Vec<_>, _>>()?,
978 };
979
980 let callable: Rc<dyn Callable> = Rc::new(NodeCallable {
981 node_id,
982 fn_id: node.function,
983 module_id: meta.module_id,
984 children_param_id: meta.children_param_id,
985 children_callable_ids,
986 arguments: node.arguments.clone(),
987 variables: variables.clone(),
988 trace: trace.clone(),
989 });
990 let id = engine.arora_register_callable(callable);
991 Ok(id.id)
992}
993
994#[wasm_bindgen]
1003pub struct BehaviorTreeRunner {
1004 inner: Pin<Box<arora_engine::engine::Engine>>,
1005 loader: SharedLoaderRc,
1006 fn_meta: HashMap<Uuid, FnMeta>,
1007 variables: Rc<RefCell<HashMap<Uuid, Value>>>,
1008 module_headers: HashMap<Uuid, String>,
1009}
1010
1011#[wasm_bindgen]
1012impl BehaviorTreeRunner {
1013 #[wasm_bindgen(constructor)]
1014 pub fn new() -> BehaviorTreeRunner {
1015 install_panic_hook();
1016 let executor = BrowserExecutor::new();
1017 let loader = executor.shared();
1018 let inner = EngineBuilder::new().add_executor(executor).build();
1019 BehaviorTreeRunner {
1020 inner,
1021 loader,
1022 fn_meta: HashMap::new(),
1023 variables: Rc::new(RefCell::new(HashMap::new())),
1024 module_headers: HashMap::new(),
1025 }
1026 }
1027
1028 #[wasm_bindgen(js_name = loadModule)]
1036 pub fn load_module(&mut self, header_json: &str, executable: &[u8]) -> Result<String, JsValue> {
1037 self.load_module_inner(header_json, executable.to_vec().into_boxed_slice())
1038 }
1039
1040 #[wasm_bindgen(js_name = prepareModule)]
1044 pub fn prepare_module(&self, header_json: &str, executable: Vec<u8>) -> js_sys::Promise {
1045 prepare_module_impl(self.loader.clone(), header_json, executable)
1046 }
1047
1048 #[wasm_bindgen(js_name = loadPreparedModule)]
1051 pub fn load_prepared_module(&mut self, header_json: &str) -> Result<String, JsValue> {
1052 self.load_module_inner(header_json, Box::new([]))
1053 }
1054
1055 fn load_module_inner(
1056 &mut self,
1057 header_json: &str,
1058 executable: Box<[u8]>,
1059 ) -> Result<String, JsValue> {
1060 let header: Header = serde_json::from_str(header_json)
1061 .map_err(|e| JsValue::from_str(&format!("invalid header: {e}")))?;
1062 let module_id = header.id;
1063 let header_json_str = header_json.to_string();
1064
1065 for export in &header.exports {
1066 let arora_types::module::low::ExportSymbol::Function(f) = export;
1067 let children_param_id = f.parameters.first().and_then(|p| {
1068 if let arora_types::module::low::TypeRef::Array { id } = &p.ty {
1069 if id == &Uuid::from_bytes(TICK_ID_STRUCT_BYTES) {
1070 Some(p.id)
1071 } else {
1072 None
1073 }
1074 } else {
1075 None
1076 }
1077 });
1078
1079 self.fn_meta.insert(
1080 f.id,
1081 FnMeta {
1082 module_id,
1083 children_param_id,
1084 },
1085 );
1086 }
1087
1088 let result = load_module_from_parts(&mut self.inner, header, executable)
1089 .map_err(|e| JsValue::from_str(&format!("load failed: {e}")))?;
1090 self.module_headers.insert(result.id, header_json_str);
1091 Ok(result.id.to_string())
1092 }
1093
1094 #[wasm_bindgen(js_name = listModules)]
1096 pub fn list_modules(&self) -> String {
1097 let headers: Vec<serde_json::Value> = self
1098 .module_headers
1099 .values()
1100 .filter_map(|s| serde_json::from_str(s).ok())
1101 .collect();
1102 serde_json::to_string(&headers).unwrap_or_else(|_| "[]".to_string())
1103 }
1104
1105 #[wasm_bindgen(js_name = setVariable)]
1108 pub fn set_variable(&mut self, var_id: &str, value_json: &str) -> Result<(), JsValue> {
1109 let var_id: Uuid = var_id
1110 .parse()
1111 .map_err(|_| JsValue::from_str("bad var_id: not a valid UUID"))?;
1112 let value: Value = serde_json::from_str(value_json)
1113 .map_err(|e| JsValue::from_str(&format!("bad value JSON: {e}")))?;
1114 self.variables.borrow_mut().insert(var_id, value);
1115 Ok(())
1116 }
1117
1118 pub fn tick(&mut self, nodes_json: &str) -> Result<String, JsValue> {
1125 let nodes: Vec<BtNode> = serde_json::from_str(nodes_json)
1126 .map_err(|e| JsValue::from_str(&format!("bad nodes JSON: {e}")))?;
1127 if nodes.is_empty() {
1128 return Err(JsValue::from_str("tree has no nodes"));
1129 }
1130
1131 let root_id = nodes[0].id;
1132 let node_index: HashMap<Uuid, BtNode> = nodes.into_iter().map(|n| (n.id, n)).collect();
1133 let trace: Rc<RefCell<Vec<(Uuid, &'static str)>>> = Rc::new(RefCell::new(Vec::new()));
1134
1135 let fn_meta = &self.fn_meta;
1136 let root_callable_id = register_node(
1137 &mut *self.inner,
1138 root_id,
1139 &node_index,
1140 fn_meta,
1141 &trace,
1142 &self.variables,
1143 )
1144 .map_err(|e| JsValue::from_str(&format!("setup error: {e}")))?;
1145
1146 let callable_id = CallableId {
1147 id: root_callable_id,
1148 };
1149 let result = Callable::call(&callable_id, &mut *self.inner)
1150 .map_err(|e| JsValue::from_str(&format!("tick error: {e}")))?;
1151 let status = value_to_status(&result);
1152
1153 let trace_json: Vec<serde_json::Value> = trace
1154 .borrow()
1155 .iter()
1156 .map(|(id, s)| serde_json::json!({ "nodeId": id.to_string(), "status": s }))
1157 .collect();
1158
1159 let vars_json: serde_json::Map<String, serde_json::Value> = self
1160 .variables
1161 .borrow()
1162 .iter()
1163 .filter_map(|(id, v)| serde_json::to_value(v).ok().map(|jv| (id.to_string(), jv)))
1164 .collect();
1165
1166 Ok(serde_json::json!({
1167 "status": status,
1168 "trace": trace_json,
1169 "variables": vars_json,
1170 })
1171 .to_string())
1172 }
1173
1174 pub fn run(&mut self, nodes_json: &str) -> Result<String, JsValue> {
1184 let nodes: Vec<BtNode> = serde_json::from_str(nodes_json)
1185 .map_err(|e| JsValue::from_str(&format!("bad nodes JSON: {e}")))?;
1186 if nodes.is_empty() {
1187 return Err(JsValue::from_str("tree has no nodes"));
1188 }
1189
1190 let root_id = nodes[0].id;
1191 let node_index: HashMap<Uuid, BtNode> = nodes.into_iter().map(|n| (n.id, n)).collect();
1192 let trace: Rc<RefCell<Vec<(Uuid, &'static str)>>> = Rc::new(RefCell::new(Vec::new()));
1193
1194 let fn_meta = &self.fn_meta;
1195 let root_callable_id = register_node(
1196 &mut *self.inner,
1197 root_id,
1198 &node_index,
1199 fn_meta,
1200 &trace,
1201 &self.variables,
1202 )
1203 .map_err(|e| JsValue::from_str(&format!("setup error: {e}")))?;
1204
1205 let callable_id = CallableId {
1206 id: root_callable_id,
1207 };
1208
1209 let mut last_status = "running";
1210 for _ in 0..10_000 {
1211 let result = Callable::call(&callable_id, &mut *self.inner)
1212 .map_err(|e| JsValue::from_str(&format!("tick error: {e}")))?;
1213 last_status = value_to_status(&result);
1214 if last_status != "running" {
1215 break;
1216 }
1217 }
1218
1219 let trace_json: Vec<serde_json::Value> = trace
1220 .borrow()
1221 .iter()
1222 .map(|(id, s)| serde_json::json!({ "nodeId": id.to_string(), "status": s }))
1223 .collect();
1224
1225 let out = serde_json::json!({
1226 "status": last_status,
1227 "trace": trace_json,
1228 });
1229 Ok(out.to_string())
1230 }
1231}
1232
1233impl Default for BehaviorTreeRunner {
1234 fn default() -> Self {
1235 Self::new()
1236 }
1237}
1238
1239#[wasm_bindgen]
1248pub struct Registry {
1249 entries: HashMap<Uuid, (String, Option<Uuid>)>,
1250}
1251
1252#[derive(serde::Deserialize)]
1253struct RecordEntry {
1254 id: Uuid,
1255 name: String,
1256 parent: Option<Uuid>,
1257}
1258
1259#[wasm_bindgen]
1260impl Registry {
1261 #[wasm_bindgen(constructor)]
1262 pub fn new() -> Registry {
1263 Registry {
1264 entries: HashMap::new(),
1265 }
1266 }
1267
1268 #[wasm_bindgen(js_name = loadRecordsJson)]
1270 pub fn load_records_json(&mut self, json: &str) -> Result<(), JsValue> {
1271 let records: Vec<RecordEntry> = serde_json::from_str(json)
1272 .map_err(|e| JsValue::from_str(&format!("invalid records JSON: {e}")))?;
1273 for r in records {
1274 self.entries.insert(r.id, (r.name, r.parent));
1275 }
1276 Ok(())
1277 }
1278
1279 #[wasm_bindgen(js_name = resolveId)]
1282 pub fn resolve_id(&self, id: &str) -> Option<String> {
1283 let uuid: Uuid = id.parse().ok()?;
1284 self.compute_path(&uuid)
1285 }
1286
1287 fn compute_path(&self, id: &Uuid) -> Option<String> {
1288 let (name, parent) = self.entries.get(id)?;
1289 match parent {
1290 None => Some(name.clone()),
1291 Some(parent_id) if !self.entries.contains_key(parent_id) => Some(name.clone()),
1292 Some(parent_id) => Some(format!("{}.{}", self.compute_path(parent_id)?, name)),
1293 }
1294 }
1295}
1296
1297impl Default for Registry {
1298 fn default() -> Self {
1299 Self::new()
1300 }
1301}