Skip to content
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 by Bors] - bevy_reflect: support map insertion #5173

Closed
wants to merge 5 commits into from
Closed
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
47 changes: 32 additions & 15 deletions crates/bevy_reflect/src/impls/std.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate as bevy_reflect;
use crate::{
map_partial_eq, Array, ArrayInfo, ArrayIter, DynamicMap, FromReflect, FromType,
map_apply, map_partial_eq, Array, ArrayInfo, ArrayIter, DynamicMap, FromReflect, FromType,
GetTypeRegistration, List, ListInfo, Map, MapInfo, MapIter, Reflect, ReflectDeserialize,
ReflectMut, ReflectRef, ReflectSerialize, TypeInfo, TypeRegistration, Typed, ValueInfo,
};
Expand Down Expand Up @@ -193,7 +193,7 @@ impl<T: FromReflect> FromReflect for Vec<T> {
}
}

impl<K: Reflect + Eq + Hash, V: Reflect> Map for HashMap<K, V> {
impl<K: FromReflect + Eq + Hash, V: FromReflect> Map for HashMap<K, V> {
fn get(&self, key: &dyn Reflect) -> Option<&dyn Reflect> {
key.downcast_ref::<K>()
.and_then(|key| HashMap::get(self, key))
Expand Down Expand Up @@ -231,9 +231,34 @@ impl<K: Reflect + Eq + Hash, V: Reflect> Map for HashMap<K, V> {
}
dynamic_map
}

fn insert_boxed(
&mut self,
key: Box<dyn Reflect>,
value: Box<dyn Reflect>,
) -> Option<Box<dyn Reflect>> {
let key = key.take::<K>().unwrap_or_else(|key| {
K::from_reflect(&*key).unwrap_or_else(|| {
panic!(
"Attempted to insert invalid key of type {}.",
key.type_name()
)
})
});
let value = value.take::<V>().unwrap_or_else(|value| {
V::from_reflect(&*value).unwrap_or_else(|| {
panic!(
"Attempted to insert invalid value of type {}.",
value.type_name()
)
})
});
self.insert(key, value)
.map(|old_value| Box::new(old_value) as Box<dyn Reflect>)
}
}

impl<K: Reflect + Eq + Hash, V: Reflect> Reflect for HashMap<K, V> {
impl<K: FromReflect + Eq + Hash, V: FromReflect> Reflect for HashMap<K, V> {
fn type_name(&self) -> &str {
std::any::type_name::<Self>()
}
Expand Down Expand Up @@ -263,15 +288,7 @@ impl<K: Reflect + Eq + Hash, V: Reflect> Reflect for HashMap<K, V> {
}

fn apply(&mut self, value: &dyn Reflect) {
if let ReflectRef::Map(map_value) = value.reflect_ref() {
for (key, value) in map_value.iter() {
if let Some(v) = Map::get_mut(self, key) {
v.apply(value);
}
}
} else {
panic!("Attempted to apply a non-map type to a map type.");
}
map_apply(self, value);
}

fn set(&mut self, value: Box<dyn Reflect>) -> Result<(), Box<dyn Reflect>> {
Expand All @@ -296,7 +313,7 @@ impl<K: Reflect + Eq + Hash, V: Reflect> Reflect for HashMap<K, V> {
}
}

impl<K: Reflect + Eq + Hash, V: Reflect> Typed for HashMap<K, V> {
impl<K: FromReflect + Eq + Hash, V: FromReflect> Typed for HashMap<K, V> {
fn type_info() -> &'static TypeInfo {
static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new();
CELL.get_or_insert::<Self, _>(|| TypeInfo::Map(MapInfo::new::<Self, K, V>()))
Expand All @@ -305,8 +322,8 @@ impl<K: Reflect + Eq + Hash, V: Reflect> Typed for HashMap<K, V> {

impl<K, V> GetTypeRegistration for HashMap<K, V>
where
K: Reflect + Clone + Eq + Hash + for<'de> Deserialize<'de>,
V: Reflect + Clone + for<'de> Deserialize<'de>,
K: FromReflect + Clone + Eq + Hash + for<'de> Deserialize<'de>,
V: FromReflect + Clone + for<'de> Deserialize<'de>,
{
fn get_type_registration() -> TypeRegistration {
let mut registration = TypeRegistration::of::<Self>();
Expand Down
3 changes: 3 additions & 0 deletions crates/bevy_reflect/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,7 @@ mod tests {

let mut map = DynamicMap::default();
map.insert(2usize, 3i8);
map.insert(3usize, 4i8);
foo_patch.insert("d", map);

let mut bar_patch = DynamicStruct::default();
Expand Down Expand Up @@ -394,6 +395,7 @@ mod tests {
let mut hash_map = HashMap::default();
hash_map.insert(1, 1);
hash_map.insert(2, 3);
hash_map.insert(3, 4);

let mut hash_map_baz = HashMap::default();
hash_map_baz.insert(1, Bar { x: 7 });
Expand All @@ -416,6 +418,7 @@ mod tests {

let mut hash_map = HashMap::default();
hash_map.insert(2, 3);
hash_map.insert(3, 4);

let expected_new_foo = Foo {
a: 2,
Expand Down
74 changes: 52 additions & 22 deletions crates/bevy_reflect/src/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@ pub trait Map: Reflect {

/// Clones the map, producing a [`DynamicMap`].
fn clone_dynamic(&self) -> DynamicMap;

/// Inserts a key-value pair into the map.
///
/// If the map did not have this key present, `None` is returned.
/// If the map did have this key present, the value is updated, and the old value is returned.
fn insert_boxed(
&mut self,
key: Box<dyn Reflect>,
value: Box<dyn Reflect>,
) -> Option<Box<dyn Reflect>>;
}

/// A container for compile-time map info.
Expand Down Expand Up @@ -153,19 +163,6 @@ impl DynamicMap {
pub fn insert<K: Reflect, V: Reflect>(&mut self, key: K, value: V) {
self.insert_boxed(Box::new(key), Box::new(value));
}

/// Inserts a key-value pair of [`Reflect`] values into the map.
pub fn insert_boxed(&mut self, key: Box<dyn Reflect>, value: Box<dyn Reflect>) {
match self.indices.entry(key.reflect_hash().expect(HASH_ERROR)) {
Entry::Occupied(entry) => {
self.values[*entry.get()] = (key, value);
}
Entry::Vacant(entry) => {
entry.insert(self.values.len());
self.values.push((key, value));
}
}
}
}

impl Map for DynamicMap {
Expand Down Expand Up @@ -210,6 +207,25 @@ impl Map for DynamicMap {
.get(index)
.map(|(key, value)| (&**key, &**value))
}

fn insert_boxed(
&mut self,
key: Box<dyn Reflect>,
mut value: Box<dyn Reflect>,
) -> Option<Box<dyn Reflect>> {
match self.indices.entry(key.reflect_hash().expect(HASH_ERROR)) {
Entry::Occupied(entry) => {
let (_old_key, old_value) = self.values.get_mut(*entry.get()).unwrap();
std::mem::swap(old_value, &mut value);
Some(value)
}
Entry::Vacant(entry) => {
entry.insert(self.values.len());
self.values.push((key, value));
None
}
}
}
}

impl Reflect for DynamicMap {
Expand Down Expand Up @@ -245,15 +261,7 @@ impl Reflect for DynamicMap {
}

fn apply(&mut self, value: &dyn Reflect) {
if let ReflectRef::Map(map_value) = value.reflect_ref() {
for (key, value) in map_value.iter() {
if let Some(v) = self.get_mut(key) {
v.apply(value);
}
}
} else {
panic!("Attempted to apply a non-map type to a map type.");
}
map_apply(self, value);
}

fn set(&mut self, value: Box<dyn Reflect>) -> Result<(), Box<dyn Reflect>> {
Expand Down Expand Up @@ -387,6 +395,28 @@ pub fn map_debug(dyn_map: &dyn Map, f: &mut std::fmt::Formatter<'_>) -> std::fmt
debug.finish()
}

/// Applies the elements of reflected map `b` to the corresponding elements of map `a`.
///
/// If a key from `b` does not exist in `a`, the value is cloned and inserted.
///
/// # Panics
///
/// This function panics if `b` is not a reflected map.
#[inline]
pub fn map_apply<M: Map>(a: &mut M, b: &dyn Reflect) {
if let ReflectRef::Map(map_value) = b.reflect_ref() {
for (key, b_value) in map_value.iter() {
if let Some(a_value) = a.get_mut(key) {
a_value.apply(b_value);
} else {
a.insert_boxed(key.clone_value(), b_value.clone_value());
}
}
} else {
panic!("Attempted to apply a non-map type to a map type.");
}
}

#[cfg(test)]
mod tests {
use super::DynamicMap;
Expand Down
7 changes: 4 additions & 3 deletions crates/bevy_reflect/src/reflect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,17 +93,18 @@ pub trait Reflect: Any + Send + Sync {
/// and excess elements in `value` are appended to `self`.
/// - If `T` is a [`Map`], then for each key in `value`, the associated
/// value is applied to the value associated with the same key in `self`.
/// Keys which are not present in both maps are ignored.
/// Keys which are not present in `self` are inserted.
/// - If `T` is none of these, then `value` is downcast to `T`, cloned, and
/// assigned to `self`.
///
/// Note that `Reflect` must be implemented manually for [`List`]s and
/// [`Map`]s in order to achieve the correct semantics, as derived
/// implementations will have the semantics for [`Struct`], [`TupleStruct`]
/// or none of the above depending on the kind of type. For lists, use the
/// [`list_apply`] helper function when implementing this method.
/// or none of the above depending on the kind of type. For lists and maps, use the
/// [`list_apply`] and [`map_apply`] helper functions when implementing this method.
///
/// [`list_apply`]: crate::list_apply
/// [`map_apply`]: crate::map_apply
///
/// # Panics
///
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_reflect/src/serde/de.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
serde::type_fields, DynamicArray, DynamicList, DynamicMap, DynamicStruct, DynamicTuple,
DynamicTupleStruct, Reflect, ReflectDeserialize, TypeRegistry,
DynamicTupleStruct, Map, Reflect, ReflectDeserialize, TypeRegistry,
};
use erased_serde::Deserializer;
use serde::de::{self, DeserializeSeed, MapAccess, SeqAccess, Visitor};
Expand Down