From 116e6fce415454d4bdb7e416125bdd6ae0a8c93c Mon Sep 17 00:00:00 2001 From: Zicklag Date: Wed, 5 Oct 2022 09:49:41 -0500 Subject: [PATCH 01/10] Add world.spawn() --- src/runtime/ops/ecs.rs | 5 +++-- src/runtime/ops/ecs/ecs.js | 4 ++++ src/runtime/ops/ecs/{component.rs => world.rs} | 16 ++++++++++++++++ types/lib.bevy.d.ts | 1 + 4 files changed, 24 insertions(+), 2 deletions(-) rename src/runtime/ops/ecs/{component.rs => world.rs} (81%) diff --git a/src/runtime/ops/ecs.rs b/src/runtime/ops/ecs.rs index f4c8933..fcd3e17 100644 --- a/src/runtime/ops/ecs.rs +++ b/src/runtime/ops/ecs.rs @@ -4,12 +4,12 @@ use crate::runtime::{JsRuntimeOp, OpMap}; use self::types::{JsReflectFunctions, JsValueRefs}; -mod component; mod info; mod query; mod resource; pub mod types; mod value; +mod world; pub fn insert_ecs_ops(ops: &mut OpMap) { ops.insert("ecs_js", Box::new(EcsJs)); @@ -40,10 +40,11 @@ pub fn insert_ecs_ops(ops: &mut OpMap) { Box::new(value::ecs_value_ref_default), ); ops.insert("ecs_value_ref_patch", Box::new(value::ecs_value_ref_patch)); + ops.insert("ecs_entity_spawn", Box::new(world::ecs_entity_spawn)); ops.insert("ecs_value_ref_cleanup", Box::new(value::EcsValueRefCleanup)); ops.insert( "ecs_component_insert", - Box::new(component::ecs_component_insert), + Box::new(world::ecs_component_insert), ); } diff --git a/src/runtime/ops/ecs/ecs.js b/src/runtime/ops/ecs/ecs.js index ea37834..bf4538b 100644 --- a/src/runtime/ops/ecs/ecs.js +++ b/src/runtime/ops/ecs/ecs.js @@ -97,6 +97,10 @@ Value.unwrapValueRef(component) ); } + + spawn() { + return Value.wrapValueRef(bevyModJsScriptingOpSync("ecs_entity_spawn")); + } } const VALUE_REF_GET_INNER = Symbol("value_ref_get_inner"); diff --git a/src/runtime/ops/ecs/component.rs b/src/runtime/ops/ecs/world.rs similarity index 81% rename from src/runtime/ops/ecs/component.rs rename to src/runtime/ops/ecs/world.rs index 0579253..3998454 100644 --- a/src/runtime/ops/ecs/component.rs +++ b/src/runtime/ops/ecs/world.rs @@ -4,6 +4,22 @@ use bevy_reflect::TypeRegistryArc; use crate::{JsValueRef, JsValueRefs, OpContext}; +pub fn ecs_entity_spawn( + context: OpContext, + world: &mut bevy::prelude::World, + _args: serde_json::Value, +) -> anyhow::Result { + let value_refs = context + .op_state + .entry::() + .or_insert_with(default); + + let entity = world.spawn().id(); + let value_ref = JsValueRef::new_free(Box::new(entity), value_refs); + + Ok(serde_json::to_value(value_ref)?) +} + pub fn ecs_component_insert( context: OpContext, world: &mut bevy::prelude::World, diff --git a/types/lib.bevy.d.ts b/types/lib.bevy.d.ts index 246bd84..309e0a7 100644 --- a/types/lib.bevy.d.ts +++ b/types/lib.bevy.d.ts @@ -80,6 +80,7 @@ declare class World { query(...query: Q): QueryItems; get(entity: Entity, component: BevyType): T | undefined; insert(entity: Entity, component: any): void; + spawn(): Entity; } declare let world: World; From a804eb3cd9915e3e8c87c2a4d3a1e1608b0cacda Mon Sep 17 00:00:00 2001 From: Zicklag Date: Thu, 6 Oct 2022 12:27:38 -0500 Subject: [PATCH 02/10] Allow Assigning Non-Primitive Value Refs --- src/runtime/ops/ecs/ecs.js | 2 +- src/runtime/ops/ecs/value.rs | 171 +++++++++++++++++++++++++++++++---- 2 files changed, 155 insertions(+), 18 deletions(-) diff --git a/src/runtime/ops/ecs/ecs.js b/src/runtime/ops/ecs/ecs.js index bf4538b..059c534 100644 --- a/src/runtime/ops/ecs/ecs.js +++ b/src/runtime/ops/ecs/ecs.js @@ -164,7 +164,7 @@ "ecs_value_ref_set", target.valueRef, p, - value + Value.unwrapValueRef(value) ); }, apply: (target, thisArg, args) => { diff --git a/src/runtime/ops/ecs/value.rs b/src/runtime/ops/ecs/value.rs index b60625f..c554b7c 100644 --- a/src/runtime/ops/ecs/value.rs +++ b/src/runtime/ops/ecs/value.rs @@ -36,7 +36,7 @@ macro_rules! try_downcast_leaf_set { return Ok(()); })* - Ok::<(), anyhow::Error>(()) + bail!("Couldn't assign to primitive"); })() }; } @@ -60,7 +60,7 @@ pub fn patch_reflect_with_json( )?; } serde_json::Value::Array(array) => match value.reflect_mut() { - bevy_reflect::ReflectMut::Struct(_) => todo!(), + bevy_reflect::ReflectMut::Struct(_) => bail!("Cannot patch struct with Array"), bevy_reflect::ReflectMut::List(target) => { let target_len = target.len(); let patch_len = array.len(); @@ -152,6 +152,53 @@ pub fn patch_reflect_with_json( Ok(()) } +/// Check whether or not it's safe to `Reflect.apply` one reflect to another +fn reflect_is_compatible(reflect1: &dyn Reflect, reflect2: &dyn Reflect) -> bool { + match (reflect1.reflect_ref(), reflect2.reflect_ref()) { + (ReflectRef::Value(value1), ReflectRef::Value(value2)) => { + value1.type_id() == value2.type_id() + } + (ReflectRef::Array(arr1), ReflectRef::Array(arr2)) => { + arr1.iter() + .zip(arr2.iter()) + .fold(true, |compatible, (reflect1, reflect2)| { + compatible && reflect_is_compatible(reflect1, reflect2) + }) + } + (ReflectRef::List(list1), ReflectRef::List(list2)) => { + list1 + .iter() + .zip(list2.iter()) + .fold(true, |compatible, (reflect1, reflect2)| { + compatible && reflect_is_compatible(reflect1, reflect2) + }) + } + (ReflectRef::Tuple(tuple1), ReflectRef::Tuple(tuple2)) => tuple1 + .iter_fields() + .zip(tuple2.iter_fields()) + .fold(true, |compatible, (reflect1, reflect2)| { + compatible && reflect_is_compatible(reflect1, reflect2) + }), + (ReflectRef::TupleStruct(tuple1), ReflectRef::TupleStruct(tuple2)) => tuple1 + .iter_fields() + .zip(tuple2.iter_fields()) + .fold(true, |compatible, (reflect1, reflect2)| { + compatible && reflect_is_compatible(reflect1, reflect2) + }), + (ReflectRef::Struct(struct1), ReflectRef::Struct(struct2)) => struct1 + .iter_fields() + .enumerate() + .fold(true, |compatible, (i, field1)| { + if let Some(field2) = struct2.field(struct1.name_at(i).unwrap()) { + compatible && reflect_is_compatible(field1, field2) + } else { + compatible + } + }), + _ => false, + } +} + pub fn ecs_value_ref_get( context: OpContext, world: &mut bevy::prelude::World, @@ -244,22 +291,53 @@ pub fn ecs_value_ref_set( // Access the provided path on the value ref let mut value_ref = append_path(value_ref, path, world)?; - // 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 - ) - .map(|_| serde_json::Value::Null) - .map_err(|e| { - format_err!( - "could not set value reference: type `{typename}` is not a primitive type: {e}", - typename = reflect.type_name(), + // Try to asign as a primitive + let primitive_assignment_result = { + let new_value = new_value.clone(); + let mut reflect = value_ref.get_mut(world)?; + + // 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 ) - }) + .map_err(|e| { + format_err!( + "could not set value reference: type `{type_name}` is not a primitive \ + type or value ref: {e}", + type_name = reflect.type_name(), + ) + }) + }; + + // If we could not assign a primitive + if let Err(e) = primitive_assignment_result { + // Try to assign as a reflect value + if let Ok(new_js_value_ref) = serde_json::from_value::(new_value) { + let new_value_ref = value_refs + .get(new_js_value_ref.key) + .ok_or_else(|| format_err!("Value ref doesn't exist"))?; + let new_reflect = new_value_ref.get(world)?.clone_value(); + let mut reflect = value_ref.get_mut(world)?; + + if !reflect_is_compatible(new_reflect.as_reflect(), reflect.as_reflect()) { + bail!( + "Cannot assign value ref. {} cannot be assigned to {}", + new_reflect.type_name(), + reflect.type_name() + ); + } + + reflect.apply(new_reflect.as_reflect()); + + Ok(serde_json::Value::Null) + } else { + Err(e) + } + } else { + Ok(serde_json::Value::Null) + } } pub fn ecs_value_ref_keys( @@ -559,3 +637,62 @@ fn append_path( }; Ok(value_ref.append_path(&path, world)?) } + +#[cfg(test)] +mod test { + use super::*; + + #[derive(Reflect, Default)] + struct S1 { + a: String, + b: f32, + } + + #[derive(Reflect, Default)] + struct S2 { + a: String, + b: f32, + c: u32, + } + + #[derive(Reflect, Default)] + struct S3 { + a: String, + b: u32, + } + + #[test] + fn test_reflect_is_compatible_check() { + let string = Box::new(String::default()) as Box; + let uint = Box::new(0u32) as Box; + let mut s1 = Box::new(S1::default()) as Box; + let s2 = Box::new(S2::default()) as Box; + let s3 = Box::new(S3::default()) as Box; + + assert!(!reflect_is_compatible( + uint.as_reflect(), + string.as_reflect() + )); + assert!(!reflect_is_compatible(s1.as_reflect(), string.as_reflect())); + + assert!(reflect_is_compatible(s1.as_reflect(), s2.as_reflect())); + s1.apply(s2.as_reflect()); + + assert!(!reflect_is_compatible(s1.as_reflect(), s3.as_reflect())); + + let mut l1 = Box::new(vec![1, 2, 3]) as Box; + let l2 = Box::new(vec![5, 4, 3, 2, 1]) as Box; + + assert!(reflect_is_compatible(l1.as_reflect(), l2.as_reflect())); + l1.apply(l2.as_reflect()); + assert!(!reflect_is_compatible(l1.as_reflect(), s1.as_reflect())); + + let mut t1 = Box::new((0u32, String::from("hi"))) as Box; + let t2 = Box::new((1u32, String::from("bye"))) as Box; + let t3 = Box::new((0f32, String::from("bye"))) as Box; + + assert!(reflect_is_compatible(t1.as_reflect(), t2.as_reflect())); + t1.apply(t2.as_reflect()); + assert!(!reflect_is_compatible(t1.as_reflect(), t3.as_reflect())); + } +} From 8608caf7c99721e3a20b9a7684a27e2b3c60e661 Mon Sep 17 00:00:00 2001 From: Zicklag Date: Thu, 6 Oct 2022 13:24:36 -0500 Subject: [PATCH 03/10] Implement Non-primitive Patching --- src/runtime/ops/ecs/ecs.js | 13 ++++- src/runtime/ops/ecs/value.rs | 93 ++++++++++++++++++++++++++++++++---- 2 files changed, 96 insertions(+), 10 deletions(-) diff --git a/src/runtime/ops/ecs/ecs.js b/src/runtime/ops/ecs/ecs.js index 059c534..88dc682 100644 --- a/src/runtime/ops/ecs/ecs.js +++ b/src/runtime/ops/ecs/ecs.js @@ -110,7 +110,16 @@ unwrapValueRef(valueRefProxy) { if (valueRefProxy === null || valueRefProxy === undefined) return valueRefProxy; const inner = valueRefProxy[VALUE_REF_GET_INNER] - return inner ? inner : valueRefProxy; + if (inner) { + return inner; + } else { + if (typeof valueRefProxy == 'object') { + for (const key of Reflect.ownKeys(valueRefProxy)) { + valueRefProxy[key] = Value.unwrapValueRef(valueRefProxy[key]); + } + } + return valueRefProxy; + } }, // keep primitives, null and undefined as is, otherwise wraps the object @@ -183,7 +192,7 @@ // Instantiates the default value of a given bevy type create(type, patch) { - return Value.wrapValueRef(bevyModJsScriptingOpSync("ecs_value_ref_default", type.typeName, patch)); + return Value.wrapValueRef(bevyModJsScriptingOpSync("ecs_value_ref_default", type.typeName, Value.unwrapValueRef(patch))); }, patch(value, patch) { diff --git a/src/runtime/ops/ecs/value.rs b/src/runtime/ops/ecs/value.rs index c554b7c..ed74d9a 100644 --- a/src/runtime/ops/ecs/value.rs +++ b/src/runtime/ops/ecs/value.rs @@ -1,7 +1,10 @@ use std::any::TypeId; use anyhow::{bail, format_err, Context}; -use bevy::prelude::{default, ReflectDefault, World}; +use bevy::{ + prelude::{default, ReflectDefault, World}, + utils::HashMap, +}; use bevy_ecs_dynamic::reflect_value_ref::ReflectValueRef; use bevy_reflect::{Reflect, ReflectRef, TypeRegistryArc}; use bevy_reflect_fns::{PassMode, ReflectArg, ReflectMethods}; @@ -41,25 +44,93 @@ macro_rules! try_downcast_leaf_set { }; } +#[derive(Debug)] +pub enum JsonValueOrReflect { + Null, + Bool(bool), + Number(serde_json::Number), + String(String), + Array(Vec), + Object(HashMap), + Reflect(Box), +} + +impl JsonValueOrReflect { + fn into_primitive_value(self) -> Option { + use serde_json::Value as V; + Some(match self { + JsonValueOrReflect::Null => V::Null, + JsonValueOrReflect::Bool(b) => V::Bool(b), + JsonValueOrReflect::Number(n) => V::Number(n), + JsonValueOrReflect::String(s) => V::String(s), + _ => return None, + }) + } + fn from_value( + value: serde_json::Value, + value_refs: &JsValueRefs, + world: &World, + ) -> anyhow::Result { + if let Ok(value_ref) = serde_json::from_value::(value.clone()) { + let value_ref = value_refs + .get(value_ref.key) + .ok_or_else(|| format_err!("Value ref doesn't exist"))?; + let reflect = value_ref.get(world)?.clone_value(); + + Ok(Self::Reflect(reflect)) + } else { + match value { + serde_json::Value::Null => Ok(Self::Null), + serde_json::Value::Bool(b) => Ok(Self::Bool(b)), + serde_json::Value::Number(n) => Ok(Self::Number(n)), + serde_json::Value::String(s) => Ok(Self::String(s)), + serde_json::Value::Array(arr) => Ok(Self::Array( + arr.into_iter() + .map(|value| Self::from_value(value, value_refs, world)) + .collect::>()?, + )), + serde_json::Value::Object(map) => { + let mut object = HashMap::default(); + for (key, value) in map { + object.insert(key, Self::from_value(value, value_refs, world)?); + } + Ok(Self::Object(object)) + } + } + } + } +} + /// Converts a JSON value to a dynamic reflect struct or list pub fn patch_reflect_with_json( value: &mut dyn Reflect, - patch: serde_json::Value, + patch: JsonValueOrReflect, ) -> anyhow::Result<()> { match patch { - serde_json::Value::Null => { + JsonValueOrReflect::Reflect(patch) => { + if !reflect_is_compatible(value, patch.as_reflect()) { + bail!( + "Cannot assign type {} to {}", + value.type_name(), + patch.type_name() + ); + } + value.apply(patch.as_reflect()); + } + JsonValueOrReflect::Null => { bail!("Can't patch values with null"); } - patch @ (serde_json::Value::Bool(_) - | serde_json::Value::Number(_) - | serde_json::Value::String(_)) => { + patch @ (JsonValueOrReflect::Bool(_) + | JsonValueOrReflect::Number(_) + | JsonValueOrReflect::String(_)) => { + let patch = patch.into_primitive_value().unwrap(); try_downcast_leaf_set!(value <- patch for u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, String, char, bool, f32, f64 )?; } - serde_json::Value::Array(array) => match value.reflect_mut() { + JsonValueOrReflect::Array(array) => match value.reflect_mut() { bevy_reflect::ReflectMut::Struct(_) => bail!("Cannot patch struct with Array"), bevy_reflect::ReflectMut::List(target) => { let target_len = target.len(); @@ -112,7 +183,7 @@ pub fn patch_reflect_with_json( bevy_reflect::ReflectMut::Map(_) => bail!("Cannot patch map with array"), bevy_reflect::ReflectMut::Value(_) => bail!("Cannot patch primitive value with array"), }, - serde_json::Value::Object(map) => match value.reflect_mut() { + JsonValueOrReflect::Object(map) => match value.reflect_mut() { bevy_reflect::ReflectMut::Struct(target) => { for (key, value) in map { let field = target.field_mut(&key).ok_or_else(|| { @@ -417,6 +488,10 @@ pub fn ecs_value_ref_default( .entry::() .or_insert_with(default); + let patch = patch + .map(|patch| JsonValueOrReflect::from_value(patch, value_refs, world)) + .transpose()?; + // Load the type registry let type_registry = world.resource::(); let type_registry = type_registry.read(); @@ -456,6 +531,8 @@ pub fn ecs_value_ref_patch( .entry::() .or_insert_with(default); + let patch = JsonValueOrReflect::from_value(patch, value_refs, world)?; + let value_ref = value_refs .get_mut(value_ref.key) .ok_or_else(|| format_err!("Value ref does not exist"))?; From 87b8e77fa78eb219c6dd659f2a3c009ac122edc4 Mon Sep 17 00:00:00 2001 From: Zicklag Date: Thu, 6 Oct 2022 13:32:10 -0500 Subject: [PATCH 04/10] Strictly Type patch() and create() Methods --- types/lib.bevy.d.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/types/lib.bevy.d.ts b/types/lib.bevy.d.ts index 309e0a7..dc5c2f3 100644 --- a/types/lib.bevy.d.ts +++ b/types/lib.bevy.d.ts @@ -22,9 +22,13 @@ declare interface BevyScript { declare type RawValueRef = unknown; +type RecursivePartial = { + [P in keyof T]?: RecursivePartial; +}; + declare interface ValueGlobal { - create(t: BevyType, patch?: any): T; - patch(value: any, patch: any): T; + create(t: BevyType, patch?: RecursivePartial): T; + patch(value: T, patch: RecursivePartial): T; } declare let Value: ValueGlobal; From fe2227d3cde44e61731e1bc2e0e268a9e54964c8 Mon Sep 17 00:00:00 2001 From: Zicklag Date: Wed, 12 Oct 2022 09:50:29 -0500 Subject: [PATCH 05/10] Clear Value Refs Every Frame --- src/runtime/ops/ecs.rs | 1 + src/runtime/ops/ecs/value.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/runtime/ops/ecs.rs b/src/runtime/ops/ecs.rs index fcd3e17..741c4e5 100644 --- a/src/runtime/ops/ecs.rs +++ b/src/runtime/ops/ecs.rs @@ -40,6 +40,7 @@ pub fn insert_ecs_ops(ops: &mut OpMap) { Box::new(value::ecs_value_ref_default), ); ops.insert("ecs_value_ref_patch", Box::new(value::ecs_value_ref_patch)); + ops.insert("ecs_value_ref_cleanup", Box::new(value::EcsValueRefCleanup)); ops.insert("ecs_entity_spawn", Box::new(world::ecs_entity_spawn)); ops.insert("ecs_value_ref_cleanup", Box::new(value::EcsValueRefCleanup)); ops.insert( diff --git a/src/runtime/ops/ecs/value.rs b/src/runtime/ops/ecs/value.rs index ed74d9a..106b294 100644 --- a/src/runtime/ops/ecs/value.rs +++ b/src/runtime/ops/ecs/value.rs @@ -9,7 +9,7 @@ use bevy_ecs_dynamic::reflect_value_ref::ReflectValueRef; use bevy_reflect::{Reflect, ReflectRef, TypeRegistryArc}; use bevy_reflect_fns::{PassMode, ReflectArg, ReflectMethods}; -use crate::runtime::OpContext; +use crate::{runtime::OpContext, JsRuntimeOp, JsReflectFunctions}; use super::{ types::{ From 0b3573d6e7b631ce0b5a5701abf5ada9d7d6be76 Mon Sep 17 00:00:00 2001 From: Zicklag Date: Thu, 13 Oct 2022 19:45:03 -0500 Subject: [PATCH 06/10] Re-export bevy_reflect_fns --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index 8f2c554..e9c73a9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,7 @@ mod transpile; use asset::JsScriptLoader; use bevy::{asset::AssetStage, prelude::*, utils::HashSet}; +pub use bevy_reflect_fns; pub use asset::JsScript; pub use bevy_ecs_dynamic; pub use runtime::{ From 1c9b8ba15ac3b58cdd4e27cb6786258904a6da57 Mon Sep 17 00:00:00 2001 From: Zicklag Date: Thu, 13 Oct 2022 20:12:02 -0500 Subject: [PATCH 07/10] Try Downcasting Function Calls to Primitive Values --- src/runtime/ops/ecs/value.rs | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/runtime/ops/ecs/value.rs b/src/runtime/ops/ecs/value.rs index 106b294..ab2882b 100644 --- a/src/runtime/ops/ecs/value.rs +++ b/src/runtime/ops/ecs/value.rs @@ -9,7 +9,7 @@ use bevy_ecs_dynamic::reflect_value_ref::ReflectValueRef; use bevy_reflect::{Reflect, ReflectRef, TypeRegistryArc}; use bevy_reflect_fns::{PassMode, ReflectArg, ReflectMethods}; -use crate::{runtime::OpContext, JsRuntimeOp, JsReflectFunctions}; +use crate::{runtime::OpContext, JsReflectFunctions, JsRuntimeOp}; use super::{ types::{ @@ -638,14 +638,25 @@ pub fn ecs_value_ref_call( // Finally call the method let ret = method.call(args.as_mut_slice()).unwrap(); - // Drop our intermediates and args so that we can use `value_refs` again, below. - drop(args); - drop(arg_intermediates); - drop(receiver_intermediate); + // Try to downcast return value to a primitive + let primitive = try_downcast_leaf_get!(ret for + u8, u16, u32, u64, u128, usize, + i8, i16, i32, i64, i128, isize, + String, char, bool, f32, f64 + )?; + + if let Some(primitive) = primitive { + Ok(primitive) + } else { + // Drop our intermediates and args so that we can use `value_refs` again, below. + drop(args); + drop(arg_intermediates); + drop(receiver_intermediate); - let ret = JsValueRef::new_free(ret, value_refs); + let ret = JsValueRef::new_free(ret, value_refs); - Ok(serde_json::to_value(ret)?) + Ok(serde_json::to_value(ret)?) + } }) } From 8144a19b097a3b233ceff2f280a0eaa2fc1b17bf Mon Sep 17 00:00:00 2001 From: Zicklag Date: Thu, 13 Oct 2022 20:52:28 -0500 Subject: [PATCH 08/10] Set Script Info During Script Load on WASM --- src/runtime/wasm.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/runtime/wasm.rs b/src/runtime/wasm.rs index 8e14ad9..73ef0d3 100644 --- a/src/runtime/wasm.rs +++ b/src/runtime/wasm.rs @@ -131,6 +131,15 @@ impl FromWorld for JsRuntime { impl JsRuntimeApi for JsRuntime { fn load_script(&self, handle: &Handle, script: &JsScript, _reload: bool) { + // Set script info + { + let mut state = self.state.try_lock().expect(LOCK_SHOULD_NOT_FAIL); + state.script_info = ScriptInfo { + path: script.path.clone(), + handle: handle.clone_weak(), + }; + } + let function = js_sys::Function::new_no_args(&format!( r#"return ((window) => {{ {code} @@ -146,6 +155,15 @@ impl JsRuntimeApi for JsRuntime { } }; + // Clear script info + { + let mut state = self.state.try_lock().expect(LOCK_SHOULD_NOT_FAIL); + state.script_info = ScriptInfo { + path: default(), + handle: default(), + }; + } + self.scripts.try_lock().expect(LOCK_SHOULD_NOT_FAIL).insert( handle.clone_weak(), ScriptData { From d9ca764310e042bdc6a5c822922d8e017847a717 Mon Sep 17 00:00:00 2001 From: Zicklag Date: Fri, 14 Oct 2022 13:39:33 -0500 Subject: [PATCH 09/10] Fix Integer Assignments on Native When trying to assign integers on native, there would be a serialization error because the numbers were deserialized with an extra decimal. This converts valid integers to integers during deserialization of the arguments to make sure integer assignments succeed. --- src/runtime/native.rs | 44 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/runtime/native.rs b/src/runtime/native.rs index 4a82ebf..b804af3 100644 --- a/src/runtime/native.rs +++ b/src/runtime/native.rs @@ -285,6 +285,7 @@ fn op_bevy_mod_js_scripting( args: serde_json::Value, ) -> Result { with_state(state, |state, custom_op_state| { + let args = convert_safe_ints(args); let script_info = state.borrow::(); let ops = state.borrow::(); let op_names = state.borrow::(); @@ -323,3 +324,46 @@ fn with_state R>(state: &mut O r } + +/// Takes a [`serde_json::Value`] and converts all floating point number types that are safe +/// integers, to integers. +/// +/// This is important for deserializing numbers to integers, because of the way `serde_json` handles +/// them. +/// +/// For example, `serde_json` will not deserialize `1.0` to a `u32` without an error, but it will +/// deserialize `1`. `serde_v8` seems to retun numbers with a decimal point, even when they are +/// valid integers, so this function makes the conversion of safe integers back to integers without +/// a decimal point. +fn convert_safe_ints(value: serde_json::Value) -> serde_json::Value { + match value { + serde_json::Value::Number(n) => { + let max_safe_int = (2u64.pow(53) - 1) as f64; + + serde_json::Value::Number(if let Some(f) = n.as_f64() { + if f.abs() <= max_safe_int && f.fract() == 0.0 { + if f == 0.0 { + serde_json::Number::from(0u64) + } else if f.is_sign_negative() { + serde_json::Number::from(f as i64) + } else { + serde_json::Number::from(f as u64) + } + } else { + n + } + } else { + n + }) + } + serde_json::Value::Array(arr) => { + serde_json::Value::Array(arr.into_iter().map(|x| convert_safe_ints(x)).collect()) + } + serde_json::Value::Object(obj) => serde_json::Value::Object( + obj.into_iter() + .map(|(k, v)| (k, convert_safe_ints(v))) + .collect(), + ), + other => other, + } +} From 9534a8db45c88f7aaba151ec2d6f9f36529a31e2 Mon Sep 17 00:00:00 2001 From: Zicklag Date: Sat, 15 Oct 2022 11:16:02 -0500 Subject: [PATCH 10/10] Avoid Error About Not Reading Prop `bind` of null --- src/runtime/ops/ecs/ecs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runtime/ops/ecs/ecs.js b/src/runtime/ops/ecs/ecs.js index 88dc682..c0290fe 100644 --- a/src/runtime/ops/ecs/ecs.js +++ b/src/runtime/ops/ecs/ecs.js @@ -79,7 +79,7 @@ default: const collected = collectedQuery(target); const prop = collected[propName]; - return prop.bind ? prop.bind(collected) : prop; + return prop && prop.bind ? prop.bind(collected) : prop; } } })