Skip to content

feat: add map_get function for cloning and returning values from a map #343

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions assets/tests/map_get/can_get_hashmap_value copy.rhai
Original file line number Diff line number Diff line change
@@ -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"));
4 changes: 4 additions & 0 deletions assets/tests/map_get/can_get_hashmap_value.lua
Original file line number Diff line number Diff line change
@@ -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"))
20 changes: 20 additions & 0 deletions crates/bevy_mod_scripting_core/src/reflection_extensions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Option<&dyn PartialReflect>, 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,
Expand Down Expand Up @@ -312,6 +318,20 @@ impl<T: PartialReflect + ?Sized> PartialReflectExt for T {
}
}

fn try_map_get(
&self,
key: &dyn PartialReflect,
) -> Result<Option<&dyn PartialReflect>, 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<Box<dyn PartialReflect>, InteropError> {
match self.reflect_mut() {
bevy::reflect::ReflectMut::List(l) => l.pop().ok_or_else(|| {
Expand Down
51 changes: 51 additions & 0 deletions crates/bevy_mod_scripting_functions/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<String, InteropError> {
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,
Expand All @@ -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<Option<ScriptValue>, InteropError> {
profiling::function_scope!("map_get");
let world = ctxt.world()?;
let key = <Box<dyn PartialReflect>>::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 = <dyn PartialReflect>::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,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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<ScriptValue, InteropError> {
profiling::function_scope!("pop");
let world = ctxt.world()?;
Expand All @@ -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,
Expand Down Expand Up @@ -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<Option<usize>, 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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
Loading