diff --git a/assets/tests/add_default_component/component_with_default_and_component_data_adds_default.lua b/assets/tests/add_default_component/component_with_default_and_component_data_adds_default.lua index 8895f0b9fd..c8138df04f 100644 --- a/assets/tests/add_default_component/component_with_default_and_component_data_adds_default.lua +++ b/assets/tests/add_default_component/component_with_default_and_component_data_adds_default.lua @@ -6,4 +6,4 @@ local added = world.has_component(entity, _type) assert(added ~= nil, 'Component not added') local component = world.get_component(entity, _type) -assert(component._1 == "Default", 'Component did not have default value, got: ' .. component._1) \ No newline at end of file +assert(component[1] == "Default", 'Component did not have default value, got: ' .. component[1]) diff --git a/assets/tests/add_default_component/component_with_default_and_component_data_adds_default.rhai b/assets/tests/add_default_component/component_with_default_and_component_data_adds_default.rhai index e2ce895868..84aec896cb 100644 --- a/assets/tests/add_default_component/component_with_default_and_component_data_adds_default.rhai +++ b/assets/tests/add_default_component/component_with_default_and_component_data_adds_default.rhai @@ -6,4 +6,4 @@ let added = world.has_component.call(entity, _type); assert(type_of(added) != "()", "Component not added"); let component = world.get_component.call(entity, _type); -assert(component["_0"] == "Default", "Component did not have default value, got: " + component["_0"]); \ No newline at end of file +assert(component[0] == "Default", "Component did not have default value, got: " + component[0]); \ No newline at end of file diff --git a/assets/tests/add_default_component/component_with_from_world_and_component_data_adds_default.lua b/assets/tests/add_default_component/component_with_from_world_and_component_data_adds_default.lua index c4725e7235..8ebc284cb3 100644 --- a/assets/tests/add_default_component/component_with_from_world_and_component_data_adds_default.lua +++ b/assets/tests/add_default_component/component_with_from_world_and_component_data_adds_default.lua @@ -6,4 +6,4 @@ local added = world.has_component(entity, _type) assert(added ~= nil, 'Component not added') local component = world.get_component(entity, _type) -assert(component._1 == "Default", 'Component did not have default value, got: ' .. component._1) \ No newline at end of file +assert(component[1] == "Default", 'Component did not have default value, got: ' .. component[1]) diff --git a/assets/tests/add_default_component/component_with_from_world_and_component_data_adds_default.rhai b/assets/tests/add_default_component/component_with_from_world_and_component_data_adds_default.rhai index 63c81f3dec..b0644743e3 100644 --- a/assets/tests/add_default_component/component_with_from_world_and_component_data_adds_default.rhai +++ b/assets/tests/add_default_component/component_with_from_world_and_component_data_adds_default.rhai @@ -6,4 +6,4 @@ let added = world.has_component.call(entity, _type); assert(type_of(added) != "()", "Component not added"); let component = world.get_component.call(entity, _type); -assert(component["_0"] == "Default", "Component did not have default value, got: " + component["_0"]) \ No newline at end of file +assert(component[0] == "Default", "Component did not have default value, got: " + component[0]) \ No newline at end of file diff --git a/assets/tests/construct/construct_enum.lua b/assets/tests/construct/construct_enum.lua index 74b8189239..6f52e03d5e 100644 --- a/assets/tests/construct/construct_enum.lua +++ b/assets/tests/construct/construct_enum.lua @@ -6,22 +6,28 @@ local constructed = construct(type, { foo = 123 }) -assert(constructed:variant_name() == "Struct", "Value was constructed incorrectly, expected constructed.variant to be Struct but got " .. constructed:variant_name()) -assert(constructed.foo == 123, "Value was constructed incorrectly, expected constructed.foo to be 123 but got " .. constructed.foo) +assert(constructed:variant_name() == "Struct", + "Value was constructed incorrectly, expected constructed.variant to be Struct but got " .. constructed:variant_name()) +assert(constructed.foo == 123, + "Value was constructed incorrectly, expected constructed.foo to be 123 but got " .. constructed.foo) -- TupleStruct Variant local constructed = construct(type, { variant = "TupleStruct", - _1 = 123 + ["1"] = 123 }) -assert(constructed:variant_name() == "TupleStruct", "Value was constructed incorrectly, expected constructed.variant to be TupleStruct but got " .. constructed:variant_name()) -assert(constructed._1 == 123, "Value was constructed incorrectly, expected constructed._1 to be 123 but got " .. constructed._1) +assert(constructed:variant_name() == "TupleStruct", + "Value was constructed incorrectly, expected constructed.variant to be TupleStruct but got " .. + constructed:variant_name()) +assert(constructed[1] == 123, + "Value was constructed incorrectly, expected constructed._1 to be 123 but got " .. constructed[1]) -- Unit Variant local constructed = construct(type, { variant = "Unit" }) -assert(constructed:variant_name() == "Unit", "Value was constructed incorrectly, expected constructed.variant to be Unit but got " .. constructed:variant_name()) \ No newline at end of file +assert(constructed:variant_name() == "Unit", + "Value was constructed incorrectly, expected constructed.variant to be Unit but got " .. constructed:variant_name()) diff --git a/assets/tests/construct/construct_tuple_struct.lua b/assets/tests/construct/construct_tuple_struct.lua index 060c90311f..52414a9d77 100644 --- a/assets/tests/construct/construct_tuple_struct.lua +++ b/assets/tests/construct/construct_tuple_struct.lua @@ -1,7 +1,7 @@ local type = world.get_type_by_name("SimpleTupleStruct") local constructed = construct(type, { - _1 = 123 + ["1"] = 123 }) -assert(constructed._1 == 123, - "Value was constructed incorrectly, expected constructed.foo to be 123 but got " .. constructed._1) +assert(constructed[1] == 123, + "Value was constructed incorrectly, expected constructed.foo to be 123 but got " .. constructed[1]) diff --git a/assets/tests/construct/simple_enum.rhai b/assets/tests/construct/simple_enum.rhai index 463de1f9a7..a13678d6fd 100644 --- a/assets/tests/construct/simple_enum.rhai +++ b/assets/tests/construct/simple_enum.rhai @@ -7,10 +7,10 @@ assert(constructed.variant_name.call() == "Struct", "Value was constructed incor assert(constructed.foo == 123, "Value was constructed incorrectly, expected constructed.foo to be 123 but got " + constructed.foo); // TupleStruct Variant -constructed = construct.call(type, #{ variant: "TupleStruct", "_0": 123 }); +constructed = construct.call(type, #{ variant: "TupleStruct", "0": 123 }); assert(constructed.variant_name.call() == "TupleStruct", "Value was constructed incorrectly, expected constructed.variant to be TupleStruct but got " + constructed.variant_name.call()); -assert(constructed["_0"] == 123, "Value was constructed incorrectly, expected constructed._0 to be 123 but got " + constructed["_0"]); +assert(constructed[0] == 123, "Value was constructed incorrectly, expected constructed[0] to be 123 but got " + constructed[0]); // Unit Variant constructed = construct.call(type, #{ variant: "Unit" }); diff --git a/assets/tests/construct/simple_tuple_struct.rhai b/assets/tests/construct/simple_tuple_struct.rhai index 411b3e806f..b7a10cb4a1 100644 --- a/assets/tests/construct/simple_tuple_struct.rhai +++ b/assets/tests/construct/simple_tuple_struct.rhai @@ -1,4 +1,4 @@ let type = world.get_type_by_name.call("SimpleTupleStruct"); -let constructed = construct.call(type, #{ "_0": 123 }); +let constructed = construct.call(type, #{ "0": 123 }); -assert(constructed["_0"] == 123, "Value was constructed incorrectly, expected constructed.foo to be 123 but got " + constructed["_0"]); \ No newline at end of file +assert(constructed[0] == 123, "Value was constructed incorrectly, expected constructed.foo to be 123 but got " + constructed[0]); \ No newline at end of file diff --git a/assets/tests/display/print_value_by_default.lua b/assets/tests/display/print_value_by_default.lua index e4cbdba284..8dc6f8d8cc 100644 --- a/assets/tests/display/print_value_by_default.lua +++ b/assets/tests/display/print_value_by_default.lua @@ -20,9 +20,10 @@ local expected_vec3_debug = [[ ReflectAllocationId(*anything*), ), }, - reflect_path: ParsedPath( - [], - ), + reflect_path: ReferencePath { + one_indexed: false, + path: [], + }, } ]] -- normalize allocation ids before comparison so tests don't fail on runtime-generated ids @@ -49,9 +50,10 @@ ReflectReference { ), ), }, - reflect_path: ParsedPath( - [], - ), + reflect_path: ReferencePath { + one_indexed: false, + path: [], + }, } ]] diff --git a/assets/tests/get_component/component_no_component_data.lua b/assets/tests/get_component/component_no_component_data.lua index d938e9d3f1..003b1a16ef 100644 --- a/assets/tests/get_component/component_no_component_data.lua +++ b/assets/tests/get_component/component_no_component_data.lua @@ -3,4 +3,4 @@ local entity = world._get_entity_with_test_component("CompWithDefault") local retrieved = world.get_component(entity, component) assert(retrieved ~= nil, "Component was not found") -assert(retrieved._1 == "Initial Value", "Component data was not retrieved correctly, retrieved._1 was: " .. retrieved._1) \ No newline at end of file +assert(retrieved[1] == "Initial Value", "Component data was not retrieved correctly, retrieved._1 was: " .. retrieved[1]) diff --git a/assets/tests/get_component/component_no_component_data.rhai b/assets/tests/get_component/component_no_component_data.rhai index 1000055921..d9e50e39c0 100644 --- a/assets/tests/get_component/component_no_component_data.rhai +++ b/assets/tests/get_component/component_no_component_data.rhai @@ -3,4 +3,4 @@ let entity = world._get_entity_with_test_component.call("CompWithDefault"); let retrieved = world.get_component.call(entity, component); assert(type_of(retrieved) != "()", "Component was not found"); -assert(retrieved["_0"] == "Initial Value", "Component data was not retrieved correctly, retrieved._0 was: " + retrieved["_0"]); \ No newline at end of file +assert(retrieved[0] == "Initial Value", "Component data was not retrieved correctly, retrieved._0 was: " + retrieved[0]); \ No newline at end of file diff --git a/assets/tests/get_resource/no_resource_data_returns_resource.lua b/assets/tests/get_resource/no_resource_data_returns_resource.lua index fd7e2ff7cc..3b161b82e6 100644 --- a/assets/tests/get_resource/no_resource_data_returns_resource.lua +++ b/assets/tests/get_resource/no_resource_data_returns_resource.lua @@ -2,4 +2,4 @@ local resource = world.get_type_by_name("ResourceWithDefault") local retrieved = world.get_resource(resource) assert(retrieved ~= nil, "Resource should exist") -assert(retrieved._1 == "Initial Value", "Resource should have default value but got: " .. retrieved._1) +assert(retrieved[1] == "Initial Value", "Resource should have default value but got: " .. retrieved[1]) diff --git a/assets/tests/get_resource/no_resource_data_returns_resource.rhai b/assets/tests/get_resource/no_resource_data_returns_resource.rhai index 3c172e5ac3..27a9f6feef 100644 --- a/assets/tests/get_resource/no_resource_data_returns_resource.rhai +++ b/assets/tests/get_resource/no_resource_data_returns_resource.rhai @@ -2,4 +2,4 @@ let resource = world.get_type_by_name.call("ResourceWithDefault"); let retrieved = world.get_resource.call(resource); assert(type_of(retrieved) != "()", "Resource should exist"); -assert(retrieved["_0"] == "Initial Value", "Resource should have default value but got: " + retrieved["_0"]); +assert(retrieved[0] == "Initial Value", "Resource should have default value but got: " + retrieved[0]); diff --git a/assets/tests/query/query_can_access_components.lua b/assets/tests/query/query_can_access_components.lua index 72dc54ef54..6b181993ee 100644 --- a/assets/tests/query/query_can_access_components.lua +++ b/assets/tests/query/query_can_access_components.lua @@ -13,14 +13,14 @@ world.insert_component(entity_a, componentC, construct(componentC, { local query_result = world.query():component(componentA):component(componentA):component(componentC):build() assert(#query_result == 1, "Expected 1 result, got " .. #query_result) -for i,result in pairs(query_result) do +for i, result in pairs(query_result) do assert(result:entity():index() == entity_a:index(), "Expected entity_a, got " .. result:entity():index()) components = result:components() assert(#components == 3, "Expected 3 components, got " .. #components) A = components[1] B = components[2] C = components[3] - assert(A._1 == "Default", "Expected 'Default', got: " .. A._1) - assert(B._1 == "Default", "Expected 'Default', got: " .. B._1) + assert(A[1] == "Default", "Expected 'Default', got: " .. A[1]) + assert(B[1] == "Default", "Expected 'Default', got: " .. B[1]) assert(C.strings[1] == "asd", "Expected 'asd', got: " .. C.strings[1]) end diff --git a/assets/tests/query/query_can_access_components.rhai b/assets/tests/query/query_can_access_components.rhai index 335c17fb6a..03b5b0a93f 100644 --- a/assets/tests/query/query_can_access_components.rhai +++ b/assets/tests/query/query_can_access_components.rhai @@ -19,7 +19,7 @@ for (result, i) in query_result { let A = components[0]; let B = components[1]; let C = components[2]; - assert(A["_0"] == "Default", "Expected 'Default', got: " + A["_0"]); - assert(B["_0"] == "Default", "Expected 'Default', got: " + B["_0"]); + assert(A[0] == "Default", "Expected 'Default', got: " + A[0]); + assert(B[0] == "Default", "Expected 'Default', got: " + B[0]); assert(C.strings[0] == "asd", "Expected 'asd', got: " + C.strings[0]); } \ No newline at end of file diff --git a/assets/tests/set/set_primitives_works.lua b/assets/tests/set/set_primitives_works.lua index cc35c7764e..83e1fc8344 100644 --- a/assets/tests/set/set_primitives_works.lua +++ b/assets/tests/set/set_primitives_works.lua @@ -1,4 +1,5 @@ local Resource = world.get_type_by_name("TestResourceWithVariousFields") +local SimpleType = types.SimpleType local resource = world.get_resource(Resource) resource.string = "Hello, World!" @@ -7,6 +8,7 @@ resource.int = 42 resource.float = 3.0 resource.vec_usize = { 1, 2 } resource.string_map = { foo = "hello", zoo = "world" } +resource.string_set = { "foo", "zoo" } assert(resource.string == "Hello, World!", "Expected 'Hello, World!', got " .. resource.string) assert(resource.bool == true, "Expected true, got " .. tostring(resource.bool)) @@ -14,8 +16,14 @@ assert(resource.int == 42, "Expected 42, got " .. resource.int) assert(resource.float == 3.0, "Expected 3.14, got " .. resource.float) assert(resource.vec_usize[1] == 1, "Expected 1, got " .. resource.vec_usize[1]) assert(resource.string_map:len() == 2, "Expected 2, got " .. resource.string_map:len()) --- assert(resource.string_map["foo"] == "hello", "Expected 'hello', got " .. resource.string_map["foo"]) --- assert(resource.string_map["zoo"] == "world", "Expected 'world', got " .. resource.string_map["zoo"]) +assert(resource.string_map["foo"] == "hello", "Expected 'hello', got " .. resource.string_map["foo"]) +assert(resource.string_map["zoo"] == "world", "Expected 'world', got " .. resource.string_map["zoo"]) +assert(resource.string_set["foo"] ~= nil, "Expected something, got " .. resource.string_set["foo"]) +local key_simple_type = construct(SimpleType, { + inner = "foo" +}) +assert(resource.simple_type_map[key_simple_type], + "Expected 'bar', got " .. tostring(resource.simple_type_map[key_simple_type])) resource.string = "Goodbye, World!" resource.bool = false @@ -23,12 +31,14 @@ resource.int = 24 resource.float = 1.0 resource.vec_usize = { 3, 4 } resource.string_map = { foo = "goodbye", zoo = "world" } +resource.simple_type_map[key_simple_type] = "bye" assert(resource.string == "Goodbye, World!", "Expected 'Goodbye, World!', got " .. resource.string) assert(resource.bool == false, "Expected false, got " .. tostring(resource.bool)) assert(resource.int == 24, "Expected 24, got " .. resource.int) assert(resource.float == 1.0, "Expected 1.41, got " .. resource.float) assert(resource.string_map:len() == 2, "Expected 2, got " .. resource.string_map:len()) --- assert(resource.string_map["foo"] == "goodbye", "Expected 'goodbye', got " .. resource.string_map["foo"]) --- assert(resource.string_map["zoo"] == "world", "Expected 'world', got " .. resource.string_map["zoo"]) - +assert(resource.string_map["foo"] == "goodbye", "Expected 'goodbye', got " .. resource.string_map["foo"]) +assert(resource.string_map["zoo"] == "world", "Expected 'world', got " .. resource.string_map["zoo"]) +assert(resource.simple_type_map[key_simple_type], + "Expected 'bye', got " .. tostring(resource.simple_type_map[key_simple_type])) diff --git a/crates/bevy_mod_scripting_asset/src/language.rs b/crates/bevy_mod_scripting_asset/src/language.rs index fb590cfcaf..1586f45bf1 100644 --- a/crates/bevy_mod_scripting_asset/src/language.rs +++ b/crates/bevy_mod_scripting_asset/src/language.rs @@ -13,12 +13,28 @@ pub enum Language { /// The Rune scripting language Rune, /// An external scripting language - External(Cow<'static, str>), + External { + /// The identifier of the language + name: Cow<'static, str>, + /// If this language is one indexed + one_indexed: bool, + }, /// Set if none of the asset path to language mappers match #[default] Unknown, } +impl Language { + /// Returns true if the language is one-indexed and requires correction when converting + pub fn one_indexed(&self) -> bool { + match &self { + Language::Lua => true, + Language::External { one_indexed, .. } => *one_indexed, + _ => false, + } + } +} + impl std::fmt::Display for Language { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Cow::<'static, str>::from(self).fmt(f) @@ -31,7 +47,7 @@ impl From<&Language> for Cow<'static, str> { Language::Rhai => Cow::Borrowed("Rhai"), Language::Lua => Cow::Borrowed("Lua"), Language::Rune => Cow::Borrowed("Rune"), - Language::External(cow) => cow.clone(), + Language::External { name, .. } => name.clone(), Language::Unknown => Cow::Borrowed("Unknown"), } } diff --git a/crates/bevy_mod_scripting_bindings/src/conversions/mod.rs b/crates/bevy_mod_scripting_bindings/src/conversions/mod.rs new file mode 100644 index 0000000000..d5b3bcdad3 --- /dev/null +++ b/crates/bevy_mod_scripting_bindings/src/conversions/mod.rs @@ -0,0 +1,5 @@ +//! Conversions from untyped reflected values to concrete values + +mod primitive; + +pub use primitive::*; diff --git a/crates/bevy_mod_scripting_bindings/src/conversions/primitive.rs b/crates/bevy_mod_scripting_bindings/src/conversions/primitive.rs new file mode 100644 index 0000000000..73ad14fb1a --- /dev/null +++ b/crates/bevy_mod_scripting_bindings/src/conversions/primitive.rs @@ -0,0 +1,468 @@ +use std::{any::TypeId, borrow::Cow, ffi::OsString, path::PathBuf}; + +use bevy_mod_scripting_display::OrFakeId; +use bevy_reflect::PartialReflect; + +/// Attempts to convert the value given by `value` into a suitable value of the target type. +/// +/// This will match up primitive types against "similar" or "appropriate" types. +/// +/// Types which are not really considered primitives, will be converted via a downcast, meaning +/// they must be of the same type as the target, dynamics won't work. +/// +/// This operation is mostly intended to be used when primitives will generally be expected and flexibility is required. +/// +/// Note: string ref types cannot be effectively converted into their static reference versions without leaking, so they are not supported here. +pub fn convert(value: &dyn PartialReflect, target: TypeId) -> Option> { + let primitive = Primitive::from(value); + primitive.convert(target) +} + +/// A coercion primitive used for intermediate normalizations. +#[derive(Debug)] +pub enum Primitive<'a> { + /// Integer family + I(i64), + /// Unsized Integer family + U(u64), + /// Floating point family + F(f64), + /// Boolean family + B(bool), + /// String-like family + S(Cow<'a, str>), + /// Non primitive + O(&'a dyn PartialReflect), + /// A zero sized type with no value + Unit, +} + +impl<'a> Primitive<'a> { + /// Converts the primitive into the target TypeId + pub fn convert(self, target: TypeId) -> Option> { + use Primitive::*; + match self { + // Integer conversions + I(i) => { + if target == TypeId::of::() { + Some(Box::new(i as i8)) + } else if target == TypeId::of::() { + Some(Box::new(i as i16)) + } else if target == TypeId::of::() { + Some(Box::new(i as i32)) + } else if target == TypeId::of::() { + Some(Box::new(i)) + } else if target == TypeId::of::() { + Some(Box::new(i as i128)) + } else if target == TypeId::of::() { + Some(Box::new(i as isize)) + } else if target == TypeId::of::() { + Some(Box::new(i as u8)) + } else if target == TypeId::of::() { + Some(Box::new(i as u16)) + } else if target == TypeId::of::() { + Some(Box::new(i as u32)) + } else if target == TypeId::of::() { + Some(Box::new(i as u64)) + } else if target == TypeId::of::() { + Some(Box::new(i as u128)) + } else if target == TypeId::of::() { + Some(Box::new(i as usize)) + } else if target == TypeId::of::() { + Some(Box::new(i as f32)) + } else if target == TypeId::of::() { + Some(Box::new(i as f64)) + } else { + None + } + } + + // Unsigned integers + U(u) => { + if target == TypeId::of::() { + Some(Box::new(u as i8)) + } else if target == TypeId::of::() { + Some(Box::new(u as i16)) + } else if target == TypeId::of::() { + Some(Box::new(u as i32)) + } else if target == TypeId::of::() { + Some(Box::new(u as i64)) + } else if target == TypeId::of::() { + Some(Box::new(u as i128)) + } else if target == TypeId::of::() { + Some(Box::new(u as isize)) + } else if target == TypeId::of::() { + Some(Box::new(u as u8)) + } else if target == TypeId::of::() { + Some(Box::new(u as u16)) + } else if target == TypeId::of::() { + Some(Box::new(u as u32)) + } else if target == TypeId::of::() { + Some(Box::new(u)) + } else if target == TypeId::of::() { + Some(Box::new(u as u128)) + } else if target == TypeId::of::() { + Some(Box::new(u as usize)) + } else if target == TypeId::of::() { + Some(Box::new(u as f32)) + } else if target == TypeId::of::() { + Some(Box::new(u as f64)) + } else { + None + } + } + + // Floating point conversions + F(f) => { + if target == TypeId::of::() { + Some(Box::new(f as f32)) + } else if target == TypeId::of::() { + Some(Box::new(f)) + } else if target == TypeId::of::() { + Some(Box::new(f as i8)) + } else if target == TypeId::of::() { + Some(Box::new(f as i16)) + } else if target == TypeId::of::() { + Some(Box::new(f as i32)) + } else if target == TypeId::of::() { + Some(Box::new(f as i64)) + } else if target == TypeId::of::() { + Some(Box::new(f as i128)) + } else if target == TypeId::of::() { + Some(Box::new(f as isize)) + } else if target == TypeId::of::() { + Some(Box::new(f as u8)) + } else if target == TypeId::of::() { + Some(Box::new(f as u16)) + } else if target == TypeId::of::() { + Some(Box::new(f as u32)) + } else if target == TypeId::of::() { + Some(Box::new(f as u64)) + } else if target == TypeId::of::() { + Some(Box::new(f as u128)) + } else if target == TypeId::of::() { + Some(Box::new(f as usize)) + } else { + None + } + } + + // Boolean conversions + B(b) => { + if target == TypeId::of::() { + Some(Box::new(b)) + } else if target == TypeId::of::() { + Some(Box::new(if b { 1 } else { 0 })) + } else if target == TypeId::of::() { + Some(Box::new(if b { 1u64 } else { 0u64 })) + } else if target == TypeId::of::() { + Some(Box::new(if b { 1.0 } else { 0.0 })) + } else { + None + } + } + + // Strings + S(s) => { + if target == TypeId::of::() { + Some(Box::new(String::from(s))) + } else if target == TypeId::of::() { + Some(Box::new(PathBuf::from(s.into_owned()))) + } else if target == TypeId::of::() { + Some(Box::new(OsString::from(s.into_owned()))) + } else { + None + } + } + + // Non-primitives: only downcast if type matches exactly + O(o) => { + if o.get_represented_type_info() + .map(|info| info.type_id()) + .or_fake_id() + == target + { + let err = o.reflect_clone(); + Some(err.ok()?) + } else { + None + } + } + + Unit => { + if target == TypeId::of::<()>() { + Some(Box::new(())) + } else { + None + } + } + } + } +} + +impl<'a> From<&'a dyn PartialReflect> for Primitive<'a> { + fn from(v: &'a dyn PartialReflect) -> Self { + let t = v + .get_represented_type_info() + .map(|info| info.type_id()) + .or_fake_id(); + + if t == TypeId::of::() + && let Some(v) = v.try_downcast_ref::() + { + Primitive::U(*v as u64) + } else if t == TypeId::of::() + && let Some(v) = v.try_downcast_ref::() + { + Primitive::I(*v as i64) + } else if t == TypeId::of::() + && let Some(v) = v.try_downcast_ref::() + { + Primitive::B(*v) + } else if t == TypeId::of::() + && let Some(v) = v.try_downcast_ref::() + { + Primitive::U(*v as u64) + } else if t == TypeId::of::() + && let Some(v) = v.try_downcast_ref::() + { + Primitive::U(*v as u64) + } else if t == TypeId::of::() + && let Some(v) = v.try_downcast_ref::() + { + Primitive::U(*v as u64) + } else if t == TypeId::of::() + && let Some(v) = v.try_downcast_ref::() + { + Primitive::U(*v) + } else if t == TypeId::of::() + && let Some(v) = v.try_downcast_ref::() + { + Primitive::U(*v as u64) + } else if t == TypeId::of::() + && let Some(v) = v.try_downcast_ref::() + { + Primitive::I(*v as i64) + } else if t == TypeId::of::() + && let Some(v) = v.try_downcast_ref::() + { + Primitive::I(*v as i64) + } else if t == TypeId::of::() + && let Some(v) = v.try_downcast_ref::() + { + Primitive::I(*v as i64) + } else if t == TypeId::of::() + && let Some(v) = v.try_downcast_ref::() + { + Primitive::I(*v) + } else if t == TypeId::of::() + && let Some(v) = v.try_downcast_ref::() + { + Primitive::I(*v as i64) + } else if t == TypeId::of::() + && let Some(v) = v.try_downcast_ref::() + { + Primitive::F(*v as f64) + } else if t == TypeId::of::() + && let Some(v) = v.try_downcast_ref::() + { + Primitive::F(*v) + } else if t == TypeId::of::>() + && let Some(v) = v.try_downcast_ref::>() + { + Primitive::S(v.clone()) + } else if t == TypeId::of::() + && let Some(v) = v.try_downcast_ref::() + { + Primitive::S(Cow::Borrowed(v.as_str())) + } else if t == TypeId::of::<&'static str>() + && let Some(v) = v.try_downcast_ref::<&'static str>() + { + Primitive::S(Cow::Borrowed(*v)) + } else if t == TypeId::of::() + && let Some(v) = v.try_downcast_ref::() + { + Primitive::S(v.to_string_lossy()) + } else if t == TypeId::of::() + && let Some(v) = v.try_downcast_ref::() + { + Primitive::S(v.as_os_str().to_string_lossy()) + } else if t == TypeId::of::<()>() + && let Some(_) = v.try_downcast_ref::<()>() + { + Primitive::Unit + } else { + Primitive::O(v) + } + } +} + +#[cfg(test)] +mod tests { + use bevy_reflect::Reflect; + + use super::*; + + #[test] + fn test_integer_conversions() { + let i: i64 = 42; + let value: &dyn PartialReflect = &i; + + // i64 -> other integer types + let targets: &[(TypeId, i64)] = &[ + (TypeId::of::(), 42), + (TypeId::of::(), 42), + (TypeId::of::(), 42), + (TypeId::of::(), 42), + (TypeId::of::(), 42), + (TypeId::of::(), 42), + (TypeId::of::(), 42), + (TypeId::of::(), 42), + (TypeId::of::(), 42), + (TypeId::of::(), 42), + (TypeId::of::(), 42), + (TypeId::of::(), 42), + ]; + + for (ty, expected) in targets { + let boxed = convert(value, *ty).unwrap(); + let downcasted = match *ty { + t if t == TypeId::of::() => *boxed.try_downcast_ref::().unwrap() as i64, + t if t == TypeId::of::() => *boxed.try_downcast_ref::().unwrap() as i64, + t if t == TypeId::of::() => *boxed.try_downcast_ref::().unwrap() as i64, + t if t == TypeId::of::() => *boxed.try_downcast_ref::().unwrap(), + t if t == TypeId::of::() => *boxed.try_downcast_ref::().unwrap() as i64, + t if t == TypeId::of::() => { + *boxed.try_downcast_ref::().unwrap() as i64 + } + t if t == TypeId::of::() => *boxed.try_downcast_ref::().unwrap() as i64, + t if t == TypeId::of::() => *boxed.try_downcast_ref::().unwrap() as i64, + t if t == TypeId::of::() => *boxed.try_downcast_ref::().unwrap() as i64, + t if t == TypeId::of::() => *boxed.try_downcast_ref::().unwrap() as i64, + t if t == TypeId::of::() => *boxed.try_downcast_ref::().unwrap() as i64, + t if t == TypeId::of::() => { + *boxed.try_downcast_ref::().unwrap() as i64 + } + _ => panic!("Unexpected type"), + }; + assert_eq!(downcasted, *expected); + } + } + + #[test] + fn test_float_conversions() { + let f: f64 = 3.5; + let value: &dyn PartialReflect = &f; + + let targets: &[(TypeId, f64)] = &[ + (TypeId::of::(), 3.5), // f32 precision + (TypeId::of::(), 3.5), + (TypeId::of::(), 3.00), + (TypeId::of::(), 3.00), + (TypeId::of::(), 3.00), + (TypeId::of::(), 3.00), + ]; + + for (ty, expected) in targets { + let boxed = convert(value, *ty).unwrap(); + let val = if *ty == TypeId::of::() { + *boxed.try_downcast_ref::().unwrap() as f64 + } else if *ty == TypeId::of::() { + *boxed.try_downcast_ref::().unwrap() + } else if *ty == TypeId::of::() { + *boxed.try_downcast_ref::().unwrap() as f64 + } else if *ty == TypeId::of::() { + *boxed.try_downcast_ref::().unwrap() as f64 + } else if *ty == TypeId::of::() { + *boxed.try_downcast_ref::().unwrap() as f64 + } else if *ty == TypeId::of::() { + *boxed.try_downcast_ref::().unwrap() as f64 + } else { + panic!("Unexpected type"); + }; + assert_eq!(val, *expected); + } + } + + #[test] + fn test_boolean_conversions() { + let b = true; + let value: &dyn PartialReflect = &b; + + let targets: &[(TypeId, bool)] = &[(TypeId::of::(), true)]; + + for (ty, expected) in targets { + let boxed = convert(value, *ty).unwrap(); + let val = *boxed.try_downcast_ref::().unwrap(); + assert_eq!(val, *expected); + } + } + + #[test] + fn test_string_conversions() { + let s = "hello".to_string(); + let value: &dyn PartialReflect = &s; + + let targets: &[(TypeId, Option<&str>)] = &[ + (TypeId::of::(), Some("hello")), + (TypeId::of::<&'static str>(), None), + (TypeId::of::>(), None), + (TypeId::of::(), Some("hello")), + (TypeId::of::(), Some("hello")), + ]; + + for (ty, expected) in targets { + let boxed = convert(value, *ty); + if *ty == TypeId::of::() { + assert_eq!( + boxed.unwrap().try_downcast_ref::().unwrap(), + expected.unwrap() + ); + } else if *ty == TypeId::of::<&'static str>() || *ty == TypeId::of::>() { + assert!(boxed.is_none()); + } else if *ty == TypeId::of::() { + assert_eq!( + boxed + .unwrap() + .try_downcast_ref::() + .unwrap() + .to_str(), + *expected + ); + } else if *ty == TypeId::of::() { + assert_eq!( + boxed + .unwrap() + .try_downcast_ref::() + .unwrap() + .to_str(), + *expected + ); + } + } + } + + #[test] + fn test_unit_conversion() { + let unit: &dyn PartialReflect = &(); + let boxed = convert(unit, TypeId::of::<()>()).unwrap(); + assert!(boxed.try_downcast_ref::<()>().is_some()); + } + + #[test] + fn test_nonprimitive_downcast() { + #[derive(Reflect)] + struct MyStruct(u32); + + let s = MyStruct(10); + let value: &dyn PartialReflect = &s; + + // Exact type match works + let boxed = convert(value, TypeId::of::()).unwrap(); + let val = boxed.try_downcast_ref::().unwrap(); + assert_eq!(val.0, 10); + + // Mismatched type fails + assert!(convert(value, TypeId::of::()).is_none()); + } +} diff --git a/crates/bevy_mod_scripting_bindings/src/error.rs b/crates/bevy_mod_scripting_bindings/src/error.rs index 8eb7b909ca..6249b917b0 100644 --- a/crates/bevy_mod_scripting_bindings/src/error.rs +++ b/crates/bevy_mod_scripting_bindings/src/error.rs @@ -9,7 +9,7 @@ use bevy_mod_scripting_display::{ DebugWithTypeInfo, DisplayWithTypeInfo, GetTypeInfo, OrFakeId, PrintReflectAsDebug, WithTypeInfo, }; -use bevy_reflect::{ApplyError, PartialReflect, Reflect, ReflectPathError}; +use bevy_reflect::{ApplyError, PartialReflect, Reflect}; use std::{any::TypeId, borrow::Cow, error::Error, fmt::Display, panic::Location, sync::Arc}; /// A wrapper around a reflect value to implement various traits useful for error reporting. @@ -389,12 +389,9 @@ impl InteropError { } /// Creates a new reflection path error. - pub fn reflection_path_error<'a>( - error: ReflectPathError<'a>, - reflected: Option, - ) -> Self { + pub fn reflection_path_error(error: String, reflected: Option) -> Self { Self::ReflectionPathError { - error: Box::new(error.to_string()), + error: Box::new(error), reflected: reflected.map(Box::new), } } diff --git a/crates/bevy_mod_scripting_bindings/src/function/from.rs b/crates/bevy_mod_scripting_bindings/src/function/from.rs index 0ef3d713c7..b03b493c4d 100644 --- a/crates/bevy_mod_scripting_bindings/src/function/from.rs +++ b/crates/bevy_mod_scripting_bindings/src/function/from.rs @@ -3,7 +3,7 @@ use crate::{ ReflectReference, ScriptValue, WorldGuard, access_map::ReflectAccessId, error::InteropError, }; -use bevy_platform::collections::HashMap; +use bevy_platform::collections::{HashMap, HashSet}; use bevy_reflect::{FromReflect, Reflect}; use std::{ any::TypeId, @@ -254,7 +254,7 @@ impl FromScript for Ref<'_, T> { if world.claim_read_access(raid) { // Safety: we just claimed access - let ref_ = unsafe { reflect_reference.reflect_unsafe(world) }?; + let ref_ = unsafe { reflect_reference.reflect_unsafe_non_empty(world) }?; let cast = ref_.try_downcast_ref::().ok_or_else(|| { InteropError::type_mismatch( std::any::TypeId::of::(), @@ -327,7 +327,7 @@ impl FromScript for Mut<'_, T> { if world.claim_write_access(raid) { // Safety: we just claimed write access - let ref_ = unsafe { reflect_reference.reflect_mut_unsafe(world) }?; + let ref_ = unsafe { reflect_reference.reflect_mut_unsafe_non_empty(world) }?; let type_id = ref_.get_represented_type_info().map(|i| i.type_id()); let cast = ref_.try_downcast_mut::().ok_or_else(|| { InteropError::type_mismatch(std::any::TypeId::of::(), type_id) @@ -490,6 +490,45 @@ macro_rules! impl_from_script_hashmap { impl_from_script_hashmap!(HashMap); impl_from_script_hashmap!(std::collections::HashMap); +macro_rules! impl_from_script_hashset { + ($hashset_type:path) => { + #[profiling::all_functions] + impl FromScript for $hashset_type + where + V: FromScript + Eq + std::hash::Hash + 'static, + for<'w> V::This<'w>: Into, + { + type This<'w> = Self; + #[profiling::function] + fn from_script(value: ScriptValue, world: WorldGuard) -> Result { + match value { + ScriptValue::Map(map) => { + let mut hashmap = <$hashset_type>::new(); + for (_, value) in map { + hashmap.insert(V::from_script(value, world.clone())?.into()); + } + Ok(hashmap) + } + ScriptValue::List(list) => { + let mut hashmap = <$hashset_type>::new(); + for elem in list { + let key = ::from_script(elem, world.clone())?; + hashmap.insert(key.into()); + } + Ok(hashmap) + } + _ => Err(InteropError::value_mismatch( + std::any::TypeId::of::<$hashset_type>(), + value, + )), + } + } + } + }; +} + +impl_from_script_hashset![HashSet]; + /// A union of two or more (by nesting unions) types. pub struct Union(Result); diff --git a/crates/bevy_mod_scripting_bindings/src/function/from_ref.rs b/crates/bevy_mod_scripting_bindings/src/function/from_ref.rs index 0de59834f1..fee1a137fb 100644 --- a/crates/bevy_mod_scripting_bindings/src/function/from_ref.rs +++ b/crates/bevy_mod_scripting_bindings/src/function/from_ref.rs @@ -5,7 +5,8 @@ use crate::{ reflection_extensions::TypeInfoExtensions, }; use bevy_reflect::{ - DynamicEnum, DynamicList, DynamicMap, DynamicTuple, DynamicVariant, Map, PartialReflect, + DynamicEnum, DynamicList, DynamicMap, DynamicSet, DynamicTuple, DynamicVariant, Map, + PartialReflect, ReflectKind, Set, }; use std::{any::TypeId, ffi::OsString, path::PathBuf}; @@ -85,34 +86,52 @@ impl FromScriptRef for Box { return Ok(Box::new(dynamic_enum)); } - if let Some(inner_list_type) = type_info.list_inner_type() - && let ScriptValue::List(vec) = value - { - let mut dynamic_list = DynamicList::default(); - for item in vec { - let inner = Self::from_script_ref(inner_list_type, item, world.clone())?; - dynamic_list.push_box(inner); + // speed up lookups for non-complex things + if matches!( + type_info.kind(), + ReflectKind::Set | ReflectKind::List | ReflectKind::Map + ) { + if let Some(inner_list_type) = type_info.list_inner_type() + && let ScriptValue::List(vec) = value + { + let mut dynamic_list = DynamicList::default(); + for item in vec { + let inner = Self::from_script_ref(inner_list_type, item, world.clone())?; + dynamic_list.push_box(inner); + } + + dynamic_list.set_represented_type(Some(type_info)); + return Ok(Box::new(dynamic_list)); } - dynamic_list.set_represented_type(Some(type_info)); - return Ok(Box::new(dynamic_list)); - } + if let Some((key_type, val_type)) = type_info.map_inner_types() + && let ScriptValue::Map(map) = value + { + let mut dynamic_map = DynamicMap::default(); + for (key, val) in map { + let key = Self::from_script_ref( + key_type, + ScriptValue::String(key.into()), + world.clone(), + )?; + let val = Self::from_script_ref(val_type, val, world.clone())?; + dynamic_map.insert_boxed(key, val); + } + dynamic_map.set_represented_type(Some(type_info)); + return Ok(Box::new(dynamic_map)); + } - if let Some((key_type, val_type)) = type_info.map_inner_types() - && let ScriptValue::Map(map) = value - { - let mut dynamic_map = DynamicMap::default(); - for (key, val) in map { - let key = Self::from_script_ref( - key_type, - ScriptValue::String(key.into()), - world.clone(), - )?; - let val = Self::from_script_ref(val_type, val, world.clone())?; - dynamic_map.insert_boxed(key, val); + if let Some(val_type) = type_info.set_inner_type() + && let ScriptValue::List(set) = value + { + let mut dynamic_set = DynamicSet::default(); + for val in set { + let key = Self::from_script_ref(val_type, val, world.clone())?; + dynamic_set.insert_boxed(key); + } + dynamic_set.set_represented_type(Some(type_info)); + return Ok(Box::new(dynamic_set)); } - dynamic_map.set_represented_type(Some(type_info)); - return Ok(Box::new(dynamic_map)); } match value { diff --git a/crates/bevy_mod_scripting_bindings/src/function/into_ref.rs b/crates/bevy_mod_scripting_bindings/src/function/into_ref.rs index 9e50446860..411de29482 100644 --- a/crates/bevy_mod_scripting_bindings/src/function/into_ref.rs +++ b/crates/bevy_mod_scripting_bindings/src/function/into_ref.rs @@ -3,11 +3,11 @@ use std::{borrow::Cow, ffi::OsString, path::PathBuf}; use bevy_mod_scripting_display::OrFakeId; -use bevy_reflect::{Access, PartialReflect}; +use bevy_reflect::PartialReflect; use crate::{ - ReflectReference, ScriptValue, WorldGuard, error::InteropError, function::into::IntoScript, - reflection_extensions::PartialReflectExt, + ReferencePart, ReflectReference, ScriptValue, WorldGuard, error::InteropError, + function::into::IntoScript, reflection_extensions::PartialReflectExt, }; /// Converts a value represented by a reference into a [`crate::ScriptValue`]. @@ -111,7 +111,8 @@ fn into_script_ref( // either return nil or ref into if let Ok(as_option) = r.as_option() { return if let Some(s) = as_option { - self_.index_path(vec![FIRST_TUPLE_FIELD_ACCESS]); + // don't want correction to mess with this + self_.push_path(ReferencePart::IntegerAccess(0, false)); into_script_ref(self_, s, world) } else { Ok(ScriptValue::Unit) @@ -120,5 +121,3 @@ fn into_script_ref( Ok(ScriptValue::Reference(self_)) } - -const FIRST_TUPLE_FIELD_ACCESS: Access = Access::TupleIndex(0); diff --git a/crates/bevy_mod_scripting_bindings/src/function/magic_functions.rs b/crates/bevy_mod_scripting_bindings/src/function/magic_functions.rs index 54387fe8e0..992681057b 100644 --- a/crates/bevy_mod_scripting_bindings/src/function/magic_functions.rs +++ b/crates/bevy_mod_scripting_bindings/src/function/magic_functions.rs @@ -1,9 +1,9 @@ //! All the switchable special functions used by language implementors use super::{FromScriptRef, FunctionCallContext, IntoScriptRef}; -use crate::{ReflectReference, ReflectionPathExt, ScriptValue, error::InteropError}; +use crate::{ReferencePart, ReflectReference, ScriptValue, error::InteropError}; use bevy_mod_scripting_derive::DebugWithTypeInfo; use bevy_mod_scripting_display::OrFakeId; -use bevy_reflect::{ParsedPath, PartialReflect}; +use bevy_reflect::PartialReflect; /// A list of magic methods, these only have one replacable implementation, and apply to all `ReflectReferences`. /// It's up to the language implementer to call these in the right order (after any type specific overrides). @@ -65,12 +65,19 @@ impl MagicFunctions { mut reference: ReflectReference, key: ScriptValue, ) -> Result { - let mut path: ParsedPath = key.try_into()?; - if ctxt.convert_to_0_indexed() { - path.convert_to_0_indexed(); - } - reference.index_path(path); let world = ctxt.world()?; + + let path: ReferencePart = + ReferencePart::new_from_script_val(key, ctxt.language(), Some(world.clone())).map_err( + |e| InteropError::InvalidIndex { + index: Box::new(e), + reason: Box::new("Cannot convert to valid reflection path".to_owned()), + }, + )?; + reference + .reflect_path + .set_is_one_indexed(ctxt.convert_to_0_indexed()); + reference.push_path(path); ReflectReference::into_script_ref(reference, world) } @@ -91,11 +98,17 @@ impl MagicFunctions { value: ScriptValue, ) -> Result<(), InteropError> { let world = ctxt.world()?; - let mut path: ParsedPath = key.try_into()?; - if ctxt.convert_to_0_indexed() { - path.convert_to_0_indexed(); - } - reference.index_path(path); + let path: ReferencePart = + ReferencePart::new_from_script_val(key, ctxt.language(), Some(world.clone())).map_err( + |e| InteropError::InvalidIndex { + index: Box::new(e), + reason: Box::new("Cannot convert to valid reflection path".to_owned()), + }, + )?; + reference + .reflect_path + .set_is_one_indexed(ctxt.convert_to_0_indexed()); + reference.push_path(path); reference.with_reflect_mut(world.clone(), |r| { let target_type_id = r .get_represented_type_info() diff --git a/crates/bevy_mod_scripting_bindings/src/function/script_function.rs b/crates/bevy_mod_scripting_bindings/src/function/script_function.rs index aade83e584..65bac5f3a9 100644 --- a/crates/bevy_mod_scripting_bindings/src/function/script_function.rs +++ b/crates/bevy_mod_scripting_bindings/src/function/script_function.rs @@ -60,7 +60,7 @@ impl FunctionCallContext { /// Whether the caller uses 1-indexing on all indexes and expects 0-indexing conversions to be performed. #[profiling::function] pub fn convert_to_0_indexed(&self) -> bool { - matches!(&self.language, Language::Lua) + self.language.one_indexed() } /// Gets the scripting language of the caller diff --git a/crates/bevy_mod_scripting_bindings/src/lib.rs b/crates/bevy_mod_scripting_bindings/src/lib.rs index b22483c870..8182b8b5f0 100644 --- a/crates/bevy_mod_scripting_bindings/src/lib.rs +++ b/crates/bevy_mod_scripting_bindings/src/lib.rs @@ -2,11 +2,12 @@ pub mod access_map; pub mod allocator; +pub mod conversions; pub mod docgen; pub mod error; pub mod function; pub mod globals; -// pub mod pretty_print; +pub mod path; pub mod query; pub mod reference; pub mod reflection_extensions; @@ -23,6 +24,8 @@ pub use error::*; pub use function::*; pub use globals::*; // pub use pretty_print::*; +pub use conversions::*; +pub use path::*; pub use query::*; pub use reference::*; pub use reflection_extensions::*; diff --git a/crates/bevy_mod_scripting_bindings/src/path/mod.rs b/crates/bevy_mod_scripting_bindings/src/path/mod.rs new file mode 100644 index 0000000000..e8bbed1c94 --- /dev/null +++ b/crates/bevy_mod_scripting_bindings/src/path/mod.rs @@ -0,0 +1,348 @@ +//! BMS native reflection paths + +use std::{borrow::Cow, fmt::Display}; + +use bevy_mod_scripting_asset::Language; +use bevy_mod_scripting_derive::DebugWithTypeInfo; +use bevy_mod_scripting_display::{DisplayWithTypeInfo, GetTypeInfo}; +use bevy_reflect::{PartialReflect, ReflectMut, ReflectRef, TypeInfo, TypeRegistry}; + +use crate::{ScriptValue, WorldGuard, convert}; + +/// A key referencing into a `Reflect` supporting trait object. +#[derive(DebugWithTypeInfo)] +#[debug_with_type_info(bms_display_path = "bevy_mod_scripting_display")] +pub enum ReferencePart { + /// A string labelled reference + StringAccess(Cow<'static, str>), + /// An integer labelled reference, with optional indexing correction + IntegerAccess(i64, bool), + /// A key to a map or set, + MapAccess(Box), +} + +impl Clone for ReferencePart { + fn clone(&self) -> Self { + match self { + Self::StringAccess(arg0) => Self::StringAccess(arg0.clone()), + Self::IntegerAccess(arg0, arg1) => Self::IntegerAccess(*arg0, *arg1), + Self::MapAccess(arg0) => Self::MapAccess(match arg0.reflect_clone() { + Ok(c) => c, + Err(_) => arg0.to_dynamic(), // this is okay, because we need to call FromReflect on the map side anyway + }), + } + } +} + +impl PartialEq for ReferencePart { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::StringAccess(l0), Self::StringAccess(r0)) => l0 == r0, + (Self::IntegerAccess(l0, l1), Self::IntegerAccess(r0, r1)) => l0 == r0 && l1 == r1, + (Self::MapAccess(l0), Self::MapAccess(r0)) => { + l0.reflect_partial_eq(r0.as_ref()).unwrap_or(false) + } + _ => false, + } + } +} + +impl Display for ReferencePart { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ReferencePart::StringAccess(cow) => { + f.write_str("[\"")?; + f.write_str(cow)?; + f.write_str("\"]") + } + ReferencePart::IntegerAccess(int, correction) => { + f.write_str("[")?; + f.write_str(&if *correction { *int - 1 } else { *int }.to_string())?; + f.write_str("]") + } + ReferencePart::MapAccess(partial_reflect) => { + f.write_str("[")?; + partial_reflect.debug(f)?; + f.write_str("]") + } + } + } +} + +#[allow(clippy::result_unit_err, reason = "it works better")] +impl ReferencePart { + /// Expect this reference to be a string, and return it if it is + pub fn expect_string(&self) -> Result<&str, ()> { + match self { + ReferencePart::StringAccess(cow) => Ok(cow.as_ref()), + _ => Err(()), + } + } + + /// Expect this reference to be an integer, and return it if it is + pub fn expect_integer(&self, correct_indexing: bool) -> Result { + match self { + ReferencePart::IntegerAccess(index, correction) => { + Ok(if *correction && correct_indexing { + *index - 1 + } else { + *index + }) + } + _ => Err(()), + } + } + + /// Casts the keys into a partial reflect value regardless of type of access + pub fn with_any O>( + &self, + correct_indexing: bool, + f: F, + ) -> O { + match self { + ReferencePart::StringAccess(cow) => f(cow), + ReferencePart::IntegerAccess(index, correction) => { + f(&(if *correction && correct_indexing { + *index - 1 + } else { + *index + })) + } + ReferencePart::MapAccess(partial_reflect) => f(partial_reflect.as_ref()), + } + } + + /// Converts a [`ScriptValue`] to a indexing corrected reference part. + /// + /// If given a world guard also supports arbitrary references as keys + pub fn new_from_script_val( + value: ScriptValue, + language: Language, + world: Option, + ) -> Result { + Ok(match value { + ScriptValue::Integer(v) => Self::IntegerAccess(v, language.one_indexed()), + ScriptValue::Float(v) => Self::IntegerAccess( + v.max(i64::MIN as f64).min(i64::MAX as f64).round() as i64, + language.one_indexed(), + ), + ScriptValue::String(cow) => Self::StringAccess(cow), + ScriptValue::Reference(_ref) => world + .and_then(|world| Some(Self::MapAccess(_ref.to_owned_value(world).ok()?))) + .ok_or_else(|| ScriptValue::Reference(_ref))?, + _ => return Err(value), + }) + } + + /// Tries to reference into the given root object with the current reference part + pub fn reflect_element<'a>( + &self, + elem: &'a dyn PartialReflect, + _type_registry: &TypeRegistry, + one_indexed: bool, + ) -> Result, ()> { + Ok(match elem.reflect_ref() { + ReflectRef::Struct(x) => x.field(self.expect_string()?), + ReflectRef::TupleStruct(x) => x.field(self.expect_integer(one_indexed)? as usize), + ReflectRef::Tuple(x) => x.field(self.expect_integer(one_indexed)? as usize), + ReflectRef::List(x) => x.get(self.expect_integer(one_indexed)? as usize), + ReflectRef::Array(x) => x.get(self.expect_integer(one_indexed)? as usize), + ReflectRef::Enum(x) => match x.variant_type() { + bevy_reflect::VariantType::Struct => x.field(self.expect_string()?), + bevy_reflect::VariantType::Tuple => { + x.field_at(self.expect_integer(one_indexed)? as usize) + } + bevy_reflect::VariantType::Unit => return Err(()), + }, + ReflectRef::Map(x) => { + let id = x.get_represented_map_info().ok_or(())?.key_ty().id(); + self.with_any(one_indexed, |key| { + let coerced = convert(key, id).ok_or(())?; + Ok(x.get(coerced.as_ref())) + })? + } + ReflectRef::Set(x) => { + let id = match x.get_represented_type_info().ok_or(())? { + TypeInfo::Set(set_info) => set_info.value_ty().id(), + _ => unreachable!("impossible"), + }; + self.with_any(one_indexed, |key| { + let coerced = convert(key, id).ok_or(())?; + Ok(x.get(coerced.as_ref())) + })? + } + _ => return Err(()), + }) + } + + /// Tries to reference into the given root object with the current reference part + pub fn reflect_element_mut<'a>( + &self, + elem: &'a mut dyn PartialReflect, + _type_registry: &TypeRegistry, + one_indexed: bool, + ) -> Result, ()> { + Ok(match elem.reflect_mut() { + ReflectMut::Struct(x) => x.field_mut(self.expect_string()?), + ReflectMut::TupleStruct(x) => x.field_mut(self.expect_integer(one_indexed)? as usize), + ReflectMut::Tuple(x) => x.field_mut(self.expect_integer(one_indexed)? as usize), + ReflectMut::List(x) => x.get_mut(self.expect_integer(one_indexed)? as usize), + ReflectMut::Array(x) => x.get_mut(self.expect_integer(one_indexed)? as usize), + ReflectMut::Enum(x) => match x.variant_type() { + bevy_reflect::VariantType::Struct => x.field_mut(self.expect_string()?), + bevy_reflect::VariantType::Tuple => { + x.field_at_mut(self.expect_integer(one_indexed)? as usize) + } + bevy_reflect::VariantType::Unit => return Err(()), + }, + ReflectMut::Map(x) => { + let id = x.get_represented_map_info().ok_or(())?.key_ty().id(); + self.with_any(one_indexed, |key| { + let coerced = convert(key, id).ok_or(())?; + Ok(x.get_mut(coerced.as_ref())) + })? + } + // ReflectMut::Set(x) => {} // no get_mut is available + _ => return Err(()), + }) + } +} + +/// A collection of references into a `Reflect` supporting trait object in series. +#[derive(DebugWithTypeInfo, Clone, PartialEq, Default)] +#[debug_with_type_info(bms_display_path = "bevy_mod_scripting_display")] +pub struct ReferencePath { + one_indexed: bool, + path: Vec, +} + +impl ReferencePath { + /// Sets the indexing mode on the path + pub fn set_is_one_indexed(&mut self, is_one_indexed: bool) { + self.one_indexed = is_one_indexed; + } + + /// Traverses the reference path from the given root object. + pub fn reflect_element<'a>( + &self, + val: &'a dyn PartialReflect, + type_registry: &TypeRegistry, + ) -> Result, ReferencePathError> { + let mut next: &'a dyn PartialReflect = val; + for i in &self.path { + next = match i.reflect_element(next, type_registry, self.one_indexed) { + Ok(None) => return Ok(None), + Ok(Some(v)) => v, + Err(_) => { + return Err(ReferencePathError { + val: next.get_represented_type_info(), + part: i.clone(), + }); + } + }; + } + Ok(Some(next)) + } + + /// Traverses the reference path from the given root object. + pub fn reflect_element_mut<'a>( + &self, + val: &'a mut dyn PartialReflect, + type_registry: &TypeRegistry, + ) -> Result, ReferencePathError> { + let mut next: &'a mut dyn PartialReflect = val; + for i in &self.path { + let type_info_current = next.get_represented_type_info(); + next = match i.reflect_element_mut(next, type_registry, self.one_indexed) { + Ok(None) => return Ok(None), + Ok(Some(v)) => v, + Err(_) => { + return Err(ReferencePathError { + val: type_info_current, + part: i.clone(), + }); + } + }; + } + Ok(Some(next)) + } + + /// Returns true if the underlying path has no elements + pub fn is_empty(&self) -> bool { + self.path.is_empty() + } + + /// Pushes a new reference at the end of the current reflection path. + pub fn push(&mut self, part: ReferencePart) { + self.path.push(part) + } + + /// Pushes a new reference at the end of the current reflection path. + pub fn extend(&mut self, part: impl Iterator) { + self.path.extend(part) + } +} + +impl Display for ReferencePath { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for i in &self.path { + std::fmt::Display::fmt(i, f)? + } + Ok(()) + } +} + +impl DisplayWithTypeInfo for ReferencePath { + fn display_with_type_info( + &self, + f: &mut std::fmt::Formatter<'_>, + _type_info_provider: Option<&dyn GetTypeInfo>, + ) -> std::fmt::Result { + std::fmt::Display::fmt(self, f) + } +} + +/// Errors to do with custom BMS reference path resolution. +pub struct ReferencePathError { + val: Option<&'static TypeInfo>, + part: ReferencePart, +} + +impl Display for ReferencePathError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("Cannot reflect into ")?; + f.write_str(match self.val { + Some(TypeInfo::Struct(_)) => "struct", + Some(TypeInfo::TupleStruct(_)) => "tuple truct", + Some(TypeInfo::Tuple(_)) => "tuple", + Some(TypeInfo::List(_)) => "list", + Some(TypeInfo::Array(_)) => "array", + Some(TypeInfo::Map(_)) => "map", + Some(TypeInfo::Set(_)) => "set", + Some(TypeInfo::Enum(_)) => "enum", + Some(TypeInfo::Opaque(_)) => "opaque type", + None => "unknown type", + })?; + + f.write_str(" of type: ")?; + f.write_str( + self.val + .map(|t| t.type_path_table().path()) + .unwrap_or("unknown type"), + )?; + + f.write_str(" with ")?; + + f.write_str(match &self.part { + ReferencePart::StringAccess(_) => "string key", + ReferencePart::IntegerAccess(_, _) => "integer key", + ReferencePart::MapAccess(_) => "map key", + })?; + + f.write_str(": `")?; + std::fmt::Display::fmt(&self.part, f)?; + f.write_str("`")?; + + Ok(()) + } +} diff --git a/crates/bevy_mod_scripting_bindings/src/query.rs b/crates/bevy_mod_scripting_bindings/src/query.rs index 899c470257..16623456e0 100644 --- a/crates/bevy_mod_scripting_bindings/src/query.rs +++ b/crates/bevy_mod_scripting_bindings/src/query.rs @@ -12,7 +12,7 @@ use ::{ reflect::ReflectComponent, world::World, }, - bevy_reflect::{ParsedPath, Reflect, TypeRegistration}, + bevy_reflect::{Reflect, TypeRegistration}, }; use std::{any::TypeId, collections::VecDeque, ptr::NonNull, sync::Arc}; @@ -352,7 +352,7 @@ impl WorldAccessGuard<'_> { type_id: c.type_registration().type_id(), base_id: super::ReflectBase::Component(r.id(), c.component_id()), }, - reflect_path: ParsedPath(vec![]), + reflect_path: Default::default(), }) .collect(); ScriptQueryResult { diff --git a/crates/bevy_mod_scripting_bindings/src/reference.rs b/crates/bevy_mod_scripting_bindings/src/reference.rs index b423f20503..46693d99e1 100644 --- a/crates/bevy_mod_scripting_bindings/src/reference.rs +++ b/crates/bevy_mod_scripting_bindings/src/reference.rs @@ -6,8 +6,9 @@ //! we need wrapper types which have owned and ref variants. use super::{WorldGuard, access_map::ReflectAccessId}; use crate::{ - ReflectAllocationId, ReflectAllocator, ThreadWorldContainer, error::InteropError, - reflection_extensions::PartialReflectExt, with_access_read, with_access_write, + ReferencePart, ReferencePath, ReflectAllocationId, ReflectAllocator, ThreadWorldContainer, + error::InteropError, reflection_extensions::PartialReflectExt, with_access_read, + with_access_write, }; use bevy_asset::{ReflectAsset, UntypedHandle}; use bevy_ecs::{component::Component, ptr::Ptr, resource::Resource}; @@ -15,7 +16,7 @@ use bevy_mod_scripting_derive::DebugWithTypeInfo; use bevy_mod_scripting_display::{ DebugWithTypeInfo, DisplayWithTypeInfo, OrFakeId, PrintReflectAsDebug, WithTypeInfo, }; -use bevy_reflect::{Access, OffsetAccess, ReflectRef}; +use bevy_reflect::{Access, OffsetAccess, ReflectRef, TypeRegistry}; use core::alloc; use std::{ any::{Any, TypeId}, @@ -26,9 +27,7 @@ use { change_detection::MutUntyped, component::ComponentId, entity::Entity, world::unsafe_world_cell::UnsafeWorldCell, }, - bevy_reflect::{ - ParsedPath, PartialReflect, Reflect, ReflectFromPtr, ReflectPath, prelude::ReflectDefault, - }, + bevy_reflect::{ParsedPath, PartialReflect, Reflect, ReflectFromPtr, prelude::ReflectDefault}, }; /// A reference to an arbitrary reflected instance. @@ -40,7 +39,7 @@ use { /// - The path, which specifies how to access the value from the base. /// /// Bindings defined on this type, apply to ALL references. -#[derive(Clone, PartialEq, Eq, Reflect, DebugWithTypeInfo)] +#[derive(Clone, PartialEq, Reflect, DebugWithTypeInfo)] #[reflect(Default, opaque)] #[debug_with_type_info(bms_display_path = "bevy_mod_scripting_display")] #[non_exhaustive] @@ -50,7 +49,7 @@ pub struct ReflectReference { // TODO: experiment with Fixed capacity vec, boxed array etc, compromise between heap allocation and runtime cost // needs benchmarks first though /// The path from the top level type to the actual value we want to access - pub reflect_path: ParsedPath, + pub reflect_path: ReferencePath, } impl DisplayWithTypeInfo for ReflectReference { @@ -99,7 +98,7 @@ impl Default for ReflectReference { type_id: None::.or_fake_id(), base_id: ReflectBase::Owned(ReflectAllocationId::new(0)), }, - reflect_path: ParsedPath(vec![]), + reflect_path: Default::default(), } } } @@ -164,7 +163,7 @@ impl ReflectReference { type_id, base_id: ReflectBase::Owned(id), }, - reflect_path: ParsedPath(Vec::default()), + reflect_path: Default::default(), } } @@ -176,7 +175,7 @@ impl ReflectReference { type_id, base_id: ReflectBase::Owned(id), }, - reflect_path: ParsedPath(Vec::default()), + reflect_path: Default::default(), } } @@ -190,7 +189,7 @@ impl ReflectReference { ) -> Result { Ok(ReflectReference { base: ReflectBaseType::new_allocated_base_partial(value, allocator)?, - reflect_path: ParsedPath(Vec::default()), + reflect_path: Default::default(), }) } @@ -201,7 +200,7 @@ impl ReflectReference { ) -> ReflectReference { ReflectReference { base: ReflectBaseType::new_allocated_base(value, allocator), - reflect_path: ParsedPath(Vec::default()), + reflect_path: Default::default(), } } @@ -209,7 +208,7 @@ impl ReflectReference { pub fn new_resource_ref(world: WorldGuard) -> Result { Ok(Self { base: ReflectBaseType::new_resource_base::(world)?, - reflect_path: ParsedPath(Vec::default()), + reflect_path: Default::default(), }) } @@ -220,7 +219,7 @@ impl ReflectReference { ) -> Result { Ok(Self { base: ReflectBaseType::new_component_base::(entity, world)?, - reflect_path: ParsedPath(Vec::default()), + reflect_path: Default::default(), }) } @@ -236,7 +235,7 @@ impl ReflectReference { type_id, base_id: ReflectBase::Component(entity, component_id), }, - reflect_path: ParsedPath(Vec::default()), + reflect_path: Default::default(), } } @@ -248,7 +247,7 @@ impl ReflectReference { type_id, base_id: ReflectBase::Resource(component_id), }, - reflect_path: ParsedPath(Vec::default()), + reflect_path: Default::default(), } } @@ -261,7 +260,7 @@ impl ReflectReference { ) -> Result { Ok(Self { base: ReflectBaseType::new_asset_base(handle, asset_type_id, world)?, - reflect_path: ParsedPath(Vec::default()), + reflect_path: Default::default(), }) } @@ -338,8 +337,14 @@ impl ReflectReference { /// Indexes into the reflect path inside this reference. /// You can use [`Self::reflect`] and [`Self::reflect_mut`] to get the actual value. - pub fn index_path>(&mut self, index: T) { - self.reflect_path.0.extend(index.into().0); + pub fn extend_path(&mut self, index: impl Iterator) { + self.reflect_path.extend(index); + } + + /// Indexes into the reflect path inside this reference. + /// You can use [`Self::reflect`] and [`Self::reflect_mut`] to get the actual value. + pub fn push_path(&mut self, index: ReferencePart) { + self.reflect_path.push(index); } /// Tries to downcast to the specified type and cloning the value if successful. @@ -406,7 +411,16 @@ impl ReflectReference { &world.inner.accesses, access_id, "could not access reflect reference", - { unsafe { self.reflect_unsafe(world.clone()) }.map(f)? } + { + f( + unsafe { self.reflect_unsafe(world.clone()) }?.ok_or_else(|| { + InteropError::reflection_path_error( + "Reference was out of bounds or value is missing".into(), + Some(self.clone()), + ) + })?, + ) + } ) } @@ -423,7 +437,16 @@ impl ReflectReference { &world.inner.accesses, access_id, "Could not access reflect reference mutably", - { unsafe { self.reflect_mut_unsafe(world.clone()) }.map(f)? } + { + f( + unsafe { self.reflect_mut_unsafe(world.clone()) }?.ok_or_else(|| { + InteropError::reflection_path_error( + "Reference was out of bounds or value is missing".into(), + Some(self.clone()), + ) + })?, + ) + } ) } @@ -460,6 +483,22 @@ impl ReflectReference { } } + /// Equivalent to [`Self::reflect_unsafe`] but expecting a non-None value from the reference, i.e. will return an Err if the reference is to a missing key in a dictionary. + /// # Safety + /// - The bounds of [`Self::reflect_unsafe`] must be upheld + pub unsafe fn reflect_unsafe_non_empty<'w>( + &self, + world: WorldGuard<'w>, + ) -> Result<&'w dyn PartialReflect, InteropError> { + let val = unsafe { self.reflect_unsafe(world) }?; + val.ok_or_else(|| { + InteropError::reflection_path_error( + "Reference out of bounds or value missing".into(), + Some(self.clone()), + ) + }) + } + /// Retrieves a reference to the underlying `dyn PartialReflect` type valid for the 'w lifetime of the world cell /// # Safety /// @@ -470,7 +509,7 @@ impl ReflectReference { pub unsafe fn reflect_unsafe<'w>( &self, world: WorldGuard<'w>, - ) -> Result<&'w dyn PartialReflect, InteropError> { + ) -> Result, InteropError> { if let ReflectBase::Owned(id) = &self.base.base_id { let allocator = world.allocator(); let allocator = allocator.read(); @@ -480,12 +519,17 @@ impl ReflectReference { .ok_or_else(|| InteropError::garbage_collected_allocation(self.clone()))?; // safety: caller promises it's fine :) - return self.walk_path(unsafe { &*arc.get_ptr() }); + let type_registry = world.type_registry(); + let type_registry = type_registry.read(); + + return self.walk_path(unsafe { &*arc.get_ptr() }, &type_registry); } if let ReflectBase::Asset(handle, _) = &self.base.base_id { let asset = unsafe { self.load_asset_mut(handle, world.clone())? }; - return self.walk_path(asset.as_partial_reflect()); + let type_registry = world.type_registry(); + let type_registry = type_registry.read(); + return self.walk_path(asset.as_partial_reflect(), &type_registry); } let type_registry = world.type_registry(); @@ -516,10 +560,23 @@ impl ReflectReference { ); let base = unsafe { from_ptr_data.as_reflect(ptr) }; + self.walk_path(base.as_partial_reflect(), &type_registry) + } - drop(type_registry); - - self.walk_path(base.as_partial_reflect()) + /// Equivalent to [`Self::reflect_mut_unsafe`] but expecting a non-None value from the reference, i.e. will return an Err if the reference is to a missing key in a dictionary. + /// # Safety + /// - The bounds of [`Self::reflect_mut_unsafe`] must be upheld + pub unsafe fn reflect_mut_unsafe_non_empty<'w>( + &self, + world: WorldGuard<'w>, + ) -> Result<&'w mut dyn PartialReflect, InteropError> { + let val = unsafe { self.reflect_mut_unsafe(world) }?; + val.ok_or_else(|| { + InteropError::reflection_path_error( + "Reference out of bounds or value missing".into(), + Some(self.clone()), + ) + }) } /// Retrieves mutable reference to the underlying `dyn PartialReflect` type valid for the 'w lifetime of the world cell @@ -531,7 +588,7 @@ impl ReflectReference { pub unsafe fn reflect_mut_unsafe<'w>( &self, world: WorldGuard<'w>, - ) -> Result<&'w mut dyn PartialReflect, InteropError> { + ) -> Result, InteropError> { if let ReflectBase::Owned(id) = &self.base.base_id { let allocator = world.allocator(); let allocator = allocator.read(); @@ -540,12 +597,16 @@ impl ReflectReference { .ok_or_else(|| InteropError::garbage_collected_allocation(self.clone()))?; // Safety: caller promises this is fine :) - return self.walk_path_mut(unsafe { &mut *arc.get_ptr() }); + let type_registry = world.type_registry(); + let type_registry = type_registry.read(); + return self.walk_path_mut(unsafe { &mut *arc.get_ptr() }, &type_registry); }; if let ReflectBase::Asset(handle, _) = &self.base.base_id { let asset = unsafe { self.load_asset_mut(handle, world.clone())? }; - return self.walk_path_mut(asset.as_partial_reflect_mut()); + let type_registry = world.type_registry(); + let type_registry = type_registry.read(); + return self.walk_path_mut(asset.as_partial_reflect_mut(), &type_registry); }; let type_registry = world.type_registry(); @@ -576,26 +637,27 @@ impl ReflectReference { ); let base = unsafe { from_ptr_data.as_reflect_mut(ptr.into_inner()) }; - drop(type_registry); - self.walk_path_mut(base.as_partial_reflect_mut()) + self.walk_path_mut(base.as_partial_reflect_mut(), &type_registry) } fn walk_path<'a>( &self, root: &'a dyn PartialReflect, - ) -> Result<&'a dyn PartialReflect, InteropError> { + type_registry: &TypeRegistry, + ) -> Result, InteropError> { self.reflect_path - .reflect_element(root) - .map_err(|e| InteropError::reflection_path_error(e, Some(self.clone()))) + .reflect_element(root, type_registry) + .map_err(|e| InteropError::reflection_path_error(e.to_string(), Some(self.clone()))) } fn walk_path_mut<'a>( &self, root: &'a mut dyn PartialReflect, - ) -> Result<&'a mut dyn PartialReflect, InteropError> { + type_registry: &TypeRegistry, + ) -> Result, InteropError> { self.reflect_path - .reflect_element_mut(root) - .map_err(|e| InteropError::reflection_path_error(e, Some(self.clone()))) + .reflect_element_mut(root, type_registry) + .map_err(|e| InteropError::reflection_path_error(e.to_string(), Some(self.clone()))) } } @@ -915,8 +977,8 @@ impl ReflectRefIter { let next = match &mut self.index { IterationKey::Index(i) => { let mut next = self.base.clone(); - let parsed_path = ParsedPath::from(vec![list_index_access(*i)]); - next.index_path(parsed_path); + let parsed_path = ReferencePart::IntegerAccess(*i as i64, false); + next.push_path(parsed_path); *i += 1; next } @@ -925,9 +987,6 @@ impl ReflectRefIter { } } -const fn list_index_access(index: usize) -> Access<'static> { - Access::ListIndex(index) -} #[profiling::all_functions] impl Iterator for ReflectRefIter { type Item = Result; @@ -937,8 +996,8 @@ impl Iterator for ReflectRefIter { match &mut self.index { IterationKey::Index(i) => { let mut next = self.base.clone(); - let parsed_path = ParsedPath::from(vec![list_index_access(*i)]); - next.index_path(parsed_path); + let parsed_path = ReferencePart::IntegerAccess(*i as i64, false); + next.push_path(parsed_path); *i += 1; Ok(next) } @@ -1020,7 +1079,7 @@ mod test { .unwrap(); // index into vec field - component_ref.index_path(ParsedPath::parse_static(".0").unwrap()); + component_ref.push_path(ReferencePart::IntegerAccess(0, true)); assert_eq!( component_ref .tail_type_id(world_guard.clone()) @@ -1053,7 +1112,7 @@ mod test { .unwrap(); // index into vec - component_ref.index_path(ParsedPath::parse_static("[0]").unwrap()); + component_ref.push_path(ReferencePart::IntegerAccess(0, true)); component_ref .with_reflect(world_guard.clone(), |s| { @@ -1103,7 +1162,8 @@ mod test { .unwrap(); // index into vec field - resource_ref.index_path(ParsedPath::parse_static(".0").unwrap()); + resource_ref.push_path(ReferencePart::IntegerAccess(0, true)); + assert_eq!( resource_ref .tail_type_id(world_guard.clone()) @@ -1136,7 +1196,7 @@ mod test { .unwrap(); // index into vec - resource_ref.index_path(ParsedPath::parse_static("[0]").unwrap()); + resource_ref.push_path(ReferencePart::IntegerAccess(0, true)); resource_ref .with_reflect(world_guard.clone(), |s| { @@ -1186,7 +1246,7 @@ mod test { .unwrap(); // index into vec field - allocation_ref.index_path(ParsedPath::parse_static(".0").unwrap()); + allocation_ref.push_path(ReferencePart::IntegerAccess(0, true)); assert_eq!( allocation_ref .tail_type_id(world_guard.clone()) @@ -1219,7 +1279,7 @@ mod test { .unwrap(); // index into vec - allocation_ref.index_path(ParsedPath::parse_static("[0]").unwrap()); + allocation_ref.push_path(ReferencePart::IntegerAccess(0, true)); allocation_ref .with_reflect(world_guard.clone(), |s| { diff --git a/crates/bevy_mod_scripting_bindings/src/reflection_extensions.rs b/crates/bevy_mod_scripting_bindings/src/reflection_extensions.rs index cc7e7b4165..72506cc495 100644 --- a/crates/bevy_mod_scripting_bindings/src/reflection_extensions.rs +++ b/crates/bevy_mod_scripting_bindings/src/reflection_extensions.rs @@ -430,9 +430,11 @@ impl PartialReflectExt for T { /// Extension trait for TypeInfos providing additional functionality for working with type information. pub trait TypeInfoExtensions { + /// Returns the inner type of the set if the type is a set, otherwise None + fn set_inner_type(&self) -> Option; /// Returns true if the type is a result. fn is_result(&self) -> bool; - /// Returns the inner type of the map if the type is a map, otherwise + /// Returns the inner type of the map if the type is a map, otherwise None. fn map_inner_types(&self) -> Option<(TypeId, TypeId)>; /// Returns the inner type of the list if the type is a list, otherwise None. fn list_inner_type(&self) -> Option; @@ -476,6 +478,13 @@ impl TypeInfoExtensions for TypeInfo { Some((map.key_ty().id(), map.value_ty().id())) } + fn set_inner_type(&self) -> Option { + match self { + TypeInfo::Set(info) => Some(info.value_ty().id()), + _ => None, + } + } + fn is_type(&self, crate_name: Option<&str>, type_ident: &str) -> bool { self.type_path_table().ident() == Some(type_ident) && self.type_path_table().crate_name() == crate_name diff --git a/crates/bevy_mod_scripting_bindings/src/script_value.rs b/crates/bevy_mod_scripting_bindings/src/script_value.rs index 82f7cd476c..211a9ebd35 100644 --- a/crates/bevy_mod_scripting_bindings/src/script_value.rs +++ b/crates/bevy_mod_scripting_bindings/src/script_value.rs @@ -6,7 +6,7 @@ use bevy_mod_scripting_display::{ DisplayWithTypeInfo, GetTypeInfo, ReflectDisplayWithTypeInfo, WithTypeInfo, }; use bevy_platform::collections::HashMap; -use bevy_reflect::{Access, OffsetAccess, ParsedPath, Reflect}; +use bevy_reflect::Reflect; use std::borrow::Cow; use super::{ @@ -232,79 +232,3 @@ impl From> for ScriptValue { ScriptValue::Map(value) } } -#[profiling::all_functions] -impl TryFrom for ParsedPath { - type Error = InteropError; - fn try_from(value: ScriptValue) -> Result { - Ok(match value { - ScriptValue::Integer(i) => ParsedPath::from(vec![OffsetAccess { - access: Access::ListIndex(i as usize), - offset: Some(1), - }]), - ScriptValue::Float(_) => { - return Err(InteropError::invalid_index( - value, - "Floating point numbers cannot be used to index into reflected values" - .to_owned(), - )); - } - ScriptValue::String(cow) => { - if let Some(tuple_struct_index) = cow.strip_prefix("_") - && let Ok(index) = tuple_struct_index.parse::() - { - let parsed_path = ParsedPath::from(vec![OffsetAccess { - access: Access::TupleIndex(index), - offset: Some(1), - }]); - return Ok(parsed_path); - } - - match cow { - Cow::Borrowed(v) => ParsedPath::parse_static(v) - .map_err(|e| InteropError::reflection_path_error(e, None))?, - Cow::Owned(o) => ParsedPath::parse(&o) - .map_err(|e| InteropError::reflection_path_error(e, None))?, - } - } - ScriptValue::Reference(reflect_reference) => { - return Err(InteropError::invalid_index( - ScriptValue::Reference(reflect_reference), - "References cannot be used to index into reflected values".to_owned(), - )); - } - _ => ParsedPath(vec![]), - }) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_script_value_to_parsed_path() { - let value = ScriptValue::String("test".into()); - let parsed_path = ParsedPath::from(vec![OffsetAccess { - access: Access::Field("test".to_owned().into()), - offset: Some(4), - }]); - assert_eq!(parsed_path, ParsedPath::try_from(value).unwrap()); - - let value = ScriptValue::String("_0".into()); - let parsed_path = ParsedPath::from(vec![OffsetAccess { - access: Access::TupleIndex(0), - offset: Some(1), - }]); - assert_eq!(parsed_path, ParsedPath::try_from(value).unwrap()); - - let value = ScriptValue::Integer(0); - let parsed_path = ParsedPath::from(vec![OffsetAccess { - access: Access::ListIndex(0), - offset: Some(1), - }]); - assert_eq!(parsed_path, ParsedPath::try_from(value).unwrap()); - - let value = ScriptValue::Float(0.0); - assert!(ParsedPath::try_from(value).is_err()); - } -} diff --git a/crates/bevy_mod_scripting_bindings/src/world.rs b/crates/bevy_mod_scripting_bindings/src/world.rs index 8a5b8bbd5e..d52ddd66de 100644 --- a/crates/bevy_mod_scripting_bindings/src/world.rs +++ b/crates/bevy_mod_scripting_bindings/src/world.rs @@ -39,7 +39,7 @@ use ::{ world::{CommandQueue, Mut, World, unsafe_world_cell::UnsafeWorldCell}, }, bevy_reflect::{ - DynamicEnum, DynamicStruct, DynamicTuple, DynamicTupleStruct, DynamicVariant, ParsedPath, + DynamicEnum, DynamicStruct, DynamicTuple, DynamicTupleStruct, DynamicVariant, PartialReflect, TypeRegistryArc, std_traits::ReflectDefault, }, }; @@ -702,7 +702,7 @@ impl WorldAccessGuard<'_> { } else { field_idx }; - let field_string = format!("_{script_idx}"); + let field_string = script_idx.to_string(); dynamic.insert_boxed(self.construct_from_script_value( field_string.clone(), field_type_id, @@ -727,7 +727,7 @@ impl WorldAccessGuard<'_> { field_idx }; - let field_string = format!("_{script_idx}"); + let field_string = script_idx.to_string(); dynamic.insert_boxed(self.construct_from_script_value( field_string.clone(), @@ -1112,7 +1112,7 @@ impl WorldAccessGuard<'_> { component_registration.component_id, ), }, - reflect_path: ParsedPath(vec![]), + reflect_path: Default::default(), })) } else { Ok(None) @@ -1169,7 +1169,7 @@ impl WorldAccessGuard<'_> { })?, base_id: ReflectBase::Resource(resource_id), }, - reflect_path: ParsedPath(vec![]), + reflect_path: Default::default(), })) } @@ -1484,7 +1484,7 @@ mod test { let type_registration = ScriptTypeRegistration::new(Arc::new(registration)); // zero indexed - let payload = HashMap::from_iter(vec![("_0".to_owned(), ScriptValue::Integer(1))]); + let payload = HashMap::from_iter(vec![("0".to_owned(), ScriptValue::Integer(1))]); let result = world.construct(type_registration.clone(), payload, false); let expected = @@ -1492,7 +1492,7 @@ mod test { pretty_assertions::assert_str_eq!(format!("{result:#?}"), format!("{expected:#?}")); // one indexed - let payload = HashMap::from_iter(vec![("_1".to_owned(), ScriptValue::Integer(1))]); + let payload = HashMap::from_iter(vec![("1".to_owned(), ScriptValue::Integer(1))]); let result = world.construct(type_registration, payload, true); let expected = @@ -1523,8 +1523,8 @@ mod test { // zero indexed let payload = HashMap::from_iter(vec![ - ("_0".to_owned(), ScriptValue::Integer(1)), - ("_1".to_owned(), ScriptValue::Integer(2)), + ("0".to_owned(), ScriptValue::Integer(1)), + ("1".to_owned(), ScriptValue::Integer(2)), ]); let result = world.construct(type_registration.clone(), payload, false); @@ -1533,8 +1533,8 @@ mod test { // one indexed let payload = HashMap::from_iter(vec![ - ("_1".to_owned(), ScriptValue::Integer(1)), - ("_2".to_owned(), ScriptValue::Integer(2)), + ("1".to_owned(), ScriptValue::Integer(1)), + ("2".to_owned(), ScriptValue::Integer(2)), ]); let result = world.construct(type_registration.clone(), payload, true); @@ -1567,7 +1567,7 @@ mod test { // tuple struct version let payload = HashMap::from_iter(vec![ - ("_0".to_owned(), ScriptValue::Integer(1)), + ("0".to_owned(), ScriptValue::Integer(1)), ( "variant".to_owned(), ScriptValue::String("TupleStruct".into()), diff --git a/crates/bevy_mod_scripting_display/src/impls/bevy_reflect.rs b/crates/bevy_mod_scripting_display/src/impls/bevy_reflect.rs index 8a811e4475..f9653e90fa 100644 --- a/crates/bevy_mod_scripting_display/src/impls/bevy_reflect.rs +++ b/crates/bevy_mod_scripting_display/src/impls/bevy_reflect.rs @@ -1,4 +1,4 @@ -use bevy_reflect::ParsedPath; +use bevy_reflect::{ParsedPath, PartialReflect}; use crate::*; @@ -7,3 +7,13 @@ impl_debug_with_type_info_via_debug!(TypeInfo); impl_debug_with_type_info_via_debug!(&'static TypeInfo); impl_display_with_type_info_via_display!(ParsedPath); + +impl DebugWithTypeInfo for Box { + fn to_string_with_type_info( + &self, + f: &mut std::fmt::Formatter<'_>, + type_info_provider: Option<&dyn GetTypeInfo>, + ) -> std::fmt::Result { + ReflectPrinter::new(f, type_info_provider).debug(self.as_ref()) + } +} diff --git a/crates/testing_crates/script_integration_test_harness/src/parse.rs b/crates/testing_crates/script_integration_test_harness/src/parse.rs index 0a4406f323..6c1c12daea 100644 --- a/crates/testing_crates/script_integration_test_harness/src/parse.rs +++ b/crates/testing_crates/script_integration_test_harness/src/parse.rs @@ -182,9 +182,10 @@ impl ScenarioStepSerialized { match language { ScenarioLanguage::Lua => Language::Lua, ScenarioLanguage::Rhai => Language::Rhai, - ScenarioLanguage::ThisScriptLanguage => { - Language::External(SCENARIO_SELF_LANGUAGE_NAME.into()) - } + ScenarioLanguage::ThisScriptLanguage => Language::External { + name: SCENARIO_SELF_LANGUAGE_NAME.into(), + one_indexed: false, + }, } } diff --git a/crates/testing_crates/script_integration_test_harness/src/scenario.rs b/crates/testing_crates/script_integration_test_harness/src/scenario.rs index 8700d6bcac..30a9435515 100644 --- a/crates/testing_crates/script_integration_test_harness/src/scenario.rs +++ b/crates/testing_crates/script_integration_test_harness/src/scenario.rs @@ -257,7 +257,10 @@ impl ScenarioSchedule { language: Option, app: &mut App, ) { - let language = language.unwrap_or(Language::External("Unset language".into())); + let language = language.unwrap_or(Language::External { + name: "Unset language".into(), + one_indexed: false, + }); match language { #[cfg(feature = "lua")] Language::Lua => { @@ -464,7 +467,7 @@ impl ScenarioStep { pub fn execute(self, context: &mut ScenarioContext, app: &mut App) -> Result<(), Error> { match self { ScenarioStep::SetCurrentLanguage { language } => { - let language = if language == Language::External(SCENARIO_SELF_LANGUAGE_NAME.into()) + let language = if matches!(&language, Language::External { name, .. } if name == SCENARIO_SELF_LANGUAGE_NAME) { // main script language can be gotten from the "this_script_asset_relative_path" let extension = context diff --git a/crates/testing_crates/test_utils/src/test_data.rs b/crates/testing_crates/test_utils/src/test_data.rs index e25ce138be..e48e940556 100644 --- a/crates/testing_crates/test_utils/src/test_data.rs +++ b/crates/testing_crates/test_utils/src/test_data.rs @@ -1,4 +1,7 @@ -use std::{alloc::Layout, collections::HashMap}; +use std::{ + alloc::Layout, + collections::{HashMap, HashSet}, +}; use bevy_app::{App, ScheduleRunnerPlugin, TaskPoolPlugin}; use bevy_diagnostic::FrameCountPlugin; @@ -27,6 +30,21 @@ impl TestComponent { } } +/// Test component with Reflect and ReflectComponent registered +#[derive(Resource, Reflect, PartialEq, Eq, Debug, Hash)] +#[reflect(Resource)] +pub struct SimpleType { + pub inner: String, +} + +impl SimpleType { + pub fn init() -> Self { + Self { + inner: String::from("initial"), + } + } +} + #[derive(Asset, Reflect, PartialEq, Debug, Clone)] pub struct TestAsset { pub value: i32, @@ -158,6 +176,8 @@ pub struct TestResourceWithVariousFields { pub bool: bool, pub vec_usize: Vec, pub string_map: HashMap, + pub string_set: HashSet, + pub simple_type_map: HashMap, } impl TestResourceWithVariousFields { @@ -169,10 +189,30 @@ impl TestResourceWithVariousFields { float: 69.0, bool: true, vec_usize: vec![1, 2, 3, 4, 5], - string_map: vec![("foo", "bar"), ("zoo", "zed")] - .into_iter() - .map(|(a, b)| (a.to_owned(), b.to_owned())) - .collect(), + string_map: HashMap::from_iter(vec![ + (String::from("foo"), String::from("bar")), + (String::from("zoo"), String::from("zed")), + ]), + string_set: HashSet::from_iter(vec![ + String::from("foo"), + String::from("bar"), + String::from("zoo"), + String::from("zed"), + ]), + simple_type_map: HashMap::from_iter(vec![ + ( + SimpleType { + inner: String::from("foo"), + }, + String::from("bar"), + ), + ( + SimpleType { + inner: String::from("zoo"), + }, + String::from("zed"), + ), + ]), } } } @@ -314,6 +354,7 @@ impl_test_component_ids!( TestResource => 10, ResourceWithDefault => 11, TestResourceWithVariousFields => 12, + SimpleType => 13 ] );