From 305425dd061cbc8e909dcfe7d21ac4b8792114da Mon Sep 17 00:00:00 2001 From: Zicklag Date: Tue, 16 Aug 2022 09:29:11 -0500 Subject: [PATCH] Cleanup Browser Runtime Code --- assets/scripts/headless.ts | 2 +- examples/headless.rs | 26 -- src/runtime/wasm/ecs/info.rs | 66 ++++ src/runtime/wasm/ecs/mod.rs | 5 + src/runtime/wasm/ecs/query.rs | 49 +++ src/runtime/wasm/ecs/resource.rs | 35 +++ src/runtime/wasm/ecs/value.rs | 304 +++++++++++++++++++ src/runtime/wasm/log.rs | 31 ++ src/runtime/wasm/mod.rs | 496 ++++--------------------------- 9 files changed, 543 insertions(+), 471 deletions(-) create mode 100644 src/runtime/wasm/ecs/info.rs create mode 100644 src/runtime/wasm/ecs/mod.rs create mode 100644 src/runtime/wasm/ecs/query.rs create mode 100644 src/runtime/wasm/ecs/resource.rs create mode 100644 src/runtime/wasm/ecs/value.rs create mode 100644 src/runtime/wasm/log.rs diff --git a/assets/scripts/headless.ts b/assets/scripts/headless.ts index c7aad77..07105c7 100644 --- a/assets/scripts/headless.ts +++ b/assets/scripts/headless.ts @@ -23,7 +23,7 @@ function run() { // info(world.resources.map(info => info.name)); // info("Resources (headless): " + filterComponentInfos(world.resources, "headless::").join(", ")); // info("Entitites: " + (world.entities.map(entity => `Entity(${entity.id}v${entity.generation})`).join(", "))); - // info("----------"); + info("----------"); let transformId = componentId( "bevy_transform::components::transform::Transform" diff --git a/examples/headless.rs b/examples/headless.rs index 5a261ee..f7a1cb5 100644 --- a/examples/headless.rs +++ b/examples/headless.rs @@ -24,33 +24,7 @@ fn main() { .add_startup_system(setup) .add_js_system("scripts/headless.ts") .register_type::() - .register_type::() - .insert_resource(Scoreboard::default()) .run(); -} - -// This resource tracks the game's score -#[derive(Reflect)] -struct Scoreboard { - score: usize, - extra: ExtraData, -} - -#[derive(Reflect)] -struct ExtraData { - name: String, -} - -impl Default for Scoreboard { - fn default() -> Self { - Self { - score: 7, - extra: ExtraData { - name: "Awesome".into(), - }, - } - } -} #[derive(Component, Reflect)] struct TestComponent { diff --git a/src/runtime/wasm/ecs/info.rs b/src/runtime/wasm/ecs/info.rs new file mode 100644 index 0000000..ade0c68 --- /dev/null +++ b/src/runtime/wasm/ecs/info.rs @@ -0,0 +1,66 @@ +use bevy::{ecs::component::ComponentId, prelude::*, utils::HashSet}; +use wasm_bindgen::prelude::*; + +use crate::runtime::{ + types::{JsComponentInfo, JsEntity}, + wasm::{BevyModJsScripting, WORLD_RID}, +}; + +#[wasm_bindgen] +impl BevyModJsScripting { + pub fn op_world_tostring(&self, rid: u32) -> String { + assert_eq!(rid, WORLD_RID); + let state = self.state(); + let world = &state.world; + + format!("{world:?}") + } + + pub fn op_world_components(&self, rid: u32) -> Result { + assert_eq!(rid, WORLD_RID); + let state = self.state(); + let world = &state.world; + + let resource_components: HashSet = + world.archetypes().resource().components().collect(); + + let infos = world + .components() + .iter() + .filter(|info| !resource_components.contains(&info.id())) + .map(JsComponentInfo::from) + .collect::>(); + + Ok(serde_wasm_bindgen::to_value(&infos)?) + } + + pub fn op_world_resources(&self, rid: u32) -> Result { + assert_eq!(rid, WORLD_RID); + let state = self.state(); + let world = &state.world; + + let infos = world + .archetypes() + .resource() + .components() + .map(|id| world.components().get_info(id).unwrap()) + .map(JsComponentInfo::from) + .collect::>(); + + Ok(serde_wasm_bindgen::to_value(&infos)?) + } + + pub fn op_world_entities(&self, rid: u32) -> Result { + assert_eq!(rid, WORLD_RID); + let mut state = self.state(); + let world = &mut state.world; + + let entities = world + .query::() + .iter(world) + .map(JsEntity::from) + .collect::>(); + + Ok(serde_wasm_bindgen::to_value(&entities)?) + } +} diff --git a/src/runtime/wasm/ecs/mod.rs b/src/runtime/wasm/ecs/mod.rs new file mode 100644 index 0000000..b2bd751 --- /dev/null +++ b/src/runtime/wasm/ecs/mod.rs @@ -0,0 +1,5 @@ + +mod info; +mod query; +mod resource; +mod value; diff --git a/src/runtime/wasm/ecs/query.rs b/src/runtime/wasm/ecs/query.rs new file mode 100644 index 0000000..008f65d --- /dev/null +++ b/src/runtime/wasm/ecs/query.rs @@ -0,0 +1,49 @@ +use bevy::ecs::component::ComponentId; +use bevy_ecs_dynamic::reflect_value_ref::{query::EcsValueRefQuery, ReflectValueRef}; +use wasm_bindgen::prelude::*; + +use crate::runtime::{ + types::QueryDescriptor, + wasm::{BevyModJsScripting, JsQueryItem, JsRuntimeState, JsValueRef, WORLD_RID}, +}; + +#[wasm_bindgen] +impl BevyModJsScripting { + pub fn op_world_query(&self, rid: u32, query: JsValue) -> Result { + assert_eq!(rid, WORLD_RID); + let mut state = self.state(); + let JsRuntimeState { + world, value_refs, .. + } = &mut *state; + + let descriptor: QueryDescriptor = serde_wasm_bindgen::from_value(query)?; + + let components: Vec = descriptor + .components + .iter() + .map(ComponentId::from) + .collect(); + + let mut query = EcsValueRefQuery::new(world, &components); + let results = query + .iter(world) + .map(|item| { + let components = item + .items + .into_iter() + .map(|value| JsValueRef { + key: value_refs.insert(ReflectValueRef::ecs_ref(value)), + function: None, + }) + .collect(); + + JsQueryItem { + entity: item.entity.into(), + components, + } + }) + .collect::>(); + + Ok(serde_wasm_bindgen::to_value(&results)?) + } +} diff --git a/src/runtime/wasm/ecs/resource.rs b/src/runtime/wasm/ecs/resource.rs new file mode 100644 index 0000000..5fe1cb4 --- /dev/null +++ b/src/runtime/wasm/ecs/resource.rs @@ -0,0 +1,35 @@ +use bevy_ecs_dynamic::reflect_value_ref::{EcsValueRef, ReflectValueRef}; +use wasm_bindgen::prelude::*; + +use crate::runtime::{ + types::ComponentIdOrBevyType, + wasm::{BevyModJsScripting, JsRuntimeState, JsValueRef, WORLD_RID}, + ToJsErr, +}; + +#[wasm_bindgen] +impl BevyModJsScripting { + pub fn op_world_get_resource( + &self, + rid: u32, + component_id: JsValue, + ) -> Result { + assert_eq!(rid, WORLD_RID); + let mut state = self.state(); + let JsRuntimeState { + world, value_refs, .. + } = &mut *state; + + let component_id: ComponentIdOrBevyType = serde_wasm_bindgen::from_value(component_id)?; + let component_id = component_id.component_id(world).to_js_error()?; + + let value_ref = EcsValueRef::resource(world, component_id).to_js_error()?; + + let value_ref = JsValueRef { + key: value_refs.insert(ReflectValueRef::ecs_ref(value_ref)), + function: None, + }; + + Ok(serde_wasm_bindgen::to_value(&value_ref)?) + } +} diff --git a/src/runtime/wasm/ecs/value.rs b/src/runtime/wasm/ecs/value.rs new file mode 100644 index 0000000..e70a341 --- /dev/null +++ b/src/runtime/wasm/ecs/value.rs @@ -0,0 +1,304 @@ +use std::{any::TypeId, cell::RefCell, rc::Rc}; + +use bevy_ecs_dynamic::reflect_value_ref::ReflectValueRef; +use bevy_reflect::{ReflectRef, TypeRegistryArc}; +use bevy_reflect_fns::{PassMode, ReflectArg, ReflectMethods}; +use wasm_bindgen::{prelude::*, JsCast}; + +use crate::runtime::{ + types::{Primitive, ReflectArgIntermediate, ReflectArgIntermediateValue}, + wasm::{ + BevyModJsScripting, GetReflectValueRef, JsRuntimeState, JsValueRef, REF_NOT_EXIST, + WORLD_RID, + }, + ToJsErr, +}; + +macro_rules! try_downcast_leaf_get { + ($value:ident for $($ty:ty $(,)?),*) => { + $(if let Some(value) = $value.downcast_ref::<$ty>() { + let value = serde_wasm_bindgen::to_value(value)?; + return Ok(value); + })* + }; +} + +macro_rules! try_downcast_leaf_set { + ($value:ident <- $new_value:ident for $($ty:ty $(,)?),*) => { + $(if let Some(value) = $value.downcast_mut::<$ty>() { + *value = serde_wasm_bindgen::from_value($new_value)?; + return Ok(()); + })* + }; +} + +#[wasm_bindgen] +impl BevyModJsScripting { + pub fn op_value_ref_get( + &self, + rid: u32, + value_ref: JsValue, + path: &str, + ) -> Result { + assert_eq!(rid, WORLD_RID); + let mut state = self.state(); + let JsRuntimeState { + world, + value_refs, + reflect_functions, + .. + } = &mut *state; + + // Get the reflect value ref from the JS argument + let value_ref = value_refs.get_reflect_value_ref(value_ref)?; + + // Load the type registry + let type_registry = world.resource::(); + let type_registry = type_registry.read(); + + // See if we can find any reflect methods for this type in the type registry + let reflect_methods = type_registry + .get_type_data::(value_ref.get(world).to_js_error()?.type_id()); + + // If we found methods for this type + if let Some(reflect_methods) = reflect_methods { + let method_name = path.trim_start_matches('.'); + // If the path we are accessing is a method on the type + if let Some(reflect_function) = reflect_methods.get(method_name.trim_start_matches('.')) + { + // Return a method reference + let value = JsValueRef { + key: value_refs.insert(value_ref.clone()), + function: Some(reflect_functions.insert(reflect_function.clone())), + }; + + return Ok(serde_wasm_bindgen::to_value(&value)?); + } + } + + // If we didn't find a method, add the path to our value ref + let value_ref = value_ref.append_path(path, world).to_js_error()?; + + // Try to downcast the value to a primitive + { + let value = value_ref.get(world).to_js_error()?; + + try_downcast_leaf_get!(value for + u8, u16, u32, u64, u128, usize, + i8, i16, i32, i64, i128, isize, + String, char, bool, f32, f64 + ); + } + + // If not a primitive, just return a new value ref + let object = JsValueRef { + key: value_refs.insert(value_ref), + function: None, + }; + + Ok(serde_wasm_bindgen::to_value(&object)?) + } + + pub fn op_value_ref_set( + &self, + rid: u32, + value_ref: JsValue, + path: &str, + new_value: JsValue, + ) -> Result<(), JsValue> { + assert_eq!(rid, WORLD_RID); + let mut state = self.state(); + let JsRuntimeState { + world, value_refs, .. + } = &mut *state; + + // Get the value ref from the JS arg + let value_ref = value_refs.get_reflect_value_ref(value_ref)?; + + // Access the provided path on the value ref + let mut value_ref = value_ref.append_path(path, world).unwrap(); + + // Get the reflect value + let mut reflect = value_ref.get_mut(world).unwrap(); + + // Try to store a primitive in the value + try_downcast_leaf_set!(reflect <- new_value for + u8, u16, u32, u64, u128, usize, + i8, i16, i32, i64, i128, isize, + String, char, bool, f32, f64 + ); + + Err(JsValue::from_str(&format!( + "could not set value reference: type `{}` is not a primitive type", + reflect.type_name(), + ))) + } + + pub fn op_value_ref_keys(&self, rid: u32, value_ref: JsValue) -> Result { + assert_eq!(rid, WORLD_RID); + let mut state = self.state(); + let JsRuntimeState { + world, value_refs, .. + } = &mut *state; + + // Get the reflect ref from the JS arg + let value_ref = value_refs.get_reflect_value_ref(value_ref)?; + let reflect = value_ref.get(world).unwrap(); + + // Enumerate the fields of the reflected object + let fields = match reflect.reflect_ref() { + ReflectRef::Struct(s) => (0..s.field_len()) + .map(|i| { + let name = s.name_at(i).ok_or_else(|| { + JsValue::from_str(&format!( + "misbehaving Reflect impl on `{}`", + s.type_name() + )) + })?; + Ok(name.to_owned()) + }) + .collect::>()?, + ReflectRef::Tuple(tuple) => (0..tuple.field_len()).map(|i| i.to_string()).collect(), + ReflectRef::TupleStruct(tuple_struct) => (0..tuple_struct.field_len()) + .map(|i| i.to_string()) + .collect(), + _ => Vec::new(), + }; + + Ok(serde_wasm_bindgen::to_value(&fields)?) + } + + pub fn op_value_ref_to_string(&self, rid: u32, value_ref: JsValue) -> Result { + assert_eq!(rid, WORLD_RID); + let mut state = self.state(); + let JsRuntimeState { + world, value_refs, .. + } = &mut *state; + + // Get the value ref from JS arg + let value_ref = value_refs.get_reflect_value_ref(value_ref)?; + let reflect = value_ref.get(world).unwrap(); + + // Return the debug formatted string for the reflected object + Ok(JsValue::from_str(&format!("{reflect:?}"))) + } + + pub fn op_value_ref_call( + &self, + rid: u32, + receiver: JsValue, + args: JsValue, + ) -> Result { + assert_eq!(rid, WORLD_RID); + let mut state = self.state(); + let JsRuntimeState { + world, + value_refs, + reflect_functions, + .. + } = &mut *state; + + // Deserialize the receiver + let receiver: JsValueRef = serde_wasm_bindgen::from_value(receiver)?; + + // Get the receiver's reflect_function + let method_key = receiver + .function + .ok_or("Cannot call non-function ref") + .to_js_error()?; + let method = reflect_functions + .get_mut(method_key) + .ok_or(REF_NOT_EXIST) + .to_js_error()?; + + // Get the receiver's reflect ref + let receiver = value_refs + .get(receiver.key) + .ok_or(REF_NOT_EXIST) + .to_js_error()?; + + // Cast the argumetn list to a JS array + let args: js_sys::Array = args + .dyn_into() + .map_err(|_| "Argument list not an array") + .to_js_error()?; + + // Collect the receiver intermediate value + let receiver_pass_mode = method.signature[0].0; + let receiver_intermediate = match receiver_pass_mode { + PassMode::Ref => ReflectArgIntermediateValue::Ref(receiver.get(world).unwrap()), + PassMode::RefMut => { + unimplemented!("values passed by mutable reference in reflect fn call") + } + PassMode::Owned => ReflectArgIntermediateValue::Owned(receiver.get(world).unwrap()), + }; + let mut receiver_intermediate = ReflectArgIntermediate::Value(receiver_intermediate); + + // Collect the intermediate values for the arguments + let mut arg_intermediates = args + .iter() + .zip(method.signature.iter().skip(1)) + .map(|(arg, &(pass_mode, type_id))| { + // Try to cast the arg as a primitive + let downcast_primitive = match type_id { + type_id if type_id == TypeId::of::() => { + Some(Primitive::f32(serde_wasm_bindgen::from_value(arg.clone())?)) + } + type_id if type_id == TypeId::of::() => { + Some(Primitive::f64(serde_wasm_bindgen::from_value(arg.clone())?)) + } + type_id if type_id == TypeId::of::() => { + Some(Primitive::i32(serde_wasm_bindgen::from_value(arg.clone())?)) + } + type_id if type_id == TypeId::of::() => { + Some(Primitive::u32(serde_wasm_bindgen::from_value(arg.clone())?)) + } + _ => None, + }; + // If the arg cast worked, return a primitive arg + if let Some(primitive) = downcast_primitive { + return Ok(ReflectArgIntermediate::Primitive(primitive, pass_mode)); + } + + // Otherwise, try get the arg as a value ref + let value_ref = value_refs.get_reflect_value_ref(arg)?; + let value_ref = match pass_mode { + PassMode::Ref => ReflectArgIntermediateValue::Ref(value_ref.get(world).unwrap()), + PassMode::RefMut => { + unimplemented!("values passed by mutable reference in reflect fn call") + } + PassMode::Owned => { + ReflectArgIntermediateValue::Owned(value_ref.get(world).unwrap()) + } + }; + + Ok(ReflectArgIntermediate::Value(value_ref)) + }) + .collect::, JsValue>>()?; + + // Collect references to our intermediates as [`ReflectArg`]s + let mut args: Vec = std::iter::once(&mut receiver_intermediate) + .chain(arg_intermediates.iter_mut()) + .map(|intermediate| intermediate.as_arg()) + .collect(); + + // Finally call the method + let ret = method.call(args.as_mut_slice()).unwrap(); + // And package it's return value as a standalone reflect ref + let ret = Rc::new(RefCell::new(ret)); + let ret = ReflectValueRef::free(ret); + + // Drop our intermediates and args so that we can use `value_refs` again, below. + drop(args); + drop(arg_intermediates); + drop(receiver_intermediate); + + // Return our resulting value ref + let ret = JsValueRef { + key: value_refs.insert(ret), + function: None, + }; + + Ok(serde_wasm_bindgen::to_value(&ret)?) + } +} diff --git a/src/runtime/wasm/log.rs b/src/runtime/wasm/log.rs new file mode 100644 index 0000000..93bbdd0 --- /dev/null +++ b/src/runtime/wasm/log.rs @@ -0,0 +1,31 @@ +use wasm_bindgen::prelude::*; + +use super::{BevyModJsScripting, LOCK_SHOULD_NOT_FAIL}; + +use bevy::utils::tracing::{event, span, Level}; + +#[wasm_bindgen] +impl BevyModJsScripting { + pub fn op_log(&self, level: &str, text: &str) { + let state = self.state.try_lock().expect(LOCK_SHOULD_NOT_FAIL); + let path = &state.current_script_path; + + let level: Level = level.parse().unwrap_or(Level::INFO); + if level == Level::TRACE { + let _span = span!(Level::TRACE, "script", ?path).entered(); + event!(target: "js_runtime", Level::TRACE, "{text}"); + } else if level == Level::DEBUG { + let _span = span!(Level::DEBUG, "script", ?path).entered(); + event!(target: "js_runtime", Level::DEBUG, "{text}"); + } else if level == Level::INFO { + let _span = span!(Level::INFO, "script", ?path).entered(); + event!(target: "js_runtime", Level::INFO, "{text}"); + } else if level == Level::WARN { + let _span = span!(Level::WARN, "script", ?path).entered(); + event!(target: "js_runtime", Level::WARN, "{text}"); + } else if level == Level::ERROR { + let _span = span!(Level::ERROR, "script", ?path).entered(); + event!(target: "js_runtime", Level::ERROR, "{text}"); + } + } +} diff --git a/src/runtime/wasm/mod.rs b/src/runtime/wasm/mod.rs index 7c8d2ac..7c2a3ca 100644 --- a/src/runtime/wasm/mod.rs +++ b/src/runtime/wasm/mod.rs @@ -1,33 +1,32 @@ -use std::any::TypeId; -use std::cell::RefCell; +mod ecs; +mod log; + use std::path::PathBuf; use std::rc::Rc; -use bevy::ecs::component::ComponentId; -use bevy::utils::{HashMap, HashSet}; -use bevy::{ - prelude::*, - utils::tracing::{event, span, Level}, -}; -use bevy_ecs_dynamic::reflect_value_ref::query::EcsValueRefQuery; -use bevy_ecs_dynamic::reflect_value_ref::{EcsValueRef, ReflectValueRef}; -use bevy_reflect::{ReflectRef, TypeRegistryArc}; -use bevy_reflect_fns::{PassMode, ReflectArg, ReflectFunction}; +use bevy::prelude::*; +use bevy::utils::HashMap; +use bevy_ecs_dynamic::reflect_value_ref::ReflectValueRef; +use bevy_reflect_fns::ReflectFunction; use serde::{Deserialize, Serialize}; use slotmap::SlotMap; use wasm_bindgen::{prelude::*, JsCast}; -use wasm_mutex::Mutex; +use wasm_mutex::{Mutex, MutexRef}; use super::JsRuntimeApi; use crate::asset::JsScript; -use crate::runtime::types::{ - ComponentIdOrBevyType, JsComponentInfo, JsEntity, Primitive, QueryDescriptor, - ReflectArgIntermediate, ReflectArgIntermediateValue, -}; +use crate::runtime::types::JsEntity; +/// Panic message when a mutex lock fails const LOCK_SHOULD_NOT_FAIL: &str = "Mutex lock should not fail because there should be no concurrent access"; +/// Error message thrown when a value ref refers to a value that doesn't exist. +const REF_NOT_EXIST: &str = + "Value referenced does not exist. Each value ref is only valid for the duration of the script \ + execution that it was created in. You may have attempted to use a value from a previous sciprt \ + run."; + const WORLD_RID: u32 = 0; slotmap::new_key_type! { @@ -52,6 +51,13 @@ struct BevyModJsScripting { state: Rc>, } +impl BevyModJsScripting { + /// Lock the state and panic if the lock cannot be obtained immediately. + fn state(&self) -> MutexRef { + self.state.try_lock().expect(LOCK_SHOULD_NOT_FAIL) + } +} + #[derive(Default)] struct JsRuntimeState { current_script_path: PathBuf, @@ -60,433 +66,6 @@ struct JsRuntimeState { reflect_functions: SlotMap, } -macro_rules! try_downcast_leaf_get { - ($value:ident for $($ty:ty $(,)?),*) => { - $(if let Some(value) = $value.downcast_ref::<$ty>() { - let value = serde_wasm_bindgen::to_value(value)?; - return Ok(value); - })* - }; -} - -macro_rules! try_downcast_leaf_set { - ($value:ident <- $new_value:ident for $($ty:ty $(,)?),*) => { - $(if let Some(value) = $value.downcast_mut::<$ty>() { - *value = serde_wasm_bindgen::from_value($new_value)?; - return Ok(()); - })* - }; -} - -#[wasm_bindgen] -impl BevyModJsScripting { - pub fn op_log(&self, level: &str, text: &str) { - let state = self.state.try_lock().expect(LOCK_SHOULD_NOT_FAIL); - let path = &state.current_script_path; - - let level: Level = level.parse().unwrap_or(Level::INFO); - if level == Level::TRACE { - let _span = span!(Level::TRACE, "script", ?path).entered(); - event!(target: "js_runtime", Level::TRACE, "{text}"); - } else if level == Level::DEBUG { - let _span = span!(Level::DEBUG, "script", ?path).entered(); - event!(target: "js_runtime", Level::DEBUG, "{text}"); - } else if level == Level::INFO { - let _span = span!(Level::INFO, "script", ?path).entered(); - event!(target: "js_runtime", Level::INFO, "{text}"); - } else if level == Level::WARN { - let _span = span!(Level::WARN, "script", ?path).entered(); - event!(target: "js_runtime", Level::WARN, "{text}"); - } else if level == Level::ERROR { - let _span = span!(Level::ERROR, "script", ?path).entered(); - event!(target: "js_runtime", Level::ERROR, "{text}"); - } - } - - pub fn op_world_tostring(&self, rid: u32) -> String { - assert_eq!(rid, WORLD_RID); - let state = self.state.try_lock().expect(LOCK_SHOULD_NOT_FAIL); - let world = &state.world; - - format!("{world:?}") - } - - pub fn op_world_components(&self, rid: u32) -> JsValue { - assert_eq!(rid, WORLD_RID); - let state = self.state.try_lock().expect(LOCK_SHOULD_NOT_FAIL); - let world = &state.world; - - let resource_components: HashSet = - world.archetypes().resource().components().collect(); - - let infos = world - .components() - .iter() - .filter(|info| !resource_components.contains(&info.id())) - .map(JsComponentInfo::from) - .collect::>(); - - serde_wasm_bindgen::to_value(&infos).unwrap() - } - - pub fn op_world_resources(&self, rid: u32) -> JsValue { - assert_eq!(rid, WORLD_RID); - let state = self.state.try_lock().expect(LOCK_SHOULD_NOT_FAIL); - let world = &state.world; - - let infos = world - .archetypes() - .resource() - .components() - .map(|id| world.components().get_info(id).unwrap()) - .map(JsComponentInfo::from) - .collect::>(); - - serde_wasm_bindgen::to_value(&infos).unwrap() - } - - pub fn op_world_entities(&self, rid: u32) -> JsValue { - assert_eq!(rid, WORLD_RID); - let mut state = self.state.try_lock().expect(LOCK_SHOULD_NOT_FAIL); - let world = &mut state.world; - - let entities = world - .query::() - .iter(world) - .map(JsEntity::from) - .collect::>(); - - serde_wasm_bindgen::to_value(&entities).unwrap() - } - - // TODO: Get rid of all the unwraps and throw proper errors - pub fn op_world_query(&self, rid: u32, query: JsValue) -> Result { - assert_eq!(rid, WORLD_RID); - let mut state = self.state.try_lock().expect(LOCK_SHOULD_NOT_FAIL); - let JsRuntimeState { - world, value_refs, .. - } = &mut *state; - - let descriptor: QueryDescriptor = serde_wasm_bindgen::from_value(query)?; - - let components: Vec = descriptor - .components - .iter() - .map(ComponentId::from) - .collect(); - - let mut query = EcsValueRefQuery::new(world, &components); - let results = query - .iter(world) - .map(|item| { - let components = item - .items - .into_iter() - .map(|value| JsValueRef { - key: value_refs.insert(ReflectValueRef::ecs_ref(value)), - function: None, - }) - .collect(); - - JsQueryItem { - entity: item.entity.into(), - components, - } - }) - .collect::>(); - - Ok(serde_wasm_bindgen::to_value(&results).unwrap()) - } - - pub fn op_world_get_resource( - &self, - rid: u32, - component_id: JsValue, - ) -> Result { - assert_eq!(rid, WORLD_RID); - let mut state = self.state.try_lock().expect(LOCK_SHOULD_NOT_FAIL); - let JsRuntimeState { - world, value_refs, .. - } = &mut *state; - - let component_id: ComponentIdOrBevyType = serde_wasm_bindgen::from_value(component_id)?; - let component_id = component_id.component_id(world).unwrap(); - - let value_ref = EcsValueRef::resource(world, component_id); - if world.get_resource_by_id(component_id).is_none() || value_ref.is_err() { - return Ok(JsValue::NULL); - } - - let value_ref = value_ref.unwrap(); - let value_ref = JsValueRef { - key: value_refs.insert(ReflectValueRef::ecs_ref(value_ref)), - function: None, - }; - - Ok(serde_wasm_bindgen::to_value(&value_ref)?) - } - - pub fn op_value_ref_get( - &self, - rid: u32, - value_ref: JsValue, - path: &str, - ) -> Result { - assert_eq!(rid, WORLD_RID); - let mut state = self.state.try_lock().expect(LOCK_SHOULD_NOT_FAIL); - let JsRuntimeState { - world, - value_refs, - reflect_functions, - .. - } = &mut *state; - - let value_ref: JsValueRef = serde_wasm_bindgen::from_value(value_ref)?; - let value_ref = match value_refs.get(value_ref.key) { - Some(value_ref) => value_ref, - None => return Ok(JsValue::NULL), - }; - - let type_registry = world.resource::(); - let type_registry = type_registry.read(); - - let reflect_methods = type_registry.get_type_data::( - value_ref.get(world).unwrap().type_id(), - ); - - if let Some(reflect_methods) = reflect_methods { - let method_name = path.trim_start_matches('.'); - if let Some(reflect_function) = reflect_methods.get(method_name.trim_start_matches('.')) - { - let value = JsValueRef { - key: value_refs.insert(value_ref.clone()), - function: Some(reflect_functions.insert(reflect_function.clone())), - }; - - return Ok(serde_wasm_bindgen::to_value(&value)?); - } - } - let value_ref = value_ref.append_path(path, world).unwrap(); - - { - let value = value_ref.get(world).unwrap(); - - try_downcast_leaf_get!(value for - u8, u16, u32, u64, u128, usize, - i8, i16, i32, i64, i128, isize, - String, char, bool, f32, f64 - ); - } - - let object = JsValueRef { - key: value_refs.insert(value_ref), - function: None, - }; - - Ok(serde_wasm_bindgen::to_value(&object)?) - } - - pub fn op_value_ref_set( - &self, - rid: u32, - value_ref: JsValue, - path: &str, - new_value: JsValue, - ) -> Result<(), JsValue> { - assert_eq!(rid, WORLD_RID); - let mut state = self.state.try_lock().expect(LOCK_SHOULD_NOT_FAIL); - let JsRuntimeState { - world, value_refs, .. - } = &mut *state; - - let value_ref: JsValueRef = serde_wasm_bindgen::from_value(value_ref)?; - let value_ref = match value_refs.get_mut(value_ref.key) { - Some(value_ref) => value_ref, - None => return Ok(()), - }; - let mut value_ref = value_ref.append_path(path, world).unwrap(); - - let mut reflect = value_ref.get_mut(world).unwrap(); - - try_downcast_leaf_set!(reflect <- new_value for - u8, u16, u32, u64, u128, usize, - i8, i16, i32, i64, i128, isize, - String, char, bool, f32, f64 - ); - - Err(JsValue::from_str(&format!( - "could not set value reference: type `{}` is not a primitive type", - reflect.type_name(), - ))) - } - - pub fn op_value_ref_keys(&self, rid: u32, value_ref: JsValue) -> Result { - assert_eq!(rid, WORLD_RID); - let mut state = self.state.try_lock().expect(LOCK_SHOULD_NOT_FAIL); - let JsRuntimeState { - world, value_refs, .. - } = &mut *state; - - let value_ref: JsValueRef = serde_wasm_bindgen::from_value(value_ref)?; - let value_ref = match value_refs.get_mut(value_ref.key) { - Some(value_ref) => value_ref, - None => return Ok(JsValue::NULL), - }; - let reflect = value_ref.get(world).unwrap(); - - let fields = match reflect.reflect_ref() { - ReflectRef::Struct(s) => (0..s.field_len()) - .map(|i| { - let name = s.name_at(i).ok_or_else(|| { - JsValue::from_str(&format!( - "misbehaving Reflect impl on `{}`", - s.type_name() - )) - })?; - Ok(name.to_owned()) - }) - .collect::>()?, - ReflectRef::Tuple(tuple) => (0..tuple.field_len()).map(|i| i.to_string()).collect(), - ReflectRef::TupleStruct(tuple_struct) => (0..tuple_struct.field_len()) - .map(|i| i.to_string()) - .collect(), - _ => Vec::new(), - }; - - Ok(serde_wasm_bindgen::to_value(&fields)?) - } - - pub fn op_value_ref_to_string(&self, rid: u32, value_ref: JsValue) -> Result { - assert_eq!(rid, WORLD_RID); - let mut state = self.state.try_lock().expect(LOCK_SHOULD_NOT_FAIL); - let JsRuntimeState { - world, value_refs, .. - } = &mut *state; - - let value_ref: JsValueRef = serde_wasm_bindgen::from_value(value_ref)?; - let value_ref = match value_refs.get(value_ref.key) { - Some(value_ref) => value_ref, - None => return Ok(JsValue::NULL), - }; - - let reflect = value_ref.get(world).unwrap(); - - Ok(JsValue::from_str(&format!("{reflect:?}"))) - } - - pub fn op_value_ref_call( - &self, - rid: u32, - receiver: JsValue, - args: JsValue, - ) -> Result { - assert_eq!(rid, WORLD_RID); - let mut state = self.state.try_lock().expect(LOCK_SHOULD_NOT_FAIL); - let JsRuntimeState { - world, - value_refs, - reflect_functions, - .. - } = &mut *state; - - #[derive(Deserialize, Debug)] - #[serde(untagged)] - enum ArgType { - JsValueRef(JsValueRef), - Number(f64), - } - - // Deserialize the receiver - let receiver: JsValueRef = serde_wasm_bindgen::from_value(receiver)?; - - // Get the receivers reflect_function - let method = match receiver.function { - Some(function) => match reflect_functions.get_mut(function) { - Some(function) => function, - None => panic!(), - }, - None => panic!(), - }; - let receiver = match value_refs.get(receiver.key) { - Some(value_ref) => value_ref.clone(), - None => panic!(), - }; - - let args: js_sys::Array = args.dyn_into().unwrap(); - - let receiver_pass_mode = method.signature[0].0; - let receiver_intermediate = match receiver_pass_mode { - PassMode::Ref => ReflectArgIntermediateValue::Ref(receiver.get(world).unwrap()), - PassMode::RefMut => { - unimplemented!("values passed by mutable reference in reflect fn call") - } - PassMode::Owned => ReflectArgIntermediateValue::Owned(receiver.get(world).unwrap()), - }; - let mut receiver_intermediate = ReflectArgIntermediate::Value(receiver_intermediate); - - let mut arg_intermediates = args - .iter() - .zip(method.signature.iter().skip(1)) - .map(|(arg, &(pass_mode, type_id))| { - let downcast_primitive = match type_id { - type_id if type_id == TypeId::of::() => { - Some(Primitive::f32(serde_wasm_bindgen::from_value(arg.clone())?)) - } - type_id if type_id == TypeId::of::() => { - Some(Primitive::f64(serde_wasm_bindgen::from_value(arg.clone())?)) - } - type_id if type_id == TypeId::of::() => { - Some(Primitive::i32(serde_wasm_bindgen::from_value(arg.clone())?)) - } - type_id if type_id == TypeId::of::() => { - Some(Primitive::u32(serde_wasm_bindgen::from_value(arg.clone())?)) - } - _ => None, - }; - if let Some(primitive) = downcast_primitive { - return Ok(ReflectArgIntermediate::Primitive(primitive, pass_mode)); - } - - let value: JsValueRef = serde_wasm_bindgen::from_value(arg)?; - let value = match value_refs.get(value.key) { - Some(value_ref) => value_ref, - None => panic!(), - }; - let value = match pass_mode { - PassMode::Ref => ReflectArgIntermediateValue::Ref(value.get(world).unwrap()), - PassMode::RefMut => { - unimplemented!("values passed by mutable reference in reflect fn call") - } - PassMode::Owned => { - ReflectArgIntermediateValue::Owned(value.get(world).unwrap()) - } - }; - - Ok(ReflectArgIntermediate::Value(value)) - }) - .collect::, JsValue>>()?; - let mut args: Vec = std::iter::once(&mut receiver_intermediate) - .chain(arg_intermediates.iter_mut()) - .map(|intermediate| intermediate.as_arg()) - .collect(); - - let ret = method.call(args.as_mut_slice()).unwrap(); - let ret = Rc::new(RefCell::new(ret)); - let ret = ReflectValueRef::free(ret); - - drop(args); - drop(arg_intermediates); - drop(receiver_intermediate); - - let ret = JsValueRef { - key: value_refs.insert(ret), - function: None, - }; - - Ok(serde_wasm_bindgen::to_value(&ret)?) - } -} - #[wasm_bindgen(module = "/src/runtime/wasm/wasm_setup.js")] extern "C" { fn setup_js_globals(bevy_mod_js_scripting: BevyModJsScripting); @@ -621,3 +200,32 @@ impl JsRuntimeApi for JsRuntime { std::mem::swap(&mut state.world, world); } } + +/// Helper trait for mapping errors to [`JsValue`]s +pub trait ToJsErr { + /// Convert the error to a [`JsValue`] + fn to_js_error(self) -> Result; +} + +impl ToJsErr for Result { + fn to_js_error(self) -> Result { + match self { + Ok(ok) => Ok(ok), + Err(e) => Err(JsValue::from_str(&e.to_string())), + } + } +} + +/// Helper trait to get a reflect value ref from the `value_refs` slotmap on [`JsRuntimeState`]. +trait GetReflectValueRef { + /// Casts a [`JsValue`] to a [`JsValueRef`] and loads its [`ReflectValueRef`]. + fn get_reflect_value_ref(&self, value: JsValue) -> Result<&ReflectValueRef, JsValue>; +} + +impl GetReflectValueRef for SlotMap { + fn get_reflect_value_ref(&self, js_value: JsValue) -> Result<&ReflectValueRef, JsValue> { + let value_ref: JsValueRef = serde_wasm_bindgen::from_value(js_value)?; + + self.get(value_ref.key).ok_or(REF_NOT_EXIST).to_js_error() + } +} \ No newline at end of file