mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
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<T: Reflect>` 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 <hellermann@sipgate.de> Co-authored-by: Carter Anderson <mcanders1@gmail.com>
This commit is contained in:
parent
0fa499a7d0
commit
7dcfaaef67
4 changed files with 139 additions and 6 deletions
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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<T: FromReflect + for<'de> Deserialize<'de>> GetTypeRegistration for Vec<T>
|
|||
fn get_type_registration() -> TypeRegistration {
|
||||
let mut registration = TypeRegistration::of::<Vec<T>>();
|
||||
registration.insert::<ReflectDeserialize>(FromType::<Vec<T>>::from_type());
|
||||
registration.insert::<ReflectFromPtr>(FromType::<Vec<T>>::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::<Self>();
|
||||
registration.insert::<ReflectDeserialize>(FromType::<Self>::from_type());
|
||||
let mut registration = TypeRegistration::of::<HashMap<K, V>>();
|
||||
registration.insert::<ReflectDeserialize>(FromType::<HashMap<K, V>>::from_type());
|
||||
registration.insert::<ReflectFromPtr>(FromType::<HashMap<K, V>>::from_type());
|
||||
registration
|
||||
}
|
||||
}
|
||||
|
@ -575,6 +577,7 @@ impl GetTypeRegistration for Cow<'static, str> {
|
|||
fn get_type_registration() -> TypeRegistration {
|
||||
let mut registration = TypeRegistration::of::<Cow<'static, str>>();
|
||||
registration.insert::<ReflectDeserialize>(FromType::<Cow<'static, str>>::from_type());
|
||||
registration.insert::<ReflectFromPtr>(FromType::<Cow<'static, str>>::from_type());
|
||||
registration.insert::<ReflectSerialize>(FromType::<Cow<'static, str>>::from_type());
|
||||
registration
|
||||
}
|
||||
|
|
|
@ -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<T: for<'a> Deserialize<'a> + Reflect> FromType<T> 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::<Reflected>();
|
||||
///
|
||||
/// 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::<Reflected>()).unwrap();
|
||||
/// let reflect_from_ptr = reflect_data.data::<ReflectFromPtr>().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::<Reflected>().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<T: Reflect> FromType<T> for ReflectFromPtr {
|
||||
fn from_type() -> Self {
|
||||
ReflectFromPtr {
|
||||
type_id: std::any::TypeId::of::<T>(),
|
||||
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::<T>() 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::<T>() 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 = <Foo as GetTypeRegistration>::get_type_registration();
|
||||
let reflect_from_ptr = foo_registration.data::<ReflectFromPtr>().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::<Foo>());
|
||||
|
||||
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::<f32>().unwrap();
|
||||
assert_eq!(*a, 2.0);
|
||||
}
|
||||
_ => panic!("invalid reflection"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_property_type_registration() {
|
||||
assert_eq!(
|
||||
|
|
Loading…
Reference in a new issue