From 24c0ac3cef27e86a566a7f16f7c10cae71e5af44 Mon Sep 17 00:00:00 2001 From: Jakob Hellermann Date: Tue, 19 Jul 2022 23:00:34 +0000 Subject: [PATCH] bevy_reflect: `ReflectFromPtr` to create `&dyn Reflect` from a `*const ()` (#4475) # Objective https://github.com/bevyengine/bevy/pull/4447 adds functions that can fetch resources/components as `*const ()` ptr by providing the `ComponentId`. This alone is not enough for them to be usable safely with reflection, because there is no general way to go from the raw pointer to a `&dyn Reflect` which is the pointer + a pointer to the VTable of the `Reflect` impl. By adding a `ReflectFromPtr` type that is included in the type type registration when deriving `Reflect`, safe functions can be implemented in scripting languages that don't assume a type layout and can access the component data via reflection: ```rust #[derive(Reflect)] struct StringResource { value: String } ``` ```lua local res_id = world:resource_id_by_name("example::StringResource") local res = world:resource(res_id) print(res.value) ``` ## Solution 1. add a `ReflectFromPtr` type with a `FromType` implementation and the following methods: - ` pub unsafe fn as_reflect_ptr<'a>(&self, val: Ptr<'a>) -> &'a dyn Reflect` - ` pub unsafe fn as_reflect_ptr_mut<'a>(&self, val: PtrMut<'a>) -> &'a mud dyn Reflect` Safety requirements of the methods are that you need to check that the `ReflectFromPtr` was constructed for the correct type. 2. add that type to the `TypeRegistration` in the `GetTypeRegistration` impl generated by `#[derive(Reflect)]`. This is different to other reflected traits because it doesn't need `#[reflect(ReflectReflectFromPtr)]` which IMO should be there by default. Co-authored-by: Jakob Hellermann Co-authored-by: Carter Anderson --- crates/bevy_reflect/Cargo.toml | 1 + .../bevy_reflect_derive/src/registration.rs | 1 + crates/bevy_reflect/src/impls/std.rs | 9 +- crates/bevy_reflect/src/type_registry.rs | 134 +++++++++++++++++- 4 files changed, 139 insertions(+), 6 deletions(-) diff --git a/crates/bevy_reflect/Cargo.toml b/crates/bevy_reflect/Cargo.toml index e632d4a25db3b..4534c22aad01a 100644 --- a/crates/bevy_reflect/Cargo.toml +++ b/crates/bevy_reflect/Cargo.toml @@ -16,6 +16,7 @@ bevy = ["glam", "smallvec"] # bevy bevy_reflect_derive = { path = "bevy_reflect_derive", version = "0.8.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.8.0-dev" } +bevy_ptr = { path = "../bevy_ptr", version = "0.8.0-dev" } # other erased-serde = "0.3" diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/registration.rs b/crates/bevy_reflect/bevy_reflect_derive/src/registration.rs index ea3157242211e..7b13f7d352ef8 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/registration.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/registration.rs @@ -17,6 +17,7 @@ pub(crate) fn impl_get_type_registration( impl #impl_generics #bevy_reflect_path::GetTypeRegistration for #type_name #ty_generics #where_clause { fn get_type_registration() -> #bevy_reflect_path::TypeRegistration { let mut registration = #bevy_reflect_path::TypeRegistration::of::<#type_name #ty_generics>(); + registration.insert::<#bevy_reflect_path::ReflectFromPtr>(#bevy_reflect_path::FromType::<#type_name #ty_generics>::from_type()); #(registration.insert::<#registration_data>(#bevy_reflect_path::FromType::<#type_name #ty_generics>::from_type());)* registration } diff --git a/crates/bevy_reflect/src/impls/std.rs b/crates/bevy_reflect/src/impls/std.rs index 61352302fa67b..e172482ed07c4 100644 --- a/crates/bevy_reflect/src/impls/std.rs +++ b/crates/bevy_reflect/src/impls/std.rs @@ -1,4 +1,4 @@ -use crate as bevy_reflect; +use crate::{self as bevy_reflect, ReflectFromPtr}; use crate::{ map_apply, map_partial_eq, Array, ArrayInfo, ArrayIter, DynamicMap, FromReflect, FromType, GetTypeRegistration, List, ListInfo, Map, MapInfo, MapIter, Reflect, ReflectDeserialize, @@ -175,6 +175,7 @@ impl Deserialize<'de>> GetTypeRegistration for Vec fn get_type_registration() -> TypeRegistration { let mut registration = TypeRegistration::of::>(); registration.insert::(FromType::>::from_type()); + registration.insert::(FromType::>::from_type()); registration } } @@ -326,8 +327,9 @@ where V: FromReflect + Clone + for<'de> Deserialize<'de>, { fn get_type_registration() -> TypeRegistration { - let mut registration = TypeRegistration::of::(); - registration.insert::(FromType::::from_type()); + let mut registration = TypeRegistration::of::>(); + registration.insert::(FromType::>::from_type()); + registration.insert::(FromType::>::from_type()); registration } } @@ -575,6 +577,7 @@ impl GetTypeRegistration for Cow<'static, str> { fn get_type_registration() -> TypeRegistration { let mut registration = TypeRegistration::of::>(); registration.insert::(FromType::>::from_type()); + registration.insert::(FromType::>::from_type()); registration.insert::(FromType::>::from_type()); registration } diff --git a/crates/bevy_reflect/src/type_registry.rs b/crates/bevy_reflect/src/type_registry.rs index 97e84fef3a56f..3a44fe4da1499 100644 --- a/crates/bevy_reflect/src/type_registry.rs +++ b/crates/bevy_reflect/src/type_registry.rs @@ -1,5 +1,5 @@ -use crate::serde::Serializable; -use crate::{Reflect, TypeInfo, Typed}; +use crate::{serde::Serializable, Reflect, TypeInfo, Typed}; +use bevy_ptr::{Ptr, PtrMut}; use bevy_utils::{HashMap, HashSet}; use downcast_rs::{impl_downcast, Downcast}; use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; @@ -411,11 +411,139 @@ impl Deserialize<'a> + Reflect> FromType for ReflectDeserialize { } } +/// [`Reflect`] values are commonly used in situations where the actual types of values +/// are not known at runtime. In such situations you might have access to a `*const ()` pointer +/// that you know implements [`Reflect`], but have no way of turning it into a `&dyn Reflect`. +/// +/// This is where [`ReflectFromPtr`] comes in, when creating a [`ReflectFromPtr`] for a given type `T: Reflect`. +/// Internally, this saves a concrete function `*const T -> const dyn Reflect` which lets you create a trait object of [`Reflect`] +/// from a pointer. +/// +/// # Example +/// ```rust +/// use bevy_reflect::{TypeRegistry, Reflect, ReflectFromPtr}; +/// use bevy_ptr::Ptr; +/// use std::ptr::NonNull; +/// +/// #[derive(Reflect)] +/// struct Reflected(String); +/// +/// let mut type_registry = TypeRegistry::default(); +/// type_registry.register::(); +/// +/// let mut value = Reflected("Hello world!".to_string()); +/// let value = unsafe { Ptr::new(NonNull::from(&mut value).cast()) }; +/// +/// let reflect_data = type_registry.get(std::any::TypeId::of::()).unwrap(); +/// let reflect_from_ptr = reflect_data.data::().unwrap(); +/// // SAFE: `value` is of type `Reflected`, which the `ReflectFromPtr` was created for +/// let value = unsafe { reflect_from_ptr.as_reflect_ptr(value) }; +/// +/// assert_eq!(value.downcast_ref::().unwrap().0, "Hello world!"); +/// ``` +#[derive(Clone)] +pub struct ReflectFromPtr { + type_id: TypeId, + to_reflect: for<'a> unsafe fn(Ptr<'a>) -> &'a dyn Reflect, + to_reflect_mut: for<'a> unsafe fn(PtrMut<'a>) -> &'a mut dyn Reflect, +} + +impl ReflectFromPtr { + /// Returns the [`TypeId`] that the [`ReflectFromPtr`] was constructed for + pub fn type_id(&self) -> TypeId { + self.type_id + } + + /// # Safety + /// + /// `val` must be a pointer to value of the type that the [`ReflectFromPtr`] was constructed for. + /// This can be verified by checking that the type id returned by [`ReflectFromPtr::type_id`] is the expected one. + pub unsafe fn as_reflect_ptr<'a>(&self, val: Ptr<'a>) -> &'a dyn Reflect { + (self.to_reflect)(val) + } + + /// # Safety + /// + /// `val` must be a pointer to a value of the type that the [`ReflectFromPtr`] was constructed for + /// This can be verified by checking that the type id returned by [`ReflectFromPtr::type_id`] is the expected one. + pub unsafe fn as_reflect_ptr_mut<'a>(&self, val: PtrMut<'a>) -> &'a mut dyn Reflect { + (self.to_reflect_mut)(val) + } +} + +impl FromType for ReflectFromPtr { + fn from_type() -> Self { + ReflectFromPtr { + type_id: std::any::TypeId::of::(), + to_reflect: |ptr| { + // SAFE: only called from `as_reflect`, where the `ptr` is guaranteed to be of type `T`, + // and `as_reflect_ptr`, where the caller promises to call it with type `T` + unsafe { ptr.deref::() as &dyn Reflect } + }, + to_reflect_mut: |ptr| { + // SAFE: only called from `as_reflect_mut`, where the `ptr` is guaranteed to be of type `T`, + // and `as_reflect_ptr_mut`, where the caller promises to call it with type `T` + unsafe { ptr.deref_mut::() as &mut dyn Reflect } + }, + } + } +} + #[cfg(test)] mod test { - use crate::TypeRegistration; + use std::ptr::NonNull; + + use crate::{GetTypeRegistration, ReflectFromPtr, TypeRegistration}; + use bevy_ptr::{Ptr, PtrMut}; use bevy_utils::HashMap; + use crate as bevy_reflect; + use crate::Reflect; + + #[test] + fn test_reflect_from_ptr() { + #[derive(Reflect)] + struct Foo { + a: f32, + } + + let foo_registration = ::get_type_registration(); + let reflect_from_ptr = foo_registration.data::().unwrap(); + + // not required in this situation because we no nobody messed with the TypeRegistry, + // but in the general case somebody could have replaced the ReflectFromPtr with an + // instance for another type, so then we'd need to check that the type is the expected one + assert_eq!(reflect_from_ptr.type_id(), std::any::TypeId::of::()); + + let mut value = Foo { a: 1.0 }; + { + // SAFETY: lifetime doesn't outlive original value, access is unique + let value = unsafe { PtrMut::new(NonNull::from(&mut value).cast()) }; + // SAFETY: reflect_from_ptr was constructed for the correct type + let dyn_reflect = unsafe { reflect_from_ptr.as_reflect_ptr_mut(value) }; + match dyn_reflect.reflect_mut() { + bevy_reflect::ReflectMut::Struct(strukt) => { + strukt.field_mut("a").unwrap().apply(&2.0f32); + } + _ => panic!("invalid reflection"), + } + } + + { + // SAFETY: lifetime doesn't outlive original value + let value = unsafe { Ptr::new(NonNull::from(&mut value).cast()) }; + // SAFETY: reflect_from_ptr was constructed for the correct type + let dyn_reflect = unsafe { reflect_from_ptr.as_reflect_ptr(value) }; + match dyn_reflect.reflect_ref() { + bevy_reflect::ReflectRef::Struct(strukt) => { + let a = strukt.field("a").unwrap().downcast_ref::().unwrap(); + assert_eq!(*a, 2.0); + } + _ => panic!("invalid reflection"), + } + } + } + #[test] fn test_property_type_registration() { assert_eq!(