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 #[wasm_bindgen(js_name = withCompiledModule)]
631 pub fn with_compiled_module(&mut self, module: &CompiledModule) {
632 let inner = std::mem::take(&mut self.inner);
633 self.inner = inner.with_compiled_module(&module.inner);
634 }
635
636 pub fn build(&mut self) -> Result<AroraWeb, JsValue> {
639 let arora = std::mem::take(&mut self.inner)
640 .build()
641 .map_err(|e| JsValue::from_str(&format!("arora build failed: {e:?}")))?;
642 Ok(AroraWeb::from(arora))
643 }
644}
645
646#[wasm_bindgen]
650pub struct CompiledModule {
651 inner: arora::CompiledModule,
652}
653
654#[wasm_bindgen]
655impl CompiledModule {
656 #[wasm_bindgen(constructor)]
662 pub fn new(header_json: &str, executable: &[u8]) -> Result<CompiledModule, JsValue> {
663 install_panic_hook();
664 let header: Header = serde_json::from_str(header_json)
665 .map_err(|e| JsValue::from_str(&format!("invalid header json: {e}")))?;
666 let inner = arora::CompiledModule::new(header, executable)
667 .map_err(|e| JsValue::from_str(&format!("module does not compile: {e}")))?;
668 Ok(CompiledModule { inner })
669 }
670}
671
672#[wasm_bindgen]
674pub struct Engine {
675 inner: std::pin::Pin<Box<arora_engine::engine::Engine>>,
676 loader: SharedLoaderRc,
677 function_module: HashMap<Uuid, Uuid>,
678 module_headers: HashMap<Uuid, String>,
679}
680
681#[wasm_bindgen]
682impl Engine {
683 #[wasm_bindgen(constructor)]
684 pub fn new() -> Engine {
685 install_panic_hook();
686 let executor = BrowserExecutor::new();
687 let loader = executor.shared();
688 let inner = EngineBuilder::new().add_executor(executor).build();
689 Engine {
690 inner,
691 loader,
692 function_module: HashMap::new(),
693 module_headers: HashMap::new(),
694 }
695 }
696
697 #[wasm_bindgen(js_name = loadModule)]
704 pub fn load_module(&mut self, header_json: &str, executable: &[u8]) -> Result<String, JsValue> {
705 self.load_module_inner(header_json, executable.to_vec().into_boxed_slice())
706 }
707
708 #[wasm_bindgen(js_name = prepareModule)]
712 pub fn prepare_module(&self, header_json: &str, executable: Vec<u8>) -> js_sys::Promise {
713 prepare_module_impl(self.loader.clone(), header_json, executable)
714 }
715
716 #[wasm_bindgen(js_name = loadPreparedModule)]
719 pub fn load_prepared_module(&mut self, header_json: &str) -> Result<String, JsValue> {
720 self.load_module_inner(header_json, Box::new([]))
721 }
722
723 fn load_module_inner(
724 &mut self,
725 header_json: &str,
726 executable: Box<[u8]>,
727 ) -> Result<String, JsValue> {
728 let header: Header = serde_json::from_str(header_json)
729 .map_err(|e| JsValue::from_str(&format!("invalid header json: {e}")))?;
730 let header_json_str = header_json.to_string();
731 let loaded = load_module_from_parts(&mut self.inner, header, executable)
732 .map_err(|e| JsValue::from_str(&format!("load_module failed: {e}")))?;
733 for fn_id in &loaded.function_ids {
734 self.function_module.insert(*fn_id, loaded.id);
735 }
736 self.module_headers.insert(loaded.id, header_json_str);
737 Ok(loaded.id.to_string())
738 }
739
740 #[wasm_bindgen(js_name = listModules)]
742 pub fn list_modules(&self) -> String {
743 let headers: Vec<serde_json::Value> = self
744 .module_headers
745 .values()
746 .filter_map(|s| serde_json::from_str(s).ok())
747 .collect();
748 serde_json::to_string(&headers).unwrap_or_else(|_| "[]".to_string())
749 }
750
751 #[wasm_bindgen]
754 pub fn call(&mut self, call_json: &str) -> Result<String, JsValue> {
755 let mut call: Call = serde_json::from_str(call_json)
756 .map_err(|e| JsValue::from_str(&format!("invalid call json: {e}")))?;
757 if call.module_id.is_none() {
758 call.module_id = Some(
760 *self
761 .function_module
762 .get(&call.id)
763 .ok_or_else(|| JsValue::from_str("no module known for function"))?,
764 );
765 }
766 let result = self
767 .inner
768 .arora_call(call)
769 .map_err(|e| JsValue::from_str(&format!("call failed: {e}")))?;
770 serde_json::to_string(&result)
771 .map_err(|e| JsValue::from_str(&format!("serialize failed: {e}")))
772 }
773}
774
775impl Default for Engine {
776 fn default() -> Self {
777 Self::new()
778 }
779}
780
781fn prepare_module_impl(
785 loader: SharedLoaderRc,
786 header_json: &str,
787 executable: Vec<u8>,
788) -> js_sys::Promise {
789 let header: Result<Header, _> = serde_json::from_str(header_json);
790 wasm_bindgen_futures::future_to_promise(async move {
791 let header = header.map_err(|e| JsValue::from_str(&format!("invalid header json: {e}")))?;
792 loader.prepare(header.id, executable).await?;
793 Ok(JsValue::UNDEFINED)
794 })
795}
796
797const TICK_ID_STRUCT_BYTES: [u8; 16] = [
807 0x6f, 0x49, 0xe6, 0x50, 0x84, 0xca, 0x48, 0x99, 0xa9, 0xbd, 0x1f, 0x3b, 0xf1, 0x7f, 0xab, 0x51,
808];
809const TICK_ID_CALLABLE_FIELD_BYTES: [u8; 16] = [
811 0x23, 0x79, 0x92, 0xd2, 0x17, 0xd1, 0x45, 0x9f, 0xbc, 0xa1, 0x71, 0x85, 0xfa, 0x6a, 0x69, 0xd7,
812];
813const STATUS_SUCCESS_BYTES: [u8; 16] = [
815 0x76, 0x6e, 0x9e, 0x9a, 0x44, 0x6d, 0x4e, 0x46, 0x83, 0xe6, 0x14, 0xb7, 0xca, 0x10, 0x11, 0x69,
816];
817const STATUS_FAILURE_BYTES: [u8; 16] = [
819 0x24, 0x68, 0xf4, 0x6c, 0xbb, 0x60, 0x42, 0x5c, 0x9a, 0x4d, 0x9a, 0xd3, 0x26, 0xcc, 0xc7, 0xe2,
820];
821const STATUS_ENUM_BYTES: [u8; 16] = [
823 0x32, 0x5a, 0x57, 0x67, 0xe3, 0x44, 0x45, 0x32, 0x86, 0x0e, 0x07, 0x49, 0xbc, 0xf2, 0xe4, 0x28,
824];
825const RET_PARAM_BYTES: [u8; 16] = [
827 0x5f, 0x72, 0x65, 0x74, 0x00, 0x00, 0x40, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
828];
829
830fn value_to_status(v: &Value) -> &'static str {
831 if let Value::Enumeration(e) = v {
832 if *e.variant_id.as_bytes() == STATUS_SUCCESS_BYTES {
833 return "success";
834 }
835 if *e.variant_id.as_bytes() == STATUS_FAILURE_BYTES {
836 return "failure";
837 }
838 }
839 "running"
840}
841
842#[derive(serde::Deserialize, Clone, Debug)]
845#[serde(rename_all = "snake_case")]
846enum BtExpression {
847 VariableId(Uuid),
848 Value(Value),
849}
850
851#[derive(serde::Deserialize, Debug)]
853struct BtNode {
854 id: Uuid,
855 function: Uuid,
856 #[serde(default)]
857 children: Option<Vec<Uuid>>,
858 #[serde(default)]
862 arguments: HashMap<Uuid, BtExpression>,
863}
864
865struct FnMeta {
867 module_id: Uuid,
868 children_param_id: Option<Uuid>,
871}
872
873struct NodeCallable {
875 node_id: Uuid,
876 fn_id: Uuid,
877 module_id: Uuid,
878 children_param_id: Option<Uuid>,
879 children_callable_ids: Vec<u64>,
880 arguments: HashMap<Uuid, BtExpression>,
881 variables: Rc<RefCell<HashMap<Uuid, Value>>>,
882 trace: Rc<RefCell<Vec<(Uuid, &'static str)>>>,
883}
884
885impl Callable for NodeCallable {
886 fn call(&self, caller: &mut dyn CallBridge) -> Result<Value, arora_engine::call::CallError> {
887 let tick_id_type = Uuid::from_bytes(TICK_ID_STRUCT_BYTES);
888 let callable_field = Uuid::from_bytes(TICK_ID_CALLABLE_FIELD_BYTES);
889 let ret_param_id = Uuid::from_bytes(RET_PARAM_BYTES);
890
891 let mut args = Vec::new();
892
893 if let Some(children_param_id) = self.children_param_id {
894 let elements: Vec<StructureWithoutId> = self
895 .children_callable_ids
896 .iter()
897 .map(|&id| StructureWithoutId {
898 fields: vec![StructureField {
899 id: callable_field,
900 value: Box::new(Value::U64(id)),
901 }],
902 })
903 .collect();
904 args.push(StructureField {
905 id: children_param_id,
906 value: Box::new(Value::ArrayStructure {
907 id: tick_id_type,
908 elements,
909 }),
910 });
911 }
912
913 for (¶m_id, expr) in &self.arguments {
914 if param_id == ret_param_id {
915 continue;
916 }
917 let value = match expr {
918 BtExpression::Value(v) => v.clone(),
919 BtExpression::VariableId(var_id) => self
920 .variables
921 .borrow()
922 .get(var_id)
923 .cloned()
924 .unwrap_or(Value::Unit),
925 };
926 args.push(StructureField {
927 id: param_id,
928 value: Box::new(value),
929 });
930 }
931
932 let mutable_param_vars: HashMap<Uuid, Uuid> = self
935 .arguments
936 .iter()
937 .filter_map(|(¶m_id, expr)| {
938 if param_id == ret_param_id {
939 return None;
940 }
941 if let BtExpression::VariableId(var_id) = expr {
942 Some((param_id, *var_id))
943 } else {
944 None
945 }
946 })
947 .collect();
948
949 let result = caller.arora_call(Call {
950 module_id: Some(self.module_id),
951 id: self.fn_id,
952 args,
953 })?;
954
955 for mutated in &result.mutated {
957 if let Some(&var_id) = mutable_param_vars.get(&mutated.id) {
958 self.variables
959 .borrow_mut()
960 .insert(var_id, *mutated.value.clone());
961 }
962 }
963
964 let has_ret = self.arguments.contains_key(&ret_param_id);
965 if has_ret {
966 if let Some(BtExpression::VariableId(var_id)) = self.arguments.get(&ret_param_id) {
967 self.variables
968 .borrow_mut()
969 .insert(*var_id, result.ret.clone());
970 }
971 }
972
973 let s = if has_ret {
974 "success"
975 } else {
976 value_to_status(&result.ret)
977 };
978 self.trace.borrow_mut().push((self.node_id, s));
979
980 if has_ret {
981 Ok(Value::Enumeration(Enumeration {
982 id: Uuid::from_bytes(STATUS_ENUM_BYTES),
983 variant_id: Uuid::from_bytes(STATUS_SUCCESS_BYTES),
984 value: Box::new(Value::Unit),
985 }))
986 } else {
987 Ok(result.ret)
988 }
989 }
990}
991
992fn register_node(
995 engine: &mut dyn CallBridge,
996 node_id: Uuid,
997 node_index: &HashMap<Uuid, BtNode>,
998 fn_meta: &HashMap<Uuid, FnMeta>,
999 trace: &Rc<RefCell<Vec<(Uuid, &'static str)>>>,
1000 variables: &Rc<RefCell<HashMap<Uuid, Value>>>,
1001) -> Result<u64, String> {
1002 let node = node_index
1003 .get(&node_id)
1004 .ok_or_else(|| format!("node {node_id} not found in tree"))?;
1005 let meta = fn_meta
1006 .get(&node.function)
1007 .ok_or_else(|| format!("function {} not registered in fn_meta", node.function))?;
1008
1009 let children_callable_ids = match &node.children {
1010 None => vec![],
1011 Some(ids) => ids
1012 .iter()
1013 .map(|&child_id| register_node(engine, child_id, node_index, fn_meta, trace, variables))
1014 .collect::<Result<Vec<_>, _>>()?,
1015 };
1016
1017 let callable: Rc<dyn Callable> = Rc::new(NodeCallable {
1018 node_id,
1019 fn_id: node.function,
1020 module_id: meta.module_id,
1021 children_param_id: meta.children_param_id,
1022 children_callable_ids,
1023 arguments: node.arguments.clone(),
1024 variables: variables.clone(),
1025 trace: trace.clone(),
1026 });
1027 let id = engine.arora_register_callable(callable);
1028 Ok(id.id)
1029}
1030
1031#[wasm_bindgen]
1040pub struct BehaviorTreeRunner {
1041 inner: Pin<Box<arora_engine::engine::Engine>>,
1042 loader: SharedLoaderRc,
1043 fn_meta: HashMap<Uuid, FnMeta>,
1044 variables: Rc<RefCell<HashMap<Uuid, Value>>>,
1045 module_headers: HashMap<Uuid, String>,
1046}
1047
1048#[wasm_bindgen]
1049impl BehaviorTreeRunner {
1050 #[wasm_bindgen(constructor)]
1051 pub fn new() -> BehaviorTreeRunner {
1052 install_panic_hook();
1053 let executor = BrowserExecutor::new();
1054 let loader = executor.shared();
1055 let inner = EngineBuilder::new().add_executor(executor).build();
1056 BehaviorTreeRunner {
1057 inner,
1058 loader,
1059 fn_meta: HashMap::new(),
1060 variables: Rc::new(RefCell::new(HashMap::new())),
1061 module_headers: HashMap::new(),
1062 }
1063 }
1064
1065 #[wasm_bindgen(js_name = loadModule)]
1073 pub fn load_module(&mut self, header_json: &str, executable: &[u8]) -> Result<String, JsValue> {
1074 self.load_module_inner(header_json, executable.to_vec().into_boxed_slice())
1075 }
1076
1077 #[wasm_bindgen(js_name = prepareModule)]
1081 pub fn prepare_module(&self, header_json: &str, executable: Vec<u8>) -> js_sys::Promise {
1082 prepare_module_impl(self.loader.clone(), header_json, executable)
1083 }
1084
1085 #[wasm_bindgen(js_name = loadPreparedModule)]
1088 pub fn load_prepared_module(&mut self, header_json: &str) -> Result<String, JsValue> {
1089 self.load_module_inner(header_json, Box::new([]))
1090 }
1091
1092 fn load_module_inner(
1093 &mut self,
1094 header_json: &str,
1095 executable: Box<[u8]>,
1096 ) -> Result<String, JsValue> {
1097 let header: Header = serde_json::from_str(header_json)
1098 .map_err(|e| JsValue::from_str(&format!("invalid header: {e}")))?;
1099 let module_id = header.id;
1100 let header_json_str = header_json.to_string();
1101
1102 for export in &header.exports {
1103 let arora_types::module::low::ExportSymbol::Function(f) = export;
1104 let children_param_id = f.parameters.first().and_then(|p| {
1105 if let arora_types::module::low::TypeRef::Array { id } = &p.ty {
1106 if id == &Uuid::from_bytes(TICK_ID_STRUCT_BYTES) {
1107 Some(p.id)
1108 } else {
1109 None
1110 }
1111 } else {
1112 None
1113 }
1114 });
1115
1116 self.fn_meta.insert(
1117 f.id,
1118 FnMeta {
1119 module_id,
1120 children_param_id,
1121 },
1122 );
1123 }
1124
1125 let result = load_module_from_parts(&mut self.inner, header, executable)
1126 .map_err(|e| JsValue::from_str(&format!("load failed: {e}")))?;
1127 self.module_headers.insert(result.id, header_json_str);
1128 Ok(result.id.to_string())
1129 }
1130
1131 #[wasm_bindgen(js_name = listModules)]
1133 pub fn list_modules(&self) -> String {
1134 let headers: Vec<serde_json::Value> = self
1135 .module_headers
1136 .values()
1137 .filter_map(|s| serde_json::from_str(s).ok())
1138 .collect();
1139 serde_json::to_string(&headers).unwrap_or_else(|_| "[]".to_string())
1140 }
1141
1142 #[wasm_bindgen(js_name = setVariable)]
1145 pub fn set_variable(&mut self, var_id: &str, value_json: &str) -> Result<(), JsValue> {
1146 let var_id: Uuid = var_id
1147 .parse()
1148 .map_err(|_| JsValue::from_str("bad var_id: not a valid UUID"))?;
1149 let value: Value = serde_json::from_str(value_json)
1150 .map_err(|e| JsValue::from_str(&format!("bad value JSON: {e}")))?;
1151 self.variables.borrow_mut().insert(var_id, value);
1152 Ok(())
1153 }
1154
1155 pub fn tick(&mut self, nodes_json: &str) -> Result<String, JsValue> {
1162 let nodes: Vec<BtNode> = serde_json::from_str(nodes_json)
1163 .map_err(|e| JsValue::from_str(&format!("bad nodes JSON: {e}")))?;
1164 if nodes.is_empty() {
1165 return Err(JsValue::from_str("tree has no nodes"));
1166 }
1167
1168 let root_id = nodes[0].id;
1169 let node_index: HashMap<Uuid, BtNode> = nodes.into_iter().map(|n| (n.id, n)).collect();
1170 let trace: Rc<RefCell<Vec<(Uuid, &'static str)>>> = Rc::new(RefCell::new(Vec::new()));
1171
1172 let fn_meta = &self.fn_meta;
1173 let root_callable_id = register_node(
1174 &mut *self.inner,
1175 root_id,
1176 &node_index,
1177 fn_meta,
1178 &trace,
1179 &self.variables,
1180 )
1181 .map_err(|e| JsValue::from_str(&format!("setup error: {e}")))?;
1182
1183 let callable_id = CallableId {
1184 id: root_callable_id,
1185 };
1186 let result = Callable::call(&callable_id, &mut *self.inner)
1187 .map_err(|e| JsValue::from_str(&format!("tick error: {e}")))?;
1188 let status = value_to_status(&result);
1189
1190 let trace_json: Vec<serde_json::Value> = trace
1191 .borrow()
1192 .iter()
1193 .map(|(id, s)| serde_json::json!({ "nodeId": id.to_string(), "status": s }))
1194 .collect();
1195
1196 let vars_json: serde_json::Map<String, serde_json::Value> = self
1197 .variables
1198 .borrow()
1199 .iter()
1200 .filter_map(|(id, v)| serde_json::to_value(v).ok().map(|jv| (id.to_string(), jv)))
1201 .collect();
1202
1203 Ok(serde_json::json!({
1204 "status": status,
1205 "trace": trace_json,
1206 "variables": vars_json,
1207 })
1208 .to_string())
1209 }
1210
1211 pub fn run(&mut self, nodes_json: &str) -> Result<String, JsValue> {
1221 let nodes: Vec<BtNode> = serde_json::from_str(nodes_json)
1222 .map_err(|e| JsValue::from_str(&format!("bad nodes JSON: {e}")))?;
1223 if nodes.is_empty() {
1224 return Err(JsValue::from_str("tree has no nodes"));
1225 }
1226
1227 let root_id = nodes[0].id;
1228 let node_index: HashMap<Uuid, BtNode> = nodes.into_iter().map(|n| (n.id, n)).collect();
1229 let trace: Rc<RefCell<Vec<(Uuid, &'static str)>>> = Rc::new(RefCell::new(Vec::new()));
1230
1231 let fn_meta = &self.fn_meta;
1232 let root_callable_id = register_node(
1233 &mut *self.inner,
1234 root_id,
1235 &node_index,
1236 fn_meta,
1237 &trace,
1238 &self.variables,
1239 )
1240 .map_err(|e| JsValue::from_str(&format!("setup error: {e}")))?;
1241
1242 let callable_id = CallableId {
1243 id: root_callable_id,
1244 };
1245
1246 let mut last_status = "running";
1247 for _ in 0..10_000 {
1248 let result = Callable::call(&callable_id, &mut *self.inner)
1249 .map_err(|e| JsValue::from_str(&format!("tick error: {e}")))?;
1250 last_status = value_to_status(&result);
1251 if last_status != "running" {
1252 break;
1253 }
1254 }
1255
1256 let trace_json: Vec<serde_json::Value> = trace
1257 .borrow()
1258 .iter()
1259 .map(|(id, s)| serde_json::json!({ "nodeId": id.to_string(), "status": s }))
1260 .collect();
1261
1262 let out = serde_json::json!({
1263 "status": last_status,
1264 "trace": trace_json,
1265 });
1266 Ok(out.to_string())
1267 }
1268}
1269
1270impl Default for BehaviorTreeRunner {
1271 fn default() -> Self {
1272 Self::new()
1273 }
1274}
1275
1276#[wasm_bindgen]
1285pub struct Registry {
1286 entries: HashMap<Uuid, (String, Option<Uuid>)>,
1287}
1288
1289#[derive(serde::Deserialize)]
1290struct RecordEntry {
1291 id: Uuid,
1292 name: String,
1293 parent: Option<Uuid>,
1294}
1295
1296#[wasm_bindgen]
1297impl Registry {
1298 #[wasm_bindgen(constructor)]
1299 pub fn new() -> Registry {
1300 Registry {
1301 entries: HashMap::new(),
1302 }
1303 }
1304
1305 #[wasm_bindgen(js_name = loadRecordsJson)]
1307 pub fn load_records_json(&mut self, json: &str) -> Result<(), JsValue> {
1308 let records: Vec<RecordEntry> = serde_json::from_str(json)
1309 .map_err(|e| JsValue::from_str(&format!("invalid records JSON: {e}")))?;
1310 for r in records {
1311 self.entries.insert(r.id, (r.name, r.parent));
1312 }
1313 Ok(())
1314 }
1315
1316 #[wasm_bindgen(js_name = resolveId)]
1319 pub fn resolve_id(&self, id: &str) -> Option<String> {
1320 let uuid: Uuid = id.parse().ok()?;
1321 self.compute_path(&uuid)
1322 }
1323
1324 fn compute_path(&self, id: &Uuid) -> Option<String> {
1325 let (name, parent) = self.entries.get(id)?;
1326 match parent {
1327 None => Some(name.clone()),
1328 Some(parent_id) if !self.entries.contains_key(parent_id) => Some(name.clone()),
1329 Some(parent_id) => Some(format!("{}.{}", self.compute_path(parent_id)?, name)),
1330 }
1331 }
1332}
1333
1334impl Default for Registry {
1335 fn default() -> Self {
1336 Self::new()
1337 }
1338}