Skip to content

Commit

Permalink
bevy_reflect: Add Type type (#14838)
Browse files Browse the repository at this point in the history
# Objective

Closes #7622.

I was working on adding support for reflecting generic functions and
found that I wanted to use an argument's `TypeId` for hashing and
comparison, but its `TypePath` for debugging and error messaging.

While I could just keep them separate, place them in a tuple or a local
struct or something, I think I see an opportunity to make a dedicate
type for this.

Additionally, we can use this type to clean up some duplication amongst
the type info structs in a manner similar to #7622.

## Solution

Added the `Type` type. This should be seen as the most basic
representation of a type apart from `TypeId`. It stores both the
`TypeId` of the type as well as its `TypePathTable`.

The `Hash` and `PartialEq` implementations rely on the `TypeId`, while
the `Debug` implementation relies on the `TypePath`.

This makes it especially useful as a key in a `HashMap` since we get the
speed of the `TypeId` hashing/comparisons with the readability of
`TypePath`.

With this type, we're able to reduce the duplication across the type
info structs by removing individual fields for `TypeId` and
`TypePathTable`, replacing them with a single `Type` field. Similarly,
we can remove many duplicate methods and replace it with a macro that
delegates to the stored `Type`.

### Caveats

It should be noted that this type is currently 3x larger than `TypeId`.
On my machine, it's 48 bytes compared to `TypeId`'s 16. While this
doesn't matter for `TypeInfo` since it would contain that data
regardless, it is something to keep in mind when using elsewhere.

## Testing

All tests should pass as normal:

```
cargo test --package bevy_reflect
```

---

## Showcase

`bevy_reflect` now exports a `Type` struct. This type contains both the
`TypeId` and the `TypePathTable` of the given type, allowing it to be
used like `TypeId` but have the debuggability of `TypePath`.

```rust
// We can create this for any type implementing `TypePath`:
let ty = Type::of::<String>();

// It has `Hash` and `Eq` impls powered by `TypeId`, making it useful for maps:
let mut map = HashMap::<Type, i32>::new();
map.insert(ty, 25);

// And it has a human-readable `Debug` representation:
let debug = format!("{:?}", map);
assert_eq!(debug, "{alloc::string::String: 25}");
```

## Migration Guide

Certain type info structs now only return their item types as `Type`
instead of exposing direct methods on them.

The following methods have been removed:

- `ArrayInfo::item_type_path_table`
- `ArrayInfo::item_type_id`
- `ArrayInfo::item_is`
- `ListInfo::item_type_path_table`
- `ListInfo::item_type_id`
- `ListInfo::item_is`
- `SetInfo::value_type_path_table`
- `SetInfo::value_type_id`
- `SetInfo::value_is`
- `MapInfo::key_type_path_table`
- `MapInfo::key_type_id`
- `MapInfo::key_is`
- `MapInfo::value_type_path_table`
- `MapInfo::value_type_id`
- `MapInfo::value_is`

Instead, access the `Type` directly using one of the new methods:

- `ArrayInfo::item_ty`
- `ListInfo::item_ty`
- `SetInfo::value_ty`
- `MapInfo::key_ty`
- `MapInfo::value_ty`

For example:

```rust
// BEFORE
let type_id = array_info.item_type_id();

// AFTER
let type_id = array_info.item_ty().id();
```
  • Loading branch information
MrGVSV authored Aug 25, 2024
1 parent f9d7a2c commit 3892adc
Show file tree
Hide file tree
Showing 14 changed files with 342 additions and 512 deletions.
62 changes: 12 additions & 50 deletions crates/bevy_reflect/src/array.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use crate::type_info::impl_type_methods;
use crate::{
self as bevy_reflect, utility::reflect_hasher, ApplyError, MaybeTyped, PartialReflect, Reflect,
ReflectKind, ReflectMut, ReflectOwned, ReflectRef, TypeInfo, TypePath, TypePathTable,
ReflectKind, ReflectMut, ReflectOwned, ReflectRef, Type, TypeInfo, TypePath,
};
use bevy_reflect_derive::impl_type_path;
use std::{
any::{Any, TypeId},
any::Any,
fmt::{Debug, Formatter},
hash::{Hash, Hasher},
};
Expand Down Expand Up @@ -77,11 +78,9 @@ pub trait Array: PartialReflect {
/// A container for compile-time array info.
#[derive(Clone, Debug)]
pub struct ArrayInfo {
type_path: TypePathTable,
type_id: TypeId,
ty: Type,
item_info: fn() -> Option<&'static TypeInfo>,
item_type_path: TypePathTable,
item_type_id: TypeId,
item_ty: Type,
capacity: usize,
#[cfg(feature = "documentation")]
docs: Option<&'static str>,
Expand All @@ -98,11 +97,9 @@ impl ArrayInfo {
capacity: usize,
) -> Self {
Self {
type_path: TypePathTable::of::<TArray>(),
type_id: TypeId::of::<TArray>(),
ty: Type::of::<TArray>(),
item_info: TItem::maybe_type_info,
item_type_path: TypePathTable::of::<TItem>(),
item_type_id: TypeId::of::<TItem>(),
item_ty: Type::of::<TItem>(),
capacity,
#[cfg(feature = "documentation")]
docs: None,
Expand All @@ -120,32 +117,7 @@ impl ArrayInfo {
self.capacity
}

/// A representation of the type path of the array.
///
/// 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 array.
///
/// 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 array.
pub fn type_id(&self) -> TypeId {
self.type_id
}

/// Check if the given type matches the array type.
pub fn is<T: Any>(&self) -> bool {
TypeId::of::<T>() == self.type_id
}
impl_type_methods!(ty);

/// The [`TypeInfo`] of the array item.
///
Expand All @@ -155,21 +127,11 @@ impl ArrayInfo {
(self.item_info)()
}

/// A representation of the type path of the array item.
/// The [type] of the array item.
///
/// Provides dynamic access to all methods on [`TypePath`].
pub fn item_type_path_table(&self) -> &TypePathTable {
&self.item_type_path
}

/// The [`TypeId`] of the array item.
pub fn item_type_id(&self) -> TypeId {
self.item_type_id
}

/// Check if the given type matches the array item type.
pub fn item_is<T: Any>(&self) -> bool {
TypeId::of::<T>() == self.item_type_id
/// [type]: Type
pub fn item_ty(&self) -> Type {
self.item_ty
}

/// The docstring of this array, if any.
Expand Down
37 changes: 5 additions & 32 deletions crates/bevy_reflect/src/enums/enum_trait.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::attributes::{impl_custom_attribute_methods, CustomAttributes};
use crate::{DynamicEnum, PartialReflect, TypePath, TypePathTable, VariantInfo, VariantType};
use crate::type_info::impl_type_methods;
use crate::{DynamicEnum, PartialReflect, Type, TypePath, VariantInfo, VariantType};
use bevy_utils::HashMap;
use std::any::{Any, TypeId};
use std::slice::Iter;
use std::sync::Arc;

Expand Down Expand Up @@ -135,8 +135,7 @@ pub trait Enum: PartialReflect {
/// A container for compile-time enum info, used by [`TypeInfo`](crate::TypeInfo).
#[derive(Clone, Debug)]
pub struct EnumInfo {
type_path: TypePathTable,
type_id: TypeId,
ty: Type,
variants: Box<[VariantInfo]>,
variant_names: Box<[&'static str]>,
variant_indices: HashMap<&'static str, usize>,
Expand All @@ -162,8 +161,7 @@ impl EnumInfo {
let variant_names = variants.iter().map(VariantInfo::name).collect();

Self {
type_path: TypePathTable::of::<TEnum>(),
type_id: TypeId::of::<TEnum>(),
ty: Type::of::<TEnum>(),
variants: variants.to_vec().into_boxed_slice(),
variant_names,
variant_indices,
Expand Down Expand Up @@ -231,32 +229,7 @@ impl EnumInfo {
self.variants.len()
}

/// A representation of the type path of the value.
///
/// 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 value.
///
/// 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 enum.
pub fn type_id(&self) -> TypeId {
self.type_id
}

/// Check if the given type matches the enum type.
pub fn is<T: Any>(&self) -> bool {
TypeId::of::<T>() == self.type_id
}
impl_type_methods!(ty);

/// The docstring of this enum, if any.
#[cfg(feature = "documentation")]
Expand Down
70 changes: 8 additions & 62 deletions crates/bevy_reflect/src/fields.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
use crate::attributes::{impl_custom_attribute_methods, CustomAttributes};
use crate::{MaybeTyped, PartialReflect, TypeInfo, TypePath, TypePathTable};
use std::any::{Any, TypeId};
use crate::type_info::impl_type_methods;
use crate::{MaybeTyped, PartialReflect, Type, TypeInfo, TypePath};
use std::sync::Arc;

/// The named field of a reflected struct.
#[derive(Clone, Debug)]
pub struct NamedField {
name: &'static str,
type_info: fn() -> Option<&'static TypeInfo>,
type_path: TypePathTable,
type_id: TypeId,
ty: Type,
custom_attributes: Arc<CustomAttributes>,
#[cfg(feature = "documentation")]
docs: Option<&'static str>,
Expand All @@ -21,8 +20,7 @@ impl NamedField {
Self {
name,
type_info: T::maybe_type_info,
type_path: TypePathTable::of::<T>(),
type_id: TypeId::of::<T>(),
ty: Type::of::<T>(),
custom_attributes: Arc::new(CustomAttributes::default()),
#[cfg(feature = "documentation")]
docs: None,
Expand Down Expand Up @@ -57,32 +55,7 @@ impl NamedField {
(self.type_info)()
}

/// A representation of the type path of the field.
///
/// 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 field.
///
/// 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 field.
pub fn type_id(&self) -> TypeId {
self.type_id
}

/// Check if the given type matches the field type.
pub fn is<T: Any>(&self) -> bool {
TypeId::of::<T>() == self.type_id
}
impl_type_methods!(ty);

/// The docstring of this field, if any.
#[cfg(feature = "documentation")]
Expand All @@ -98,8 +71,7 @@ impl NamedField {
pub struct UnnamedField {
index: usize,
type_info: fn() -> Option<&'static TypeInfo>,
type_path: TypePathTable,
type_id: TypeId,
ty: Type,
custom_attributes: Arc<CustomAttributes>,
#[cfg(feature = "documentation")]
docs: Option<&'static str>,
Expand All @@ -110,8 +82,7 @@ impl UnnamedField {
Self {
index,
type_info: T::maybe_type_info,
type_path: TypePathTable::of::<T>(),
type_id: TypeId::of::<T>(),
ty: Type::of::<T>(),
custom_attributes: Arc::new(CustomAttributes::default()),
#[cfg(feature = "documentation")]
docs: None,
Expand Down Expand Up @@ -146,32 +117,7 @@ impl UnnamedField {
(self.type_info)()
}

/// A representation of the type path of the field.
///
/// 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 field.
///
/// 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 field.
pub fn type_id(&self) -> TypeId {
self.type_id
}

/// Check if the given type matches the field type.
pub fn is<T: Any>(&self) -> bool {
TypeId::of::<T>() == self.type_id
}
impl_type_methods!(ty);

/// The docstring of this field, if any.
#[cfg(feature = "documentation")]
Expand Down
18 changes: 7 additions & 11 deletions crates/bevy_reflect/src/func/args/info.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use alloc::borrow::Cow;

use crate::func::args::{GetOwnership, Ownership};
use crate::TypePath;
use crate::type_info::impl_type_methods;
use crate::{Type, TypePath};

/// Type information for an [`Arg`] used in a [`DynamicFunction`] or [`DynamicFunctionMut`].
///
Expand All @@ -16,10 +17,10 @@ pub struct ArgInfo {
name: Option<Cow<'static, str>>,
/// The ownership of the argument.
ownership: Ownership,
/// The [type path] of the argument.
/// The [type] of the argument.
///
/// [type path]: TypePath::type_path
type_path: &'static str,
/// [type]: Type
ty: Type,
}

impl ArgInfo {
Expand All @@ -31,7 +32,7 @@ impl ArgInfo {
index,
name: None,
ownership: T::ownership(),
type_path: T::type_path(),
ty: Type::of::<T>(),
}
}

Expand Down Expand Up @@ -72,12 +73,7 @@ impl ArgInfo {
self.ownership
}

/// The [type path] of the argument.
///
/// [type path]: TypePath::type_path
pub fn type_path(&self) -> &'static str {
self.type_path
}
impl_type_methods!(ty);

/// Get an ID representing the argument.
///
Expand Down
14 changes: 6 additions & 8 deletions crates/bevy_reflect/src/func/info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ use alloc::borrow::Cow;
use bevy_utils::all_tuples;

use crate::func::args::{ArgInfo, GetOwnership, Ownership};
use crate::TypePath;
use crate::type_info::impl_type_methods;
use crate::{Type, TypePath};

/// Type information for a [`DynamicFunction`] or [`DynamicFunctionMut`].
///
Expand Down Expand Up @@ -140,25 +141,22 @@ impl FunctionInfo {
/// [`DynamicFunctionMut`]: crate::func::DynamicFunctionMut
#[derive(Debug, Clone)]
pub struct ReturnInfo {
type_path: &'static str,
ty: Type,
ownership: Ownership,
}

impl ReturnInfo {
/// Create a new [`ReturnInfo`] representing the given type, `T`.
pub fn new<T: TypePath + GetOwnership>() -> Self {
Self {
type_path: T::type_path(),
ty: Type::of::<T>(),
ownership: T::ownership(),
}
}

/// The type path of the return type.
pub fn type_path(&self) -> &'static str {
self.type_path
}
impl_type_methods!(ty);

/// The ownership of the return type.
/// The ownership of this type.
pub fn ownership(&self) -> Ownership {
self.ownership
}
Expand Down
Loading

0 comments on commit 3892adc

Please sign in to comment.