use super::{WorldGuard, access_map::ReflectAccessId};
use crate::{
ReferencePart, ReferencePath, ReflectAllocationId, ReflectAllocator, ThreadWorldContainer,
error::InteropError, reflection_extensions::PartialReflectExt, with_access_read,
with_access_write,
};
use bevy_asset::{ReflectAsset, UntypedHandle};
use bevy_ecs::{component::Component, ptr::Ptr, resource::Resource};
use bevy_mod_scripting_derive::DebugWithTypeInfo;
use bevy_mod_scripting_display::{
DebugWithTypeInfo, DisplayWithTypeInfo, OrFakeId, PrintReflectAsDebug, WithTypeInfo,
};
use bevy_reflect::{Access, OffsetAccess, ReflectRef, TypeRegistry};
use std::{
any::{Any, TypeId},
fmt::Debug,
};
use {
bevy_ecs::{
change_detection::MutUntyped, component::ComponentId, entity::Entity,
world::unsafe_world_cell::UnsafeWorldCell,
},
bevy_reflect::{ParsedPath, PartialReflect, Reflect, ReflectFromPtr, prelude::ReflectDefault},
};
#[derive(Clone, PartialEq, Reflect, DebugWithTypeInfo)]
#[reflect(Default, opaque)]
#[debug_with_type_info(bms_display_path = "bevy_mod_scripting_display")]
#[non_exhaustive]
pub struct ReflectReference {
pub base: ReflectBaseType,
pub reflect_path: ReferencePath,
}
impl DisplayWithTypeInfo for ReflectReference {
fn display_with_type_info(
&self,
f: &mut std::fmt::Formatter<'_>,
type_info_provider: Option<&dyn bevy_mod_scripting_display::GetTypeInfo>,
) -> std::fmt::Result {
if let Some(type_info_provider) = type_info_provider {
let any: &dyn Any = unsafe { type_info_provider.as_any_static() };
let guard = any.downcast_ref::<WorldGuard>().cloned().or_else(|| {
any.downcast_ref::<ThreadWorldContainer>()
.and_then(|t| t.try_get_context().ok().map(|c| c.world))
});
if let Some(guard) = guard
&& let Ok(r) = self.with_reflect(guard.clone(), |s| {
PrintReflectAsDebug::new_with_opt_info(s, Some(type_info_provider))
.to_string_with_type_info(f, Some(type_info_provider))
})
{
return r;
}
}
f.write_str("(cannot access value, showing reference) ")?;
self.base.display_with_type_info(f, type_info_provider)?;
if !self.reflect_path.is_empty() {
f.write_str(" at path ")?;
self.reflect_path
.display_with_type_info(f, type_info_provider)?;
}
Ok(())
}
}
impl Default for ReflectReference {
fn default() -> Self {
Self {
base: ReflectBaseType {
type_id: None::<TypeId>.or_fake_id(),
base_id: ReflectBase::Owned(ReflectAllocationId::new(0)),
},
reflect_path: Default::default(),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum TypeIdSource {
Tail,
Element,
Key,
}
#[profiling::all_functions]
impl ReflectReference {
pub fn variant_name(&self, world: WorldGuard) -> Result<Option<String>, InteropError> {
self.with_reflect(world, |s| {
s.reflect_ref()
.as_enum()
.ok()
.map(|enum_ref| enum_ref.variant_name().to_owned())
})
}
pub fn into_iter_infinite(self) -> ReflectRefIter {
ReflectRefIter::new_indexed(self)
}
pub fn len(&self, world: WorldGuard) -> Result<Option<usize>, InteropError> {
self.with_reflect(world, |r| match r.reflect_ref() {
ReflectRef::Struct(s) => Some(s.field_len()),
ReflectRef::TupleStruct(ts) => Some(ts.field_len()),
ReflectRef::Tuple(t) => Some(t.field_len()),
ReflectRef::List(l) => Some(l.len()),
ReflectRef::Array(a) => Some(a.len()),
ReflectRef::Map(m) => Some(m.len()),
ReflectRef::Set(s) => Some(s.len()),
ReflectRef::Enum(e) => Some(e.field_len()),
_ => None,
})
}
pub fn new_allocated<T: Reflect>(
value: T,
allocator: &mut ReflectAllocator,
) -> ReflectReference {
let type_id = std::any::TypeId::of::<T>();
let id = allocator.allocate(value);
ReflectReference {
base: ReflectBaseType {
type_id,
base_id: ReflectBase::Owned(id),
},
reflect_path: Default::default(),
}
}
pub fn new_allocated_raw(type_id: TypeId, id: ReflectAllocationId) -> ReflectReference {
ReflectReference {
base: ReflectBaseType {
type_id,
base_id: ReflectBase::Owned(id),
},
reflect_path: Default::default(),
}
}
pub fn new_allocated_boxed_parial_reflect(
value: Box<dyn PartialReflect>,
allocator: &mut ReflectAllocator,
) -> Result<ReflectReference, InteropError> {
Ok(ReflectReference {
base: ReflectBaseType::new_allocated_base_partial(value, allocator)?,
reflect_path: Default::default(),
})
}
pub fn new_allocated_boxed(
value: Box<dyn Reflect>,
allocator: &mut ReflectAllocator,
) -> ReflectReference {
ReflectReference {
base: ReflectBaseType::new_allocated_base(value, allocator),
reflect_path: Default::default(),
}
}
pub fn new_resource_ref<T: Resource>(world: WorldGuard) -> Result<Self, InteropError> {
Ok(Self {
base: ReflectBaseType::new_resource_base::<T>(world)?,
reflect_path: Default::default(),
})
}
pub fn new_component_ref<T: Component>(
entity: Entity,
world: WorldGuard,
) -> Result<Self, InteropError> {
Ok(Self {
base: ReflectBaseType::new_component_base::<T>(entity, world)?,
reflect_path: Default::default(),
})
}
pub fn new_component_ref_by_id(
entity: Entity,
component_id: ComponentId,
type_id: TypeId,
) -> Self {
Self {
base: ReflectBaseType {
type_id,
base_id: ReflectBase::Component(entity, component_id),
},
reflect_path: Default::default(),
}
}
pub fn new_resource_ref_by_id(component_id: ComponentId, type_id: TypeId) -> Self {
Self {
base: ReflectBaseType {
type_id,
base_id: ReflectBase::Resource(component_id),
},
reflect_path: Default::default(),
}
}
pub fn new_asset_ref(
handle: UntypedHandle,
asset_type_id: TypeId,
world: WorldGuard,
) -> Result<Self, InteropError> {
Ok(Self {
base: ReflectBaseType::new_asset_base(handle, asset_type_id, world)?,
reflect_path: Default::default(),
})
}
pub fn try_untyped_asset_handle(
&self,
world: WorldGuard,
) -> Result<UntypedHandle, InteropError> {
let handle_type_id = self.tail_type_id(world.clone())?.ok_or_else(|| {
InteropError::invariant("Cannot determine handle type ID from reflection")
.with_context("Asset handle reflection failed - handle may be invalid or corrupted")
})?;
let type_registry = world.type_registry();
let type_registry = type_registry.read();
let reflect_handle = type_registry
.get_type_data::<bevy_asset::ReflectHandle>(handle_type_id)
.ok_or_else(|| {
InteropError::missing_type_data(
handle_type_id,
stringify!(ReflectHandle).into(),
)
.with_context("Handle type is not registered for asset operations - ensure that you registered it with bevy::App::register_asset_reflect::<T>()")
})?;
let untyped_handle = self.with_reflect(world.clone(), |reflect| {
let reflect_any = reflect.try_as_reflect().ok_or_else(|| {
InteropError::unsupported_operation(
Some(handle_type_id),
None,
"Asset handle must implement Reflect trait for asset operations",
)
})?;
reflect_handle
.downcast_handle_untyped(reflect_any.as_any())
.ok_or_else(|| {
InteropError::could_not_downcast(self.clone(), handle_type_id).with_context(
"UntypedHandle downcast failed - handle may be of wrong type or corrupted",
)
})
})??;
Ok(untyped_handle)
}
unsafe fn load_asset_mut<'w>(
&self,
handle: &UntypedHandle,
world: WorldGuard<'w>,
) -> Result<&'w mut dyn Reflect, InteropError> {
let type_registry = world.type_registry();
let type_registry = type_registry.read();
let reflect_asset: &ReflectAsset = type_registry
.get_type_data(self.base.type_id)
.ok_or_else(|| InteropError::unregistered_base(self.base.clone()))?;
let world_cell = world.as_unsafe_world_cell()?;
let asset =
unsafe { reflect_asset.get_unchecked_mut(world_cell, handle) }.ok_or_else(|| {
InteropError::unsupported_operation(
Some(self.base.type_id),
None,
"Asset not loaded or handle is invalid",
)
})?;
Ok(asset)
}
pub fn extend_path(&mut self, index: impl Iterator<Item = ReferencePart>) {
self.reflect_path.extend(index);
}
pub fn push_path(&mut self, index: ReferencePart) {
self.reflect_path.push(index);
}
pub fn downcast<O: Clone + PartialReflect>(
&self,
world: WorldGuard,
) -> Result<O, InteropError> {
self.with_reflect(world, |r| {
r.try_downcast_ref::<O>()
.cloned()
.ok_or_else(|| InteropError::could_not_downcast(self.clone(), TypeId::of::<O>()))
})?
}
pub fn to_owned_value(
&self,
world: WorldGuard,
) -> Result<Box<dyn PartialReflect>, InteropError> {
if let ReflectBase::Owned(id) = &self.base.base_id
&& self.reflect_path.is_empty()
&& id.strong_count() == 0
{
let allocator = world.allocator();
let mut allocator = allocator.write();
let arc = allocator
.remove(id)
.ok_or_else(|| InteropError::garbage_collected_allocation(self.clone()))?;
let access_id = ReflectAccessId::for_allocation(id.clone());
if world.claim_write_access(access_id) {
if unsafe { &*arc.get_ptr() }.try_as_reflect().is_some() {
unsafe { world.release_access(access_id) };
return Ok(unsafe { arc.take() });
} else {
unsafe { world.release_access(access_id) };
}
}
allocator.insert(id.clone(), arc);
}
self.with_reflect(world.clone(), |r| {
<dyn PartialReflect>::from_reflect_or_clone(r, world.clone())
})?
}
#[track_caller]
pub fn with_reflect<O, F: FnOnce(&dyn PartialReflect) -> O>(
&self,
world: WorldGuard,
f: F,
) -> Result<O, InteropError> {
let access_id = ReflectAccessId::for_reference(self.base.base_id.clone());
with_access_read!(
&world.inner.accesses,
access_id,
"could not access reflect reference",
{
f(
unsafe { self.reflect_unsafe(world.clone()) }?.ok_or_else(|| {
InteropError::reflection_path_error(
"Reference was out of bounds or value is missing".into(),
Some(self.clone()),
)
})?,
)
}
)
}
#[track_caller]
pub fn with_reflect_mut<O, F: FnOnce(&mut dyn PartialReflect) -> O>(
&self,
world: WorldGuard,
f: F,
) -> Result<O, InteropError> {
let access_id = ReflectAccessId::for_reference(self.base.base_id.clone());
with_access_write!(
&world.inner.accesses,
access_id,
"Could not access reflect reference mutably",
{
f(
unsafe { self.reflect_mut_unsafe(world.clone()) }?.ok_or_else(|| {
InteropError::reflection_path_error(
"Reference was out of bounds or value is missing".into(),
Some(self.clone()),
)
})?,
)
}
)
}
pub fn tail_type_id(&self, world: WorldGuard) -> Result<Option<TypeId>, InteropError> {
if self.reflect_path.is_empty() {
return Ok(Some(self.base.type_id));
}
self.with_reflect(world, |r| {
r.get_represented_type_info().map(|t| t.type_id())
})
}
pub fn element_type_id(&self, world: WorldGuard) -> Result<Option<TypeId>, InteropError> {
self.with_reflect(world, |r| r.element_type_id())
}
pub fn key_type_id(&self, world: WorldGuard) -> Result<Option<TypeId>, InteropError> {
self.with_reflect(world, |r| r.key_type_id())
}
pub fn type_id_of(
&self,
source: TypeIdSource,
world: WorldGuard,
) -> Result<Option<TypeId>, InteropError> {
match source {
TypeIdSource::Tail => self.tail_type_id(world),
TypeIdSource::Element => self.element_type_id(world),
TypeIdSource::Key => self.key_type_id(world),
}
}
pub unsafe fn reflect_unsafe_non_empty<'w>(
&self,
world: WorldGuard<'w>,
) -> Result<&'w dyn PartialReflect, InteropError> {
let val = unsafe { self.reflect_unsafe(world) }?;
val.ok_or_else(|| {
InteropError::reflection_path_error(
"Reference out of bounds or value missing".into(),
Some(self.clone()),
)
})
}
pub unsafe fn reflect_unsafe<'w>(
&self,
world: WorldGuard<'w>,
) -> Result<Option<&'w dyn PartialReflect>, InteropError> {
if let ReflectBase::Owned(id) = &self.base.base_id {
let allocator = world.allocator();
let allocator = allocator.read();
let arc = allocator
.get(id)
.ok_or_else(|| InteropError::garbage_collected_allocation(self.clone()))?;
let type_registry = world.type_registry();
let type_registry = type_registry.read();
return self.walk_path(unsafe { &*arc.get_ptr() }, &type_registry);
}
if let ReflectBase::Asset(handle, _) = &self.base.base_id {
let asset = unsafe { self.load_asset_mut(handle, world.clone())? };
let type_registry = world.type_registry();
let type_registry = type_registry.read();
return self.walk_path(asset.as_partial_reflect(), &type_registry);
}
let type_registry = world.type_registry();
let type_registry = type_registry.read();
let from_ptr_data: &ReflectFromPtr = type_registry
.get_type_data(self.base.type_id)
.ok_or_else(|| InteropError::unregistered_base(self.base.clone()))?;
let ptr = unsafe {
self.base
.base_id
.clone()
.into_ptr(world.as_unsafe_world_cell()?)
}
.ok_or_else(|| InteropError::unregistered_base(self.base.clone()))?;
debug_assert_eq!(
from_ptr_data.type_id(),
self.base.type_id,
"Safety invariant violated"
);
let base = unsafe { from_ptr_data.as_reflect(ptr) };
self.walk_path(base.as_partial_reflect(), &type_registry)
}
pub unsafe fn reflect_mut_unsafe_non_empty<'w>(
&self,
world: WorldGuard<'w>,
) -> Result<&'w mut dyn PartialReflect, InteropError> {
let val = unsafe { self.reflect_mut_unsafe(world) }?;
val.ok_or_else(|| {
InteropError::reflection_path_error(
"Reference out of bounds or value missing".into(),
Some(self.clone()),
)
})
}
pub unsafe fn reflect_mut_unsafe<'w>(
&self,
world: WorldGuard<'w>,
) -> Result<Option<&'w mut dyn PartialReflect>, InteropError> {
if let ReflectBase::Owned(id) = &self.base.base_id {
let allocator = world.allocator();
let allocator = allocator.read();
let arc = allocator
.get(id)
.ok_or_else(|| InteropError::garbage_collected_allocation(self.clone()))?;
let type_registry = world.type_registry();
let type_registry = type_registry.read();
return self.walk_path_mut(unsafe { &mut *arc.get_ptr() }, &type_registry);
};
if let ReflectBase::Asset(handle, _) = &self.base.base_id {
let asset = unsafe { self.load_asset_mut(handle, world.clone())? };
let type_registry = world.type_registry();
let type_registry = type_registry.read();
return self.walk_path_mut(asset.as_partial_reflect_mut(), &type_registry);
};
let type_registry = world.type_registry();
let type_registry = type_registry.read();
let from_ptr_data: &ReflectFromPtr = type_registry
.get_type_data(self.base.type_id)
.ok_or_else(|| InteropError::unregistered_base(self.base.clone()))?;
let ptr = unsafe {
self.base
.base_id
.clone()
.into_ptr_mut(world.as_unsafe_world_cell()?)
}
.ok_or_else(|| InteropError::unregistered_base(self.base.clone()))?;
debug_assert_eq!(
from_ptr_data.type_id(),
self.base.type_id,
"Invariant violated"
);
let base = unsafe { from_ptr_data.as_reflect_mut(ptr.into_inner()) };
self.walk_path_mut(base.as_partial_reflect_mut(), &type_registry)
}
fn walk_path<'a>(
&self,
root: &'a dyn PartialReflect,
type_registry: &TypeRegistry,
) -> Result<Option<&'a dyn PartialReflect>, InteropError> {
self.reflect_path
.reflect_element(root, type_registry)
.map_err(|e| InteropError::reflection_path_error(e.to_string(), Some(self.clone())))
}
fn walk_path_mut<'a>(
&self,
root: &'a mut dyn PartialReflect,
type_registry: &TypeRegistry,
) -> Result<Option<&'a mut dyn PartialReflect>, InteropError> {
self.reflect_path
.reflect_element_mut(root, type_registry)
.map_err(|e| InteropError::reflection_path_error(e.to_string(), Some(self.clone())))
}
}
#[derive(Clone, PartialEq, Eq, PartialOrd, DebugWithTypeInfo)]
#[debug_with_type_info(bms_display_path = "bevy_mod_scripting_display")]
pub struct ReflectBaseType {
pub(crate) type_id: TypeId,
pub base_id: ReflectBase,
}
impl DisplayWithTypeInfo for ReflectBaseType {
fn display_with_type_info(
&self,
f: &mut std::fmt::Formatter<'_>,
type_info_provider: Option<&dyn bevy_mod_scripting_display::GetTypeInfo>,
) -> std::fmt::Result {
f.write_str("base type: ")?;
WithTypeInfo::new_with_opt_info(&self.type_id, type_info_provider)
.display_with_type_info(f, type_info_provider)?;
f.write_str(", of kind: ")?;
self.base_id.display_with_type_info(f, type_info_provider)?;
Ok(())
}
}
impl ReflectBaseType {
#[inline]
pub fn type_id(&self) -> TypeId {
self.type_id
}
pub fn new_component_base<T: Component>(
entity: Entity,
world: WorldGuard,
) -> Result<Self, InteropError> {
let reflect_id = ReflectAccessId::for_component::<T>(&world.as_unsafe_world_cell()?)?;
Ok(Self {
type_id: TypeId::of::<T>(),
base_id: ReflectBase::Component(entity, reflect_id.into()),
})
}
pub fn new_resource_base<T: Resource>(world: WorldGuard) -> Result<Self, InteropError> {
let reflect_id = ReflectAccessId::for_resource::<T>(&world.as_unsafe_world_cell()?)?;
Ok(Self {
type_id: TypeId::of::<T>(),
base_id: ReflectBase::Resource(reflect_id.into()),
})
}
pub fn new_allocated_base(value: Box<dyn Reflect>, allocator: &mut ReflectAllocator) -> Self {
let type_id = (*value).type_id();
let id = allocator.allocate_boxed(value.into_partial_reflect());
Self {
type_id,
base_id: ReflectBase::Owned(id),
}
}
pub fn new_allocated_base_partial(
value: Box<dyn PartialReflect>,
allocator: &mut ReflectAllocator,
) -> Result<Self, InteropError> {
match value.get_represented_type_info() {
Some(i) => {
let id = allocator.allocate_boxed(value);
Ok(Self {
type_id: i.type_id(),
base_id: ReflectBase::Owned(id),
})
}
None => Err(InteropError::unsupported_operation(
None,
Some(value),
"Tried to create a reference base to a partial reflect value with no represented type info",
)),
}
}
pub fn new_asset_base(
handle: UntypedHandle,
asset_type_id: TypeId,
world: WorldGuard,
) -> Result<Self, InteropError> {
let type_registry = world.type_registry();
let type_registry = type_registry.read();
let reflect_asset: &ReflectAsset =
type_registry.get_type_data(asset_type_id).ok_or_else(|| {
InteropError::unsupported_operation(
Some(asset_type_id),
None,
"Asset type is not registered with ReflectAsset type data",
)
})?;
let assets_resource_type_id = reflect_asset.assets_resource_type_id();
let world_cell = world.as_unsafe_world_cell()?;
let components = world_cell.components();
let assets_resource_id = components
.get_resource_id(assets_resource_type_id)
.ok_or_else(|| {
InteropError::unsupported_operation(
Some(assets_resource_type_id),
None,
"Assets<T> resource is not registered in the world",
)
})?;
Ok(Self {
type_id: asset_type_id,
base_id: ReflectBase::Asset(handle, assets_resource_id),
})
}
}
#[derive(Clone, PartialEq, Eq, PartialOrd, DebugWithTypeInfo)]
#[debug_with_type_info(bms_display_path = "bevy_mod_scripting_display")]
pub enum ReflectBase {
Component(Entity, ComponentId),
Resource(ComponentId),
Owned(ReflectAllocationId),
Asset(UntypedHandle, ComponentId),
}
impl DisplayWithTypeInfo for ReflectBase {
fn display_with_type_info(
&self,
f: &mut std::fmt::Formatter<'_>,
type_info_provider: Option<&dyn bevy_mod_scripting_display::GetTypeInfo>,
) -> std::fmt::Result {
match self {
ReflectBase::Component(entity, component_id) => {
f.write_str("component: ")?;
WithTypeInfo::new_with_opt_info(component_id, type_info_provider)
.display_with_type_info(f, type_info_provider)?;
f.write_str(", on entity: ")?;
entity.fmt(f)
}
ReflectBase::Resource(component_id) => {
f.write_str("resource: ")?;
WithTypeInfo::new_with_opt_info(component_id, type_info_provider)
.display_with_type_info(f, type_info_provider)
}
ReflectBase::Owned(id) => {
if let Some(type_info_provider) = type_info_provider {
let any: &dyn Any = unsafe { type_info_provider.as_any_static() };
let guard = any.downcast_ref::<WorldGuard>().cloned().or_else(|| {
any.downcast_ref::<ThreadWorldContainer>()
.and_then(|t| t.try_get_context().ok().map(|c| c.world))
});
if let Some(guard) = guard {
let allocator = guard.allocator();
let allocator = allocator.read();
if let Some(allocation) = allocator.get(id) {
let ptr = allocation.get_ptr();
if let Ok(v) = guard.with_read_access(id.clone(), |_| {
PrintReflectAsDebug::new_with_opt_info(
unsafe { &*ptr },
Some(type_info_provider),
)
.to_string_with_type_info(f, Some(type_info_provider))
}) {
return v;
}
}
}
}
f.write_str("allocated value with id: ")?;
WithTypeInfo::new_with_opt_info(id, type_info_provider)
.display_with_type_info(f, type_info_provider)
}
ReflectBase::Asset(handle, assets_resource_id) => {
f.write_str("asset with handle: ")?;
write!(f, "{handle:?}")?;
f.write_str(", in Assets resource: ")?;
WithTypeInfo::new_with_opt_info(assets_resource_id, type_info_provider)
.display_with_type_info(f, type_info_provider)
}
}
}
}
#[profiling::all_functions]
impl ReflectBase {
pub unsafe fn into_ptr(self, world: UnsafeWorldCell<'_>) -> Option<Ptr<'_>> {
match self {
ReflectBase::Component(entity, component_id) => {
unsafe { world.get_entity(entity).ok()?.get_by_id(component_id) }
}
ReflectBase::Resource(component_id) => {
unsafe { world.get_resource_by_id(component_id) }
}
_ => None,
}
}
pub unsafe fn into_ptr_mut(self, world: UnsafeWorldCell<'_>) -> Option<MutUntyped<'_>> {
match self {
ReflectBase::Component(entity, component_id) => {
unsafe { world.get_entity(entity).ok()?.get_mut_by_id(component_id) }.ok()
}
ReflectBase::Resource(component_id) => {
unsafe { world.get_resource_mut_by_id(component_id) }
}
_ => None,
}
}
}
pub trait ReflectionPathExt {
fn convert_to_0_indexed(&mut self);
fn is_empty(&self) -> bool;
fn iter(&self) -> impl Iterator<Item = &OffsetAccess>;
}
#[profiling::all_functions]
impl ReflectionPathExt for ParsedPath {
fn convert_to_0_indexed(&mut self) {
self.0.iter_mut().for_each(|a| match a.access {
Access::FieldIndex(ref mut i) => *i -= 1,
Access::TupleIndex(ref mut i) => *i -= 1,
Access::ListIndex(ref mut i) => *i -= 1,
_ => {}
});
}
fn is_empty(&self) -> bool {
self.0.is_empty()
}
fn iter(&self) -> impl Iterator<Item = &OffsetAccess> {
self.0.iter()
}
}
#[derive(Clone, DebugWithTypeInfo)]
#[debug_with_type_info(bms_display_path = "bevy_mod_scripting_display")]
pub struct ReflectRefIter {
pub(crate) base: ReflectReference,
pub(crate) index: IterationKey,
}
#[derive(Clone, PartialEq, Eq, DebugWithTypeInfo)]
#[debug_with_type_info(bms_display_path = "bevy_mod_scripting_display")]
pub enum IterationKey {
Index(usize),
}
#[profiling::all_functions]
impl ReflectRefIter {
pub fn new_indexed(base: ReflectReference) -> Self {
Self {
base,
index: IterationKey::Index(0),
}
}
pub fn index(&self) -> IterationKey {
self.index.clone()
}
pub fn next_ref(&mut self) -> (ReflectReference, IterationKey) {
let index = self.index();
let next = match &mut self.index {
IterationKey::Index(i) => {
let mut next = self.base.clone();
let parsed_path = ReferencePart::IntegerAccess(*i as i64, false);
next.push_path(parsed_path);
*i += 1;
next
}
};
(next, index)
}
}
#[profiling::all_functions]
impl Iterator for ReflectRefIter {
type Item = Result<ReflectReference, InteropError>;
fn next(&mut self) -> Option<Self::Item> {
let result: Result<_, _> = {
match &mut self.index {
IterationKey::Index(i) => {
let mut next = self.base.clone();
let parsed_path = ReferencePart::IntegerAccess(*i as i64, false);
next.push_path(parsed_path);
*i += 1;
Ok(next)
}
}
};
Some(result)
}
}
#[cfg(test)]
mod test {
use bevy_ecs::{
component::Component, reflect::AppTypeRegistry, resource::Resource, world::World,
};
use crate::{AppReflectAllocator, function::script_function::AppScriptFunctionRegistry};
use super::*;
#[derive(Reflect, Component, Debug, Clone, PartialEq)]
struct TestComponent(Vec<String>);
#[derive(Reflect, Resource, Debug, Clone, PartialEq)]
struct TestResource(Vec<String>);
fn setup_world() -> World {
let mut world = World::default();
let type_registry = AppTypeRegistry::default();
{
let mut guard_type_registry = type_registry.write();
guard_type_registry.register::<TestComponent>();
guard_type_registry.register::<TestResource>();
}
world.insert_resource(type_registry);
let allocator = AppReflectAllocator::default();
world.insert_resource(allocator);
let script_function_registry = AppScriptFunctionRegistry::default();
world.insert_resource(script_function_registry);
world
}
#[test]
fn test_component_ref() {
let mut world = setup_world();
let entity = world
.spawn(TestComponent(vec!["hello".to_owned(), "world".to_owned()]))
.id();
let world_guard = WorldGuard::new_exclusive(&mut world);
let mut component_ref =
ReflectReference::new_component_ref::<TestComponent>(entity, world_guard.clone())
.expect("could not create component reference");
assert_eq!(
component_ref
.tail_type_id(world_guard.clone())
.unwrap()
.unwrap(),
TypeId::of::<TestComponent>()
);
component_ref
.with_reflect(world_guard.clone(), |s| {
let s = s.try_downcast_ref::<TestComponent>().unwrap();
assert_eq!(
s,
&TestComponent(vec!["hello".to_owned(), "world".to_owned()])
);
})
.unwrap();
component_ref.push_path(ReferencePart::IntegerAccess(0, true));
assert_eq!(
component_ref
.tail_type_id(world_guard.clone())
.unwrap()
.unwrap(),
TypeId::of::<Vec<String>>()
);
assert_eq!(
component_ref
.element_type_id(world_guard.clone())
.unwrap()
.unwrap(),
TypeId::of::<String>()
);
assert_eq!(
component_ref
.key_type_id(world_guard.clone())
.unwrap()
.unwrap(),
TypeId::of::<usize>()
);
component_ref
.with_reflect(world_guard.clone(), |s| {
let s = s.try_downcast_ref::<Vec<String>>().unwrap();
assert_eq!(s, &vec!["hello".to_owned(), "world".to_owned()]);
})
.unwrap();
component_ref.push_path(ReferencePart::IntegerAccess(0, true));
component_ref
.with_reflect(world_guard.clone(), |s| {
let s = s.try_downcast_ref::<String>().unwrap();
assert_eq!(s, "hello");
})
.unwrap();
assert_eq!(
component_ref
.tail_type_id(world_guard.clone())
.unwrap()
.unwrap(),
TypeId::of::<String>()
);
}
#[test]
fn test_resource_ref() {
let mut world = setup_world();
world.insert_resource(TestResource(vec!["hello".to_owned(), "world".to_owned()]));
let world_guard = WorldGuard::new_exclusive(&mut world);
let mut resource_ref =
ReflectReference::new_resource_ref::<TestResource>(world_guard.clone())
.expect("could not create resource reference");
assert_eq!(
resource_ref
.tail_type_id(world_guard.clone())
.unwrap()
.unwrap(),
TypeId::of::<TestResource>()
);
resource_ref
.with_reflect(world_guard.clone(), |s| {
let s = s.try_downcast_ref::<TestResource>().unwrap();
assert_eq!(
s,
&TestResource(vec!["hello".to_owned(), "world".to_owned()])
);
})
.unwrap();
resource_ref.push_path(ReferencePart::IntegerAccess(0, true));
assert_eq!(
resource_ref
.tail_type_id(world_guard.clone())
.unwrap()
.unwrap(),
TypeId::of::<Vec<String>>()
);
assert_eq!(
resource_ref
.element_type_id(world_guard.clone())
.unwrap()
.unwrap(),
TypeId::of::<String>()
);
assert_eq!(
resource_ref
.key_type_id(world_guard.clone())
.unwrap()
.unwrap(),
TypeId::of::<usize>()
);
resource_ref
.with_reflect(world_guard.clone(), |s| {
let s = s.try_downcast_ref::<Vec<String>>().unwrap();
assert_eq!(s, &vec!["hello".to_owned(), "world".to_owned()]);
})
.unwrap();
resource_ref.push_path(ReferencePart::IntegerAccess(0, true));
resource_ref
.with_reflect(world_guard.clone(), |s| {
let s = s.try_downcast_ref::<String>().unwrap();
assert_eq!(s, "hello");
})
.unwrap();
assert_eq!(
resource_ref
.tail_type_id(world_guard.clone())
.unwrap()
.unwrap(),
TypeId::of::<String>()
);
}
#[test]
fn test_allocation_ref() {
let mut world = setup_world();
let value: TestComponent = TestComponent(vec!["hello".to_owned(), "world".to_owned()]);
let world_guard = WorldGuard::new_exclusive(&mut world);
let allocator = world_guard.allocator();
let mut allocator_write = allocator.write();
let mut allocation_ref = ReflectReference::new_allocated(value, &mut allocator_write);
drop(allocator_write);
assert_eq!(
allocation_ref
.tail_type_id(world_guard.clone())
.unwrap()
.unwrap(),
TypeId::of::<TestComponent>()
);
allocation_ref
.with_reflect(world_guard.clone(), |s| {
let s = s.try_downcast_ref::<TestComponent>().unwrap();
assert_eq!(
s,
&TestComponent(vec!["hello".to_owned(), "world".to_owned()])
);
})
.unwrap();
allocation_ref.push_path(ReferencePart::IntegerAccess(0, true));
assert_eq!(
allocation_ref
.tail_type_id(world_guard.clone())
.unwrap()
.unwrap(),
TypeId::of::<Vec<String>>()
);
assert_eq!(
allocation_ref
.element_type_id(world_guard.clone())
.unwrap()
.unwrap(),
TypeId::of::<String>()
);
assert_eq!(
allocation_ref
.key_type_id(world_guard.clone())
.unwrap()
.unwrap(),
TypeId::of::<usize>()
);
allocation_ref
.with_reflect(world_guard.clone(), |s| {
let s = s.try_downcast_ref::<Vec<String>>().unwrap();
assert_eq!(s, &vec!["hello".to_owned(), "world".to_owned()]);
})
.unwrap();
allocation_ref.push_path(ReferencePart::IntegerAccess(0, true));
allocation_ref
.with_reflect(world_guard.clone(), |s| {
let s = s.try_downcast_ref::<String>().unwrap();
assert_eq!(s, "hello");
})
.unwrap();
assert_eq!(
allocation_ref
.tail_type_id(world_guard.clone())
.unwrap()
.unwrap(),
TypeId::of::<String>()
);
}
}