From 52a2a3b146d11fe57b82124fc59b54989d28ad4a Mon Sep 17 00:00:00 2001 From: Robert Walter <26892280+RobWalt@users.noreply.github.com> Date: Wed, 24 Jul 2024 21:43:26 +0200 Subject: [PATCH] Dedicated `Reflect` implementation for `Set`-like things (#13014) # Objective I just wanted to inspect `HashSet`s in `bevy-inspector-egui` but I noticed that it didn't work for some reason. A few minutes later I found myself looking into the bevy reflect impls noticing that `HashSet`s have been covered only rudimentary up until now. ## Solution I'm not sure if this is overkill (especially the first bullet), but here's a list of the changes: - created a whole new trait and enum variants for `ReflectRef` and the like called `Set` - mostly oriented myself at the `Map` trait and made the necessary changes until RA was happy - create macro `impl_reflect_for_hashset!` and call it on `std::HashSet` and `hashbrown::HashSet` Extra notes: - no `get_mut` or `get_mut_at` mirroring the `std::HashSet` - `insert[_boxed]` and `remove` return `bool` mirroring `std::HashSet`, additionally that bool is reflect as I thought that would be how we handle things in bevy reflect, but I'm not sure on this - ser/de are handled via `SeqAccess` - I'm not sure about the general deduplication property of this impl of `Set` that is generally expected? I'm also not sure yet if `Map` does provide this. This mainly refers to the `Dynamic[...]` structs - I'm not sure if there are other methods missing from the `trait`, I felt like `contains` or the set-operations (union/diff/...) could've been helpful, but I wanted to get out the bare minimum for feedback first --- ## Changelog ### Added - `Set` trait for `bevy_reflect` ### Changed - `std::collections::HashSet` and `bevy_utils::hashbrown::HashSet` now implement a more complete set of reflect functionalities instead of "just" `reflect_value` - `TypeInfo` contains a new variant `Set` that contains `SetInfo` - `ReflectKind` contains a new variant `Set` - `ReflectRef` contains a new variant `Set` - `ReflectMut` contains a new variant `Set` - `ReflectOwned` contains a new variant `Set` ## Migration Guide - The new `Set` variants on the enums listed in the change section should probably be considered by people working with this level of the lib ### Help wanted! I'm not sure if this change is able to break code. From my understanding it shouldn't since we just add functionality but I'm not sure yet if theres anything missing from my impl that would be normally provided by `impl_reflect_value!` --- crates/bevy_reflect/src/impls/std.rs | 233 ++++++++++- crates/bevy_reflect/src/lib.rs | 2 + crates/bevy_reflect/src/reflect.rs | 9 +- crates/bevy_reflect/src/serde/de.rs | 49 ++- crates/bevy_reflect/src/serde/ser.rs | 25 +- crates/bevy_reflect/src/set.rs | 520 ++++++++++++++++++++++++ crates/bevy_reflect/src/type_info.rs | 9 +- examples/reflection/reflection_types.rs | 4 + 8 files changed, 831 insertions(+), 20 deletions(-) create mode 100644 crates/bevy_reflect/src/set.rs diff --git a/crates/bevy_reflect/src/impls/std.rs b/crates/bevy_reflect/src/impls/std.rs index aff3f0d130e91..16520dd496a9e 100644 --- a/crates/bevy_reflect/src/impls/std.rs +++ b/crates/bevy_reflect/src/impls/std.rs @@ -3,12 +3,12 @@ use crate::utility::{ reflect_hasher, GenericTypeInfoCell, GenericTypePathCell, NonGenericTypeInfoCell, }; use crate::{ - self as bevy_reflect, impl_type_path, map_apply, map_partial_eq, map_try_apply, ApplyError, - Array, ArrayInfo, ArrayIter, DynamicMap, DynamicTypePath, FromReflect, FromType, - GetTypeRegistration, List, ListInfo, ListIter, Map, MapInfo, MapIter, MaybeTyped, Reflect, - ReflectDeserialize, ReflectFromPtr, ReflectFromReflect, ReflectKind, ReflectMut, ReflectOwned, - ReflectRef, ReflectSerialize, TypeInfo, TypePath, TypeRegistration, TypeRegistry, Typed, - ValueInfo, + self as bevy_reflect, impl_type_path, map_apply, map_partial_eq, map_try_apply, set_apply, + set_partial_eq, set_try_apply, ApplyError, Array, ArrayInfo, ArrayIter, DynamicMap, DynamicSet, + DynamicTypePath, FromReflect, FromType, GetTypeRegistration, List, ListInfo, ListIter, Map, + MapInfo, MapIter, MaybeTyped, Reflect, ReflectDeserialize, ReflectFromPtr, ReflectFromReflect, + ReflectKind, ReflectMut, ReflectOwned, ReflectRef, ReflectSerialize, Set, SetInfo, TypeInfo, + TypePath, TypeRegistration, TypeRegistry, Typed, ValueInfo, }; use bevy_reflect_derive::{impl_reflect, impl_reflect_value}; use std::fmt; @@ -97,8 +97,6 @@ impl_reflect_value!(::std::path::PathBuf( )); impl_reflect_value!(::std::any::TypeId(Debug, Hash, PartialEq,)); impl_reflect_value!(::std::collections::BTreeSet()); -impl_reflect_value!(::std::collections::HashSet()); -impl_reflect_value!(::bevy_utils::hashbrown::HashSet()); impl_reflect_value!(::core::ops::Range()); impl_reflect_value!(::core::ops::RangeInclusive()); impl_reflect_value!(::core::ops::RangeFrom()); @@ -216,10 +214,6 @@ impl_reflect_value!(::std::ffi::OsString( impl_reflect_value!(::std::ffi::OsString(Debug, Hash, PartialEq)); impl_reflect_value!(::alloc::collections::BinaryHeap); -impl_type_path!(::bevy_utils::NoOpHash); -impl_type_path!(::bevy_utils::EntityHash); -impl_type_path!(::bevy_utils::FixedState); - macro_rules! impl_reflect_for_veclike { ($ty:path, $insert:expr, $remove:expr, $push:expr, $pop:expr, $sub:ty) => { impl List for $ty { @@ -662,6 +656,221 @@ crate::func::macros::impl_function_traits!(::bevy_utils::hashbrown::HashMap ); +macro_rules! impl_reflect_for_hashset { + ($ty:path) => { + impl Set for $ty + where + V: FromReflect + TypePath + GetTypeRegistration + Eq + Hash, + S: TypePath + BuildHasher + Send + Sync, + { + fn get(&self, value: &dyn Reflect) -> Option<&dyn Reflect> { + value + .downcast_ref::() + .and_then(|value| Self::get(self, value)) + .map(|value| value as &dyn Reflect) + } + + fn len(&self) -> usize { + Self::len(self) + } + + fn iter(&self) -> Box + '_> { + let iter = self.iter().map(|v| v as &dyn Reflect); + Box::new(iter) + } + + fn drain(self: Box) -> Vec> { + self.into_iter() + .map(|value| Box::new(value) as Box) + .collect() + } + + fn clone_dynamic(&self) -> DynamicSet { + let mut dynamic_set = DynamicSet::default(); + dynamic_set.set_represented_type(self.get_represented_type_info()); + for v in self { + dynamic_set.insert_boxed(v.clone_value()); + } + dynamic_set + } + + fn insert_boxed(&mut self, value: Box) -> bool { + let value = V::take_from_reflect(value).unwrap_or_else(|value| { + panic!( + "Attempted to insert invalid value of type {}.", + value.reflect_type_path() + ) + }); + self.insert(value) + } + + fn remove(&mut self, value: &dyn Reflect) -> bool { + let mut from_reflect = None; + value + .downcast_ref::() + .or_else(|| { + from_reflect = V::from_reflect(value); + from_reflect.as_ref() + }) + .map_or(false, |value| self.remove(value)) + } + + fn contains(&self, value: &dyn Reflect) -> bool { + let mut from_reflect = None; + value + .downcast_ref::() + .or_else(|| { + from_reflect = V::from_reflect(value); + from_reflect.as_ref() + }) + .map_or(false, |value| self.contains(value)) + } + } + + impl Reflect for $ty + where + V: FromReflect + TypePath + GetTypeRegistration + Eq + Hash, + S: TypePath + BuildHasher + Send + Sync, + { + fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { + Some(::type_info()) + } + + fn into_any(self: Box) -> Box { + self + } + + fn as_any(&self) -> &dyn Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } + + #[inline] + fn into_reflect(self: Box) -> Box { + self + } + + fn as_reflect(&self) -> &dyn Reflect { + self + } + + fn as_reflect_mut(&mut self) -> &mut dyn Reflect { + self + } + + fn apply(&mut self, value: &dyn Reflect) { + set_apply(self, value); + } + + fn try_apply(&mut self, value: &dyn Reflect) -> Result<(), ApplyError> { + set_try_apply(self, value) + } + + fn set(&mut self, value: Box) -> Result<(), Box> { + *self = value.take()?; + Ok(()) + } + + fn reflect_kind(&self) -> ReflectKind { + ReflectKind::Set + } + + fn reflect_ref(&self) -> ReflectRef { + ReflectRef::Set(self) + } + + fn reflect_mut(&mut self) -> ReflectMut { + ReflectMut::Set(self) + } + + fn reflect_owned(self: Box) -> ReflectOwned { + ReflectOwned::Set(self) + } + + fn clone_value(&self) -> Box { + Box::new(self.clone_dynamic()) + } + + fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option { + set_partial_eq(self, value) + } + } + + impl Typed for $ty + where + V: FromReflect + TypePath + GetTypeRegistration + Eq + Hash, + S: TypePath + BuildHasher + Send + Sync, + { + fn type_info() -> &'static TypeInfo { + static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new(); + CELL.get_or_insert::(|| TypeInfo::Set(SetInfo::new::())) + } + } + + impl GetTypeRegistration for $ty + where + V: FromReflect + TypePath + GetTypeRegistration + Eq + Hash, + S: TypePath + BuildHasher + Send + Sync, + { + fn get_type_registration() -> TypeRegistration { + let mut registration = TypeRegistration::of::(); + registration.insert::(FromType::::from_type()); + registration + } + + fn register_type_dependencies(registry: &mut TypeRegistry) { + registry.register::(); + } + } + + impl FromReflect for $ty + where + V: FromReflect + TypePath + GetTypeRegistration + Eq + Hash, + S: TypePath + BuildHasher + Default + Send + Sync, + { + fn from_reflect(reflect: &dyn Reflect) -> Option { + if let ReflectRef::Set(ref_set) = reflect.reflect_ref() { + let mut new_set = Self::with_capacity_and_hasher(ref_set.len(), S::default()); + for value in ref_set.iter() { + let new_value = V::from_reflect(value)?; + new_set.insert(new_value); + } + Some(new_set) + } else { + None + } + } + } + }; +} + +impl_type_path!(::bevy_utils::NoOpHash); +impl_type_path!(::bevy_utils::EntityHash); +impl_type_path!(::bevy_utils::FixedState); + +impl_reflect_for_hashset!(::std::collections::HashSet); +impl_type_path!(::std::collections::HashSet); +#[cfg(feature = "functions")] +crate::func::macros::impl_function_traits!(::std::collections::HashSet; + < + V: Hash + Eq + FromReflect + TypePath + GetTypeRegistration, + S: TypePath + BuildHasher + Default + Send + Sync + > +); + +impl_reflect_for_hashset!(::bevy_utils::hashbrown::HashSet); +impl_type_path!(::bevy_utils::hashbrown::HashSet); +#[cfg(feature = "functions")] +crate::func::macros::impl_function_traits!(::bevy_utils::hashbrown::HashSet; + < + V: Hash + Eq + FromReflect + TypePath + GetTypeRegistration, + S: TypePath + BuildHasher + Default + Send + Sync + > +); + impl Map for ::std::collections::BTreeMap where K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Ord, diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index 868694f5d31cd..37957c098ad9e 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -482,6 +482,7 @@ mod list; mod map; mod path; mod reflect; +mod set; mod struct_trait; mod tuple; mod tuple_struct; @@ -531,6 +532,7 @@ pub use list::*; pub use map::*; pub use path::*; pub use reflect::*; +pub use set::*; pub use struct_trait::*; pub use tuple::*; pub use tuple_struct::*; diff --git a/crates/bevy_reflect/src/reflect.rs b/crates/bevy_reflect/src/reflect.rs index 8238048d0a5b0..2063cf317095f 100644 --- a/crates/bevy_reflect/src/reflect.rs +++ b/crates/bevy_reflect/src/reflect.rs @@ -1,6 +1,6 @@ use crate::{ array_debug, enum_debug, list_debug, map_debug, serde::Serializable, struct_debug, tuple_debug, - tuple_struct_debug, Array, DynamicTypePath, Enum, List, Map, Struct, Tuple, TupleStruct, + tuple_struct_debug, Array, DynamicTypePath, Enum, List, Map, Set, Struct, Tuple, TupleStruct, TypeInfo, TypePath, Typed, ValueInfo, }; use std::{ @@ -24,6 +24,7 @@ macro_rules! impl_reflect_enum { Self::List(_) => ReflectKind::List, Self::Array(_) => ReflectKind::Array, Self::Map(_) => ReflectKind::Map, + Self::Set(_) => ReflectKind::Set, Self::Enum(_) => ReflectKind::Enum, Self::Value(_) => ReflectKind::Value, } @@ -39,6 +40,7 @@ macro_rules! impl_reflect_enum { $name::List(_) => Self::List, $name::Array(_) => Self::Array, $name::Map(_) => Self::Map, + $name::Set(_) => Self::Set, $name::Enum(_) => Self::Enum, $name::Value(_) => Self::Value, } @@ -60,6 +62,7 @@ pub enum ReflectRef<'a> { List(&'a dyn List), Array(&'a dyn Array), Map(&'a dyn Map), + Set(&'a dyn Set), Enum(&'a dyn Enum), Value(&'a dyn Reflect), } @@ -78,6 +81,7 @@ pub enum ReflectMut<'a> { List(&'a mut dyn List), Array(&'a mut dyn Array), Map(&'a mut dyn Map), + Set(&'a mut dyn Set), Enum(&'a mut dyn Enum), Value(&'a mut dyn Reflect), } @@ -96,6 +100,7 @@ pub enum ReflectOwned { List(Box), Array(Box), Map(Box), + Set(Box), Enum(Box), Value(Box), } @@ -149,6 +154,7 @@ pub enum ReflectKind { List, Array, Map, + Set, Enum, Value, } @@ -162,6 +168,7 @@ impl std::fmt::Display for ReflectKind { ReflectKind::List => f.pad("list"), ReflectKind::Array => f.pad("array"), ReflectKind::Map => f.pad("map"), + ReflectKind::Set => f.pad("set"), ReflectKind::Enum => f.pad("enum"), ReflectKind::Value => f.pad("value"), } diff --git a/crates/bevy_reflect/src/serde/de.rs b/crates/bevy_reflect/src/serde/de.rs index 38dd63cd25df9..3670d0662384f 100644 --- a/crates/bevy_reflect/src/serde/de.rs +++ b/crates/bevy_reflect/src/serde/de.rs @@ -1,9 +1,9 @@ use crate::serde::SerializationData; use crate::{ - ArrayInfo, DynamicArray, DynamicEnum, DynamicList, DynamicMap, DynamicStruct, DynamicTuple, - DynamicTupleStruct, DynamicVariant, EnumInfo, ListInfo, Map, MapInfo, NamedField, Reflect, - ReflectDeserialize, StructInfo, StructVariantInfo, TupleInfo, TupleStructInfo, - TupleVariantInfo, TypeInfo, TypeRegistration, TypeRegistry, VariantInfo, + ArrayInfo, DynamicArray, DynamicEnum, DynamicList, DynamicMap, DynamicSet, DynamicStruct, + DynamicTuple, DynamicTupleStruct, DynamicVariant, EnumInfo, ListInfo, Map, MapInfo, NamedField, + Reflect, ReflectDeserialize, Set, SetInfo, StructInfo, StructVariantInfo, TupleInfo, + TupleStructInfo, TupleVariantInfo, TypeInfo, TypeRegistration, TypeRegistry, VariantInfo, }; use erased_serde::Deserializer; use serde::de::{ @@ -582,6 +582,14 @@ impl<'a, 'de> DeserializeSeed<'de> for TypedReflectDeserializer<'a> { dynamic_map.set_represented_type(Some(self.registration.type_info())); Ok(Box::new(dynamic_map)) } + TypeInfo::Set(set_info) => { + let mut dynamic_set = deserializer.deserialize_seq(SetVisitor { + set_info, + registry: self.registry, + })?; + dynamic_set.set_represented_type(Some(self.registration.type_info())); + Ok(Box::new(dynamic_set)) + } TypeInfo::Tuple(tuple_info) => { let mut dynamic_tuple = deserializer.deserialize_tuple( tuple_info.field_len(), @@ -817,6 +825,39 @@ impl<'a, 'de> Visitor<'de> for MapVisitor<'a> { } } +struct SetVisitor<'a> { + set_info: &'static SetInfo, + registry: &'a TypeRegistry, +} + +impl<'a, 'de> Visitor<'de> for SetVisitor<'a> { + type Value = DynamicSet; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str("reflected set value") + } + + fn visit_seq(self, mut set: V) -> Result + where + V: SeqAccess<'de>, + { + let mut dynamic_set = DynamicSet::default(); + let value_registration = get_registration( + self.set_info.value_type_id(), + self.set_info.value_type_path_table().path(), + self.registry, + )?; + while let Some(value) = set.next_element_seed(TypedReflectDeserializer { + registration: value_registration, + registry: self.registry, + })? { + dynamic_set.insert_boxed(value); + } + + Ok(dynamic_set) + } +} + struct EnumVisitor<'a> { enum_info: &'static EnumInfo, registration: &'a TypeRegistration, diff --git a/crates/bevy_reflect/src/serde/ser.rs b/crates/bevy_reflect/src/serde/ser.rs index f862d0139e7fb..ebc1bcf18554e 100644 --- a/crates/bevy_reflect/src/serde/ser.rs +++ b/crates/bevy_reflect/src/serde/ser.rs @@ -1,5 +1,5 @@ use crate::{ - Array, Enum, List, Map, Reflect, ReflectRef, ReflectSerialize, Struct, Tuple, TupleStruct, + Array, Enum, List, Map, Reflect, ReflectRef, ReflectSerialize, Set, Struct, Tuple, TupleStruct, TypeInfo, TypeRegistry, VariantInfo, VariantType, }; use serde::ser::{ @@ -223,6 +223,11 @@ impl<'a> Serialize for TypedReflectSerializer<'a> { registry: self.registry, } .serialize(serializer), + ReflectRef::Set(value) => SetSerializer { + set: value, + registry: self.registry, + } + .serialize(serializer), ReflectRef::Enum(value) => EnumSerializer { enum_value: value, registry: self.registry, @@ -503,6 +508,24 @@ impl<'a> Serialize for MapSerializer<'a> { } } +pub struct SetSerializer<'a> { + pub set: &'a dyn Set, + pub registry: &'a TypeRegistry, +} + +impl<'a> Serialize for SetSerializer<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_seq(Some(self.set.len()))?; + for value in self.set.iter() { + state.serialize_element(&TypedReflectSerializer::new(value, self.registry))?; + } + state.end() + } +} + pub struct ListSerializer<'a> { pub list: &'a dyn List, pub registry: &'a TypeRegistry, diff --git a/crates/bevy_reflect/src/set.rs b/crates/bevy_reflect/src/set.rs new file mode 100644 index 0000000000000..cde29d7ded25f --- /dev/null +++ b/crates/bevy_reflect/src/set.rs @@ -0,0 +1,520 @@ +use std::any::{Any, TypeId}; +use std::fmt::{Debug, Formatter}; + +use bevy_reflect_derive::impl_type_path; +use bevy_utils::hashbrown::hash_table::OccupiedEntry as HashTableOccupiedEntry; +use bevy_utils::hashbrown::HashTable; + +use crate::{ + self as bevy_reflect, hash_error, ApplyError, Reflect, ReflectKind, ReflectMut, ReflectOwned, + ReflectRef, TypeInfo, TypePath, TypePathTable, +}; + +/// A trait used to power [set-like] operations via [reflection]. +/// +/// Sets contain zero or more entries of a fixed type, and correspond to types like [`HashSet`](std::collections::HashSet). The +/// order of these entries is not guaranteed by this trait. +/// +/// # Hashing +/// +/// All values are expected to return a valid hash value from [`Reflect::reflect_hash`]. +/// If using the [`#[derive(Reflect)]`](derive@crate::Reflect) macro, this can be done by adding `#[reflect(Hash)]` +/// to the entire struct or enum. +/// This is true even for manual implementors who do not use the hashed value, +/// as it is still relied on by [`DynamicSet`]. +/// +/// # Example +/// +/// ``` +/// use bevy_reflect::{Reflect, Set}; +/// use bevy_utils::HashSet; +/// +/// +/// let foo: &mut dyn Set = &mut HashSet::::new(); +/// foo.insert_boxed(Box::new(123_u32)); +/// assert_eq!(foo.len(), 1); +/// +/// let field: &dyn Reflect = foo.get(&123_u32).unwrap(); +/// assert_eq!(field.downcast_ref::(), Some(&123_u32)); +/// ``` +/// +/// [set-like]: https://doc.rust-lang.org/stable/std/collections/struct.HashSet.html +/// [reflection]: crate +pub trait Set: Reflect { + /// Returns a reference to the value. + /// + /// If no value is contained, returns `None`. + fn get(&self, value: &dyn Reflect) -> Option<&dyn Reflect>; + + /// Returns the number of elements in the set. + fn len(&self) -> usize; + + /// Returns `true` if the list contains no elements. + fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Returns an iterator over the values of the set. + fn iter(&self) -> Box + '_>; + + /// Drain the values of this set to get a vector of owned values. + fn drain(self: Box) -> Vec>; + + /// Clones the set, producing a [`DynamicSet`]. + fn clone_dynamic(&self) -> DynamicSet; + + /// Inserts a value into the set. + /// + /// If the set did not have this value present, `true` is returned. + /// If the set did have this value present, `false` is returned. + fn insert_boxed(&mut self, value: Box) -> bool; + + /// Removes a value from the set. + /// + /// If the set did not have this value present, `true` is returned. + /// If the set did have this value present, `false` is returned. + fn remove(&mut self, value: &dyn Reflect) -> bool; + + /// Checks if the given value is contained in the set + fn contains(&self, value: &dyn Reflect) -> bool; +} + +/// A container for compile-time set info. +#[derive(Clone, Debug)] +pub struct SetInfo { + type_path: TypePathTable, + type_id: TypeId, + value_type_path: TypePathTable, + value_type_id: TypeId, + #[cfg(feature = "documentation")] + docs: Option<&'static str>, +} + +impl SetInfo { + /// Create a new [`SetInfo`]. + pub fn new() -> Self { + Self { + type_path: TypePathTable::of::(), + type_id: TypeId::of::(), + value_type_path: TypePathTable::of::(), + value_type_id: TypeId::of::(), + #[cfg(feature = "documentation")] + docs: None, + } + } + + /// Sets the docstring for this set. + #[cfg(feature = "documentation")] + pub fn with_docs(self, docs: Option<&'static str>) -> Self { + Self { docs, ..self } + } + + /// A representation of the type path of the set. + /// + /// Provides dynamic access to all methods on [`TypePath`]. + pub fn type_path_table(&self) -> &TypePathTable { + &self.type_path + } + + /// The [stable, full type path] of the set. + /// + /// Use [`type_path_table`] if you need access to the other methods on [`TypePath`]. + /// + /// [stable, full type path]: TypePath + /// [`type_path_table`]: Self::type_path_table + pub fn type_path(&self) -> &'static str { + self.type_path_table().path() + } + + /// The [`TypeId`] of the set. + pub fn type_id(&self) -> TypeId { + self.type_id + } + + /// Check if the given type matches the set type. + pub fn is(&self) -> bool { + TypeId::of::() == self.type_id + } + + /// A representation of the type path of the value type. + /// + /// Provides dynamic access to all methods on [`TypePath`]. + pub fn value_type_path_table(&self) -> &TypePathTable { + &self.value_type_path + } + + /// The [`TypeId`] of the value. + pub fn value_type_id(&self) -> TypeId { + self.value_type_id + } + + /// Check if the given type matches the value type. + pub fn value_is(&self) -> bool { + TypeId::of::() == self.value_type_id + } + + /// The docstring of this set, if any. + #[cfg(feature = "documentation")] + pub fn docs(&self) -> Option<&'static str> { + self.docs + } +} + +/// An ordered set of reflected values. +#[derive(Default)] +pub struct DynamicSet { + represented_type: Option<&'static TypeInfo>, + hash_table: HashTable>, +} + +impl DynamicSet { + /// Sets the [type] to be represented by this `DynamicSet`. + /// + /// # Panics + /// + /// Panics if the given [type] is not a [`TypeInfo::Set`]. + /// + /// [type]: TypeInfo + pub fn set_represented_type(&mut self, represented_type: Option<&'static TypeInfo>) { + if let Some(represented_type) = represented_type { + assert!( + matches!(represented_type, TypeInfo::Set(_)), + "expected TypeInfo::Set but received: {:?}", + represented_type + ); + } + + self.represented_type = represented_type; + } + + /// Inserts a typed value into the set. + pub fn insert(&mut self, value: V) { + self.insert_boxed(Box::new(value)); + } + + #[allow(clippy::borrowed_box)] + fn internal_hash(value: &Box) -> u64 { + value.reflect_hash().expect(hash_error!(value)) + } + + #[allow(clippy::borrowed_box)] + fn internal_eq(value: &Box) -> impl FnMut(&Box) -> bool + '_ { + |other| { + value + .reflect_partial_eq(&**other) + .expect("Underlying type does not reflect `PartialEq` and hence doesn't support equality checks") + } + } +} + +// I just created this function to have only one point where we ignore the rust warning about the +// unused allocation +fn box_and_clone(val: &dyn Reflect) -> Box { + #[allow(unused_allocation)] + Box::new(val).clone_value() +} + +impl Set for DynamicSet { + fn get(&self, value: &dyn Reflect) -> Option<&dyn Reflect> { + let boxed = box_and_clone(value); + self.hash_table + .find(Self::internal_hash(&boxed), Self::internal_eq(&boxed)) + .map(|value| &**value) + } + + fn len(&self) -> usize { + self.hash_table.len() + } + + fn iter(&self) -> Box + '_> { + let iter = self.hash_table.iter().map(|v| &**v); + Box::new(iter) + } + + fn drain(self: Box) -> Vec> { + self.hash_table.into_iter().collect::>() + } + + fn clone_dynamic(&self) -> DynamicSet { + let mut hash_table = HashTable::new(); + self.hash_table + .iter() + .map(|value| value.clone_value()) + .for_each(|value| { + hash_table.insert_unique(Self::internal_hash(&value), value, Self::internal_hash); + }); + + DynamicSet { + represented_type: self.represented_type, + hash_table, + } + } + + fn insert_boxed(&mut self, value: Box) -> bool { + assert_eq!( + value.reflect_partial_eq(&*value), + Some(true), + "Values inserted in `Set` like types are expected to reflect `PartialEq`" + ); + match self + .hash_table + .find_mut(Self::internal_hash(&value), Self::internal_eq(&value)) + { + Some(old) => { + *old = value; + false + } + None => { + self.hash_table.insert_unique( + Self::internal_hash(&value), + value, + Self::internal_hash, + ); + true + } + } + } + + fn remove(&mut self, value: &dyn Reflect) -> bool { + let boxed = box_and_clone(value); + self.hash_table + .find_entry(Self::internal_hash(&boxed), Self::internal_eq(&boxed)) + .map(HashTableOccupiedEntry::remove) + .is_ok() + } + + fn contains(&self, value: &dyn Reflect) -> bool { + let boxed = box_and_clone(value); + self.hash_table + .find(Self::internal_hash(&boxed), Self::internal_eq(&boxed)) + .is_some() + } +} + +impl Reflect for DynamicSet { + #[inline] + fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { + self.represented_type + } + + fn into_any(self: Box) -> Box { + self + } + + fn as_any(&self) -> &dyn Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } + + #[inline] + fn into_reflect(self: Box) -> Box { + self + } + + #[inline] + fn as_reflect(&self) -> &dyn Reflect { + self + } + + #[inline] + fn as_reflect_mut(&mut self) -> &mut dyn Reflect { + self + } + + fn apply(&mut self, value: &dyn Reflect) { + set_apply(self, value); + } + + fn try_apply(&mut self, value: &dyn Reflect) -> Result<(), ApplyError> { + set_try_apply(self, value) + } + + fn set(&mut self, value: Box) -> Result<(), Box> { + *self = value.take()?; + Ok(()) + } + + fn reflect_kind(&self) -> ReflectKind { + ReflectKind::Set + } + + fn reflect_ref(&self) -> ReflectRef { + ReflectRef::Set(self) + } + + fn reflect_mut(&mut self) -> ReflectMut { + ReflectMut::Set(self) + } + + fn reflect_owned(self: Box) -> ReflectOwned { + ReflectOwned::Set(self) + } + + fn clone_value(&self) -> Box { + Box::new(self.clone_dynamic()) + } + + fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option { + set_partial_eq(self, value) + } + + fn debug(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "DynamicSet(")?; + set_debug(self, f)?; + write!(f, ")") + } + + #[inline] + fn is_dynamic(&self) -> bool { + true + } +} + +impl_type_path!((in bevy_reflect) DynamicSet); + +impl Debug for DynamicSet { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.debug(f) + } +} + +impl IntoIterator for DynamicSet { + type Item = Box; + type IntoIter = bevy_utils::hashbrown::hash_table::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.hash_table.into_iter() + } +} + +/// Compares a [`Set`] with a [`Reflect`] value. +/// +/// Returns true if and only if all of the following are true: +/// - `b` is a set; +/// - `b` is the same length as `a`; +/// - For each value pair in `a`, `b` contains the value too, +/// and [`Reflect::reflect_partial_eq`] returns `Some(true)` for the two values. +/// +/// Returns [`None`] if the comparison couldn't even be performed. +#[inline] +pub fn set_partial_eq(a: &M, b: &dyn Reflect) -> Option { + let ReflectRef::Set(set) = b.reflect_ref() else { + return Some(false); + }; + + if a.len() != set.len() { + return Some(false); + } + + for value in a.iter() { + if let Some(set_value) = set.get(value) { + let eq_result = value.reflect_partial_eq(set_value); + if let failed @ (Some(false) | None) = eq_result { + return failed; + } + } else { + return Some(false); + } + } + + Some(true) +} + +/// The default debug formatter for [`Set`] types. +/// +/// # Example +/// ``` +/// # use bevy_utils::HashSet; +/// use bevy_reflect::Reflect; +/// +/// let mut my_set = HashSet::new(); +/// my_set.insert(String::from("Hello")); +/// println!("{:#?}", &my_set as &dyn Reflect); +/// +/// // Output: +/// +/// // { +/// // "Hello", +/// // } +/// ``` +#[inline] +pub fn set_debug(dyn_set: &dyn Set, f: &mut Formatter<'_>) -> std::fmt::Result { + let mut debug = f.debug_set(); + for value in dyn_set.iter() { + debug.entry(&value as &dyn Debug); + } + debug.finish() +} + +/// Applies the elements of reflected set `b` to the corresponding elements of set `a`. +/// +/// If a value from `b` does not exist in `a`, the value is cloned and inserted. +/// +/// # Panics +/// +/// This function panics if `b` is not a reflected set. +#[inline] +pub fn set_apply(a: &mut M, b: &dyn Reflect) { + if let ReflectRef::Set(set_value) = b.reflect_ref() { + for b_value in set_value.iter() { + if a.get(b_value).is_none() { + a.insert_boxed(b_value.clone_value()); + } + } + } else { + panic!("Attempted to apply a non-set type to a set type."); + } +} + +/// Tries to apply the elements of reflected set `b` to the corresponding elements of set `a` +/// and returns a Result. +/// +/// If a key from `b` does not exist in `a`, the value is cloned and inserted. +/// +/// # Errors +/// +/// This function returns an [`ApplyError::MismatchedKinds`] if `b` is not a reflected set or if +/// applying elements to each other fails. +#[inline] +pub fn set_try_apply(a: &mut S, b: &dyn Reflect) -> Result<(), ApplyError> { + if let ReflectRef::Set(set_value) = b.reflect_ref() { + for b_value in set_value.iter() { + if a.get(b_value).is_none() { + a.insert_boxed(b_value.clone_value()); + } + } + } else { + return Err(ApplyError::MismatchedKinds { + from_kind: b.reflect_kind(), + to_kind: ReflectKind::Set, + }); + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::DynamicSet; + + #[test] + fn test_into_iter() { + let expected = ["foo", "bar", "baz"]; + + let mut set = DynamicSet::default(); + set.insert(expected[0].to_string()); + set.insert(expected[1].to_string()); + set.insert(expected[2].to_string()); + + for item in set.into_iter() { + let value = item.take::().expect("couldn't downcast to String"); + let index = expected + .iter() + .position(|i| *i == value.as_str()) + .expect("Element found in expected array"); + assert_eq!(expected[index], value); + } + } +} diff --git a/crates/bevy_reflect/src/type_info.rs b/crates/bevy_reflect/src/type_info.rs index 1de6e754a1422..bbd9b22464104 100644 --- a/crates/bevy_reflect/src/type_info.rs +++ b/crates/bevy_reflect/src/type_info.rs @@ -1,7 +1,7 @@ use crate::{ ArrayInfo, DynamicArray, DynamicEnum, DynamicList, DynamicMap, DynamicStruct, DynamicTuple, - DynamicTupleStruct, EnumInfo, ListInfo, MapInfo, Reflect, ReflectKind, StructInfo, TupleInfo, - TupleStructInfo, TypePath, TypePathTable, + DynamicTupleStruct, EnumInfo, ListInfo, MapInfo, Reflect, ReflectKind, SetInfo, StructInfo, + TupleInfo, TupleStructInfo, TypePath, TypePathTable, }; use std::any::{Any, TypeId}; use std::fmt::Debug; @@ -164,6 +164,7 @@ pub enum TypeInfo { List(ListInfo), Array(ArrayInfo), Map(MapInfo), + Set(SetInfo), Enum(EnumInfo), Value(ValueInfo), } @@ -178,6 +179,7 @@ impl TypeInfo { Self::List(info) => info.type_id(), Self::Array(info) => info.type_id(), Self::Map(info) => info.type_id(), + Self::Set(info) => info.type_id(), Self::Enum(info) => info.type_id(), Self::Value(info) => info.type_id(), } @@ -194,6 +196,7 @@ impl TypeInfo { Self::List(info) => info.type_path_table(), Self::Array(info) => info.type_path_table(), Self::Map(info) => info.type_path_table(), + Self::Set(info) => info.type_path_table(), Self::Enum(info) => info.type_path_table(), Self::Value(info) => info.type_path_table(), } @@ -224,6 +227,7 @@ impl TypeInfo { Self::List(info) => info.docs(), Self::Array(info) => info.docs(), Self::Map(info) => info.docs(), + Self::Set(info) => info.docs(), Self::Enum(info) => info.docs(), Self::Value(info) => info.docs(), } @@ -240,6 +244,7 @@ impl TypeInfo { Self::List(_) => ReflectKind::List, Self::Array(_) => ReflectKind::Array, Self::Map(_) => ReflectKind::Map, + Self::Set(_) => ReflectKind::Set, Self::Enum(_) => ReflectKind::Enum, Self::Value(_) => ReflectKind::Value, } diff --git a/examples/reflection/reflection_types.rs b/examples/reflection/reflection_types.rs index 3271b5a5d38b4..1ecb88ca8b8c2 100644 --- a/examples/reflection/reflection_types.rs +++ b/examples/reflection/reflection_types.rs @@ -105,6 +105,10 @@ fn setup() { // This exposes "map" operations on your type, such as getting / inserting by key. // Map is automatically implemented for relevant core types like HashMap ReflectRef::Map(_) => {} + // `Set` is a special trait that can be manually implemented (instead of deriving Reflect). + // This exposes "set" operations on your type, such as getting / inserting by value. + // Set is automatically implemented for relevant core types like HashSet + ReflectRef::Set(_) => {} // `Value` types do not implement any of the other traits above. They are simply a Reflect // implementation. Value is implemented for core types like i32, usize, f32, and // String.