From cc651410573be486a24e845aded9c1b90652afb0 Mon Sep 17 00:00:00 2001 From: makspll Date: Mon, 3 Mar 2025 14:47:07 +0000 Subject: [PATCH] feat: add `map_get` function for cloning and returning values on a hashmap --- .../map_get/can_get_hashmap_value copy.rhai | 4 ++ .../tests/map_get/can_get_hashmap_value.lua | 4 ++ .../src/reflection_extensions.rs | 20 ++++++++ .../bevy_mod_scripting_functions/src/core.rs | 51 +++++++++++++++++++ 4 files changed, 79 insertions(+) create mode 100644 assets/tests/map_get/can_get_hashmap_value copy.rhai create mode 100644 assets/tests/map_get/can_get_hashmap_value.lua diff --git a/assets/tests/map_get/can_get_hashmap_value copy.rhai b/assets/tests/map_get/can_get_hashmap_value copy.rhai new file mode 100644 index 0000000000..33f2dc1525 --- /dev/null +++ b/assets/tests/map_get/can_get_hashmap_value copy.rhai @@ -0,0 +1,4 @@ +let Resource = world.get_type_by_name.call("TestResourceWithVariousFields"); +let resource = world.get_resource.call(Resource); + +assert(resource.string_map.map_get.call("foo") == "bar", "Expected bar, got " + resource.string_map.map_get.call("foo")); \ No newline at end of file diff --git a/assets/tests/map_get/can_get_hashmap_value.lua b/assets/tests/map_get/can_get_hashmap_value.lua new file mode 100644 index 0000000000..2bd4a4459e --- /dev/null +++ b/assets/tests/map_get/can_get_hashmap_value.lua @@ -0,0 +1,4 @@ +local Resource = world.get_type_by_name("TestResourceWithVariousFields") +local resource = world.get_resource(Resource) + +assert(resource.string_map:map_get("foo") == "bar", "Expected bar, got " .. resource.string_map:map_get("foo")) \ No newline at end of file diff --git a/crates/bevy_mod_scripting_core/src/reflection_extensions.rs b/crates/bevy_mod_scripting_core/src/reflection_extensions.rs index 9e7f5e57b6..f7a615e306 100644 --- a/crates/bevy_mod_scripting_core/src/reflection_extensions.rs +++ b/crates/bevy_mod_scripting_core/src/reflection_extensions.rs @@ -11,6 +11,12 @@ use std::{ }; /// Extension trait for [`PartialReflect`] providing additional functionality for working with specific types. pub trait PartialReflectExt { + /// Try to get a reference to the given key in an underyling map, if the type is a map. + fn try_map_get( + &self, + key: &dyn PartialReflect, + ) -> Result, InteropError>; + /// Try to remove the value at the given key, if the type supports removing with the given key. fn try_remove_boxed( &mut self, @@ -312,6 +318,20 @@ impl PartialReflectExt for T { } } + fn try_map_get( + &self, + key: &dyn PartialReflect, + ) -> Result, InteropError> { + match self.reflect_ref() { + bevy::reflect::ReflectRef::Map(m) => Ok(m.get(key)), + _ => Err(InteropError::unsupported_operation( + self.get_represented_type_info().map(|ti| ti.type_id()), + None, + "map_get".to_owned(), + )), + } + } + fn try_pop_boxed(&mut self) -> Result, InteropError> { match self.reflect_mut() { bevy::reflect::ReflectMut::List(l) => l.pop().ok_or_else(|| { diff --git a/crates/bevy_mod_scripting_functions/src/core.rs b/crates/bevy_mod_scripting_functions/src/core.rs index 92a549381f..f2814f73b3 100644 --- a/crates/bevy_mod_scripting_functions/src/core.rs +++ b/crates/bevy_mod_scripting_functions/src/core.rs @@ -321,6 +321,7 @@ impl World { name = "reflect_reference_functions" )] impl ReflectReference { + /// If this type is an enum, will return the name of the variant it represents on the type. fn variant_name( ctxt: FunctionCallContext, s: ReflectReference, @@ -330,12 +331,14 @@ impl ReflectReference { s.variant_name(world) } + /// Displays this reference without printing the exact contents. fn display_ref(ctxt: FunctionCallContext, s: ReflectReference) -> Result { profiling::function_scope!("display_ref"); let world = ctxt.world()?; Ok(s.display_with_world(world)) } + /// Displays the "value" of this reference fn display_value( ctxt: FunctionCallContext, s: ReflectReference, @@ -345,6 +348,43 @@ impl ReflectReference { Ok(s.display_value_with_world(world)) } + /// Gets and clones the value under the specified key if the underlying type is a map type. + fn map_get( + ctxt: FunctionCallContext, + self_: ReflectReference, + key: ScriptValue, + ) -> Result, InteropError> { + profiling::function_scope!("map_get"); + let world = ctxt.world()?; + let key = >::from_script_ref( + self_.key_type_id(world.clone())?.ok_or_else(|| { + InteropError::unsupported_operation( + self_.tail_type_id(world.clone()).unwrap_or_default(), + Some(Box::new(key.clone())), + "Could not get key type id. Are you trying to index into a type that's not a map?".to_owned(), + ) + })?, + key, + world.clone(), + )?; + self_.with_reflect_mut(world.clone(), |s| match s.try_map_get(key.as_ref())? { + Some(value) => { + let reference = { + let allocator = world.allocator(); + let mut allocator = allocator.write(); + let owned_value = ::from_reflect(value, world.clone())?; + ReflectReference::new_allocated_boxed(owned_value, &mut allocator) + }; + Ok(Some(ReflectReference::into_script_ref(reference, world)?)) + } + None => Ok(None), + })? + } + + /// Indexes into the given reference and if the nested type is a reference type, returns a deeper reference, otherwise + /// returns the concrete value. + /// + /// Does not support map types at the moment, for maps see `map_get` fn get( ctxt: FunctionCallContext, mut self_: ReflectReference, @@ -360,6 +400,7 @@ impl ReflectReference { ReflectReference::into_script_ref(self_, world) } + /// Sets the value under the specified path on the underlying value. fn set( ctxt: FunctionCallContext, self_: ScriptValue, @@ -395,6 +436,7 @@ impl ReflectReference { Ok(ScriptValue::Unit) } + /// Pushes the value into the reference, if the reference is an appropriate container type. fn push( ctxt: FunctionCallContext, s: ReflectReference, @@ -413,6 +455,7 @@ impl ReflectReference { s.with_reflect_mut(world, |s| s.try_push_boxed(other))? } + /// Pops the value from the reference, if the reference is an appropriate container type. fn pop(ctxt: FunctionCallContext, s: ReflectReference) -> Result { profiling::function_scope!("pop"); let world = ctxt.world()?; @@ -426,6 +469,7 @@ impl ReflectReference { ReflectReference::into_script_ref(reference, world) } + /// Inserts the value into the reference at the specified index, if the reference is an appropriate container type. fn insert( ctxt: FunctionCallContext, s: ReflectReference, @@ -461,18 +505,21 @@ impl ReflectReference { s.with_reflect_mut(world, |s| s.try_insert_boxed(key, value))? } + /// Clears the container, if the reference is an appropriate container type. fn clear(ctxt: FunctionCallContext, s: ReflectReference) -> Result<(), InteropError> { profiling::function_scope!("clear"); let world = ctxt.world()?; s.with_reflect_mut(world, |s| s.try_clear())? } + /// Retrieves the length of the reference, if the reference is an appropriate container type. fn len(ctxt: FunctionCallContext, s: ReflectReference) -> Result, InteropError> { profiling::function_scope!("len"); let world = ctxt.world()?; s.len(world) } + /// Removes the value at the specified key from the reference, if the reference is an appropriate container type. fn remove( ctxt: FunctionCallContext, s: ReflectReference, @@ -508,6 +555,9 @@ impl ReflectReference { } } + /// Iterates over the reference, if the reference is an appropriate container type. + /// + /// Returns an "next" iterator function. fn iter( ctxt: FunctionCallContext, s: ReflectReference, @@ -536,6 +586,7 @@ impl ReflectReference { Ok(iter_function.into_dynamic_script_function_mut()) } + /// Lists the functions available on the reference. fn functions( ctxt: FunctionCallContext, s: ReflectReference,