mirror of
https://github.com/bevyengine/bevy
synced 2024-12-29 22:43:14 +00:00
c27a3cff6d
# Objective Currently, `Reflect` is unsafe to implement because of a contract in which `any` and `any_mut` must return `self`, or `downcast` will cause UB. This PR makes `Reflect` safe, makes `downcast` not use unsafe, and eliminates this contract. ## Solution This PR adds a method to `Reflect`, `any`. It also renames the old `any` to `as_any`. `any` now takes a `Box<Self>` and returns a `Box<dyn Any>`. --- ## Changelog ### Added: - `any()` method - `represents()` method ### Changed: - `Reflect` is now a safe trait - `downcast()` is now safe - The old `any` is now called `as_any`, and `any_mut` is now `as_mut_any` ## Migration Guide - Reflect derives should not have to change anything - Manual reflect impls will need to remove the `unsafe` keyword, add `any()` implementations, and rename the old `any` and `any_mut` to `as_any` and `as_mut_any`. - Calls to `any`/`any_mut` must be changed to `as_any`/`as_mut_any` ## Points of discussion: - Should renaming `any` be avoided and instead name the new method `any_box`? - ~~Could there be a performance regression from avoiding the unsafe? I doubt it, but this change does seem to introduce redundant checks.~~ - ~~Could/should `is` and `type_id()` be implemented differently? For example, moving `is` onto `Reflect` as an `fn(&self, TypeId) -> bool`~~ Co-authored-by: PROMETHIA-27 <42193387+PROMETHIA-27@users.noreply.github.com>
144 lines
5.4 KiB
Rust
144 lines
5.4 KiB
Rust
//! Helpers for working with Bevy reflection.
|
|
|
|
use crate::TypeInfo;
|
|
use bevy_utils::HashMap;
|
|
use once_cell::race::OnceBox;
|
|
use parking_lot::RwLock;
|
|
use std::any::{Any, TypeId};
|
|
|
|
/// A container for [`TypeInfo`] over non-generic types, allowing instances to be stored statically.
|
|
///
|
|
/// This is specifically meant for use with _non_-generic types. If your type _is_ generic,
|
|
/// then use [`GenericTypeInfoCell`] instead. Otherwise, it will not take into account all
|
|
/// monomorphizations of your type.
|
|
///
|
|
/// ## Example
|
|
///
|
|
/// ```
|
|
/// # use std::any::Any;
|
|
/// # use bevy_reflect::{NamedField, Reflect, ReflectMut, ReflectRef, StructInfo, Typed, TypeInfo};
|
|
/// use bevy_reflect::utility::NonGenericTypeInfoCell;
|
|
///
|
|
/// struct Foo {
|
|
/// bar: i32
|
|
/// }
|
|
///
|
|
/// impl Typed for Foo {
|
|
/// fn type_info() -> &'static TypeInfo {
|
|
/// static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new();
|
|
/// CELL.get_or_set(|| {
|
|
/// let fields = [NamedField::new::<i32, _>("bar")];
|
|
/// let info = StructInfo::new::<Self>(&fields);
|
|
/// TypeInfo::Struct(info)
|
|
/// })
|
|
/// }
|
|
/// }
|
|
/// #
|
|
/// # impl Reflect for Foo {
|
|
/// # fn type_name(&self) -> &str { todo!() }
|
|
/// # fn get_type_info(&self) -> &'static TypeInfo { todo!() }
|
|
/// # fn into_any(self: Box<Self>) -> Box<dyn Any> { todo!() }
|
|
/// # fn as_any(&self) -> &dyn Any { todo!() }
|
|
/// # fn as_any_mut(&mut self) -> &mut dyn Any { todo!() }
|
|
/// # fn as_reflect(&self) -> &dyn Reflect { todo!() }
|
|
/// # fn as_reflect_mut(&mut self) -> &mut dyn Reflect { todo!() }
|
|
/// # fn apply(&mut self, value: &dyn Reflect) { todo!() }
|
|
/// # fn set(&mut self, value: Box<dyn Reflect>) -> Result<(), Box<dyn Reflect>> { todo!() }
|
|
/// # fn reflect_ref(&self) -> ReflectRef { todo!() }
|
|
/// # fn reflect_mut(&mut self) -> ReflectMut { todo!() }
|
|
/// # fn clone_value(&self) -> Box<dyn Reflect> { todo!() }
|
|
/// # }
|
|
/// ```
|
|
pub struct NonGenericTypeInfoCell(OnceBox<TypeInfo>);
|
|
|
|
impl NonGenericTypeInfoCell {
|
|
/// Initialize a [`NonGenericTypeInfoCell`] for non-generic types.
|
|
pub const fn new() -> Self {
|
|
Self(OnceBox::new())
|
|
}
|
|
|
|
/// Returns a reference to the [`TypeInfo`] stored in the cell.
|
|
///
|
|
/// If there is no [`TypeInfo`] found, a new one will be generated from the given function.
|
|
///
|
|
/// [`TypeInfos`]: TypeInfo
|
|
pub fn get_or_set<F>(&self, f: F) -> &TypeInfo
|
|
where
|
|
F: FnOnce() -> TypeInfo,
|
|
{
|
|
self.0.get_or_init(|| Box::new(f()))
|
|
}
|
|
}
|
|
|
|
/// A container for [`TypeInfo`] over generic types, allowing instances to be stored statically.
|
|
///
|
|
/// This is specifically meant for use with generic types. If your type isn't generic,
|
|
/// then use [`NonGenericTypeInfoCell`] instead as it should be much more performant.
|
|
///
|
|
/// ## Example
|
|
///
|
|
/// ```
|
|
/// # use std::any::Any;
|
|
/// # use bevy_reflect::{Reflect, ReflectMut, ReflectRef, TupleStructInfo, Typed, TypeInfo, UnnamedField};
|
|
/// use bevy_reflect::utility::GenericTypeInfoCell;
|
|
///
|
|
/// struct Foo<T: Reflect>(T);
|
|
///
|
|
/// impl<T: Reflect> Typed for Foo<T> {
|
|
/// fn type_info() -> &'static TypeInfo {
|
|
/// static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new();
|
|
/// CELL.get_or_insert::<Self, _>(|| {
|
|
/// let fields = [UnnamedField::new::<T>(0)];
|
|
/// let info = TupleStructInfo::new::<Self>(&fields);
|
|
/// TypeInfo::TupleStruct(info)
|
|
/// })
|
|
/// }
|
|
/// }
|
|
/// #
|
|
/// # impl<T: Reflect> Reflect for Foo<T> {
|
|
/// # fn type_name(&self) -> &str { todo!() }
|
|
/// # fn get_type_info(&self) -> &'static TypeInfo { todo!() }
|
|
/// # fn into_any(self: Box<Self>) -> Box<dyn Any> { todo!() }
|
|
/// # fn as_any(&self) -> &dyn Any { todo!() }
|
|
/// # fn as_any_mut(&mut self) -> &mut dyn Any { todo!() }
|
|
/// # fn as_reflect(&self) -> &dyn Reflect { todo!() }
|
|
/// # fn as_reflect_mut(&mut self) -> &mut dyn Reflect { todo!() }
|
|
/// # fn apply(&mut self, value: &dyn Reflect) { todo!() }
|
|
/// # fn set(&mut self, value: Box<dyn Reflect>) -> Result<(), Box<dyn Reflect>> { todo!() }
|
|
/// # fn reflect_ref(&self) -> ReflectRef { todo!() }
|
|
/// # fn reflect_mut(&mut self) -> ReflectMut { todo!() }
|
|
/// # fn clone_value(&self) -> Box<dyn Reflect> { todo!() }
|
|
/// # }
|
|
/// ```
|
|
pub struct GenericTypeInfoCell(OnceBox<RwLock<HashMap<TypeId, &'static TypeInfo>>>);
|
|
|
|
impl GenericTypeInfoCell {
|
|
/// Initialize a [`GenericTypeInfoCell`] for generic types.
|
|
pub const fn new() -> Self {
|
|
Self(OnceBox::new())
|
|
}
|
|
|
|
/// Returns a reference to the [`TypeInfo`] stored in the cell.
|
|
///
|
|
/// This method will then return the correct [`TypeInfo`] reference for the given type `T`.
|
|
/// If there is no [`TypeInfo`] found, a new one will be generated from the given function.
|
|
pub fn get_or_insert<T, F>(&self, f: F) -> &TypeInfo
|
|
where
|
|
T: Any + ?Sized,
|
|
F: FnOnce() -> TypeInfo,
|
|
{
|
|
let type_id = TypeId::of::<T>();
|
|
let mapping = self.0.get_or_init(|| Box::new(RwLock::default()));
|
|
if let Some(info) = mapping.read().get(&type_id) {
|
|
return info;
|
|
}
|
|
|
|
mapping.write().entry(type_id).or_insert_with(|| {
|
|
// We leak here in order to obtain a `&'static` reference.
|
|
// Otherwise, we won't be able to return a reference due to the `RwLock`.
|
|
// This should be okay, though, since we expect it to remain statically
|
|
// available over the course of the application.
|
|
Box::leak(Box::new(f()))
|
|
})
|
|
}
|
|
}
|