From 60584139de7d1d6dcbf4d1ff4d658b8ca59f8cf6 Mon Sep 17 00:00:00 2001 From: Jakob Hellermann Date: Mon, 30 May 2022 15:32:47 +0000 Subject: [PATCH] untyped APIs for components and resources (#4447) # Objective Even if bevy itself does not provide any builtin scripting or modding APIs, it should have the foundations for building them yourself. For that it should be enough to have APIs that are not tied to the actual rust types with generics, but rather accept `ComponentId`s and `bevy_ptr` ptrs. ## Solution Add the following APIs to bevy ```rust fn EntityRef::get_by_id(ComponentId) -> Option>; fn EntityMut::get_by_id(ComponentId) -> Option>; fn EntityMut::get_mut_by_id(ComponentId) -> Option>; fn World::get_resource_by_id(ComponentId) -> Option>; fn World::get_resource_mut_by_id(ComponentId) -> Option>; // Safety: `value` must point to a valid value of the component unsafe fn World::insert_resource_by_id(ComponentId, value: OwningPtr); fn ComponentDescriptor::new_with_layout(..) -> Self; fn World::init_component_with_descriptor(ComponentDescriptor) -> ComponentId; ``` ~~This PR would definitely benefit from #3001 (lifetime'd pointers) to make sure that the lifetimes of the pointers are valid and the my-move pointer in `insert_resource_by_id` could be an `OwningPtr`, but that can be adapter later if/when #3001 is merged.~~ ### Not in this PR - inserting components on entities (this is very tied to types with bundles and the `BundleInserter`) - an untyped version of a query (needs good API design, has a large implementation complexity, can be done in a third-party crate) Co-authored-by: Jakob Hellermann --- crates/bevy_ecs/src/change_detection.rs | 56 ++++- crates/bevy_ecs/src/component.rs | 68 +++++- crates/bevy_ecs/src/storage/blob_vec.rs | 5 + crates/bevy_ecs/src/world/entity_ref.rs | 153 ++++++++++++- crates/bevy_ecs/src/world/mod.rs | 275 +++++++++++++++++++++++- 5 files changed, 535 insertions(+), 22 deletions(-) diff --git a/crates/bevy_ecs/src/change_detection.rs b/crates/bevy_ecs/src/change_detection.rs index e3614f47afce0..2b5ae2bd2f5cb 100644 --- a/crates/bevy_ecs/src/change_detection.rs +++ b/crates/bevy_ecs/src/change_detection.rs @@ -1,6 +1,6 @@ //! Types that detect when their internal data mutate. -use crate::{component::ComponentTicks, system::Resource}; +use crate::{component::ComponentTicks, ptr::PtrMut, system::Resource}; #[cfg(feature = "bevy_reflect")] use bevy_reflect::Reflect; use std::ops::{Deref, DerefMut}; @@ -228,6 +228,60 @@ change_detection_impl!(ReflectMut<'a>, dyn Reflect,); #[cfg(feature = "bevy_reflect")] impl_into_inner!(ReflectMut<'a>, dyn Reflect,); +/// Unique mutable borrow of resources or an entity's component. +/// +/// Similar to [`Mut`], but not generic over the component type, instead +/// exposing the raw pointer as a `*mut ()`. +/// +/// Usually you don't need to use this and can instead use the APIs returning a +/// [`Mut`], but in situations where the types are not known at compile time +/// or are defined outside of rust this can be used. +pub struct MutUntyped<'a> { + pub(crate) value: PtrMut<'a>, + pub(crate) ticks: Ticks<'a>, +} + +impl<'a> MutUntyped<'a> { + /// Returns the pointer to the value, without marking it as changed. + /// + /// In order to mark the value as changed, you need to call [`set_changed`](DetectChanges::set_changed) manually. + pub fn into_inner(self) -> PtrMut<'a> { + self.value + } +} + +impl DetectChanges for MutUntyped<'_> { + fn is_added(&self) -> bool { + self.ticks + .component_ticks + .is_added(self.ticks.last_change_tick, self.ticks.change_tick) + } + + fn is_changed(&self) -> bool { + self.ticks + .component_ticks + .is_changed(self.ticks.last_change_tick, self.ticks.change_tick) + } + + fn set_changed(&mut self) { + self.ticks + .component_ticks + .set_changed(self.ticks.change_tick); + } + + fn last_changed(&self) -> u32 { + self.ticks.last_change_tick + } +} + +impl std::fmt::Debug for MutUntyped<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("MutUntyped") + .field(&self.value.as_ptr()) + .finish() + } +} + #[cfg(test)] mod tests { use crate::{ diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index b2c3b00732a4c..15a3bd85d87d5 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -198,6 +198,7 @@ impl ComponentDescriptor { x.drop_as::() } + /// Create a new `ComponentDescriptor` for the type `T`. pub fn new() -> Self { Self { name: std::any::type_name::().to_string(), @@ -209,6 +210,27 @@ impl ComponentDescriptor { } } + /// Create a new `ComponentDescriptor`. + /// + /// # Safety + /// - the `drop` fn must be usable on a pointer with a value of the layout `layout` + /// - the component type must be safe to access from any thread (Send + Sync in rust terms) + pub unsafe fn new_with_layout( + name: String, + storage_type: StorageType, + layout: Layout, + drop: Option unsafe fn(OwningPtr<'a>)>, + ) -> Self { + Self { + name, + storage_type, + is_send_and_sync: true, + type_id: None, + layout, + drop, + } + } + /// Create a new `ComponentDescriptor` for a resource. /// /// The [`StorageType`] for resources is always [`TableStorage`]. @@ -263,20 +285,42 @@ impl Components { #[inline] pub fn init_component(&mut self, storages: &mut Storages) -> ComponentId { let type_id = TypeId::of::(); - let components = &mut self.components; - let index = self.indices.entry(type_id).or_insert_with(|| { - let index = components.len(); - let descriptor = ComponentDescriptor::new::(); - let info = ComponentInfo::new(ComponentId(index), descriptor); - if T::Storage::STORAGE_TYPE == StorageType::SparseSet { - storages.sparse_sets.get_or_insert(&info); - } - components.push(info); - index + + let Components { + indices, + components, + .. + } = self; + let index = indices.entry(type_id).or_insert_with(|| { + Components::init_component_inner(components, storages, ComponentDescriptor::new::()) }); ComponentId(*index) } + pub fn init_component_with_descriptor( + &mut self, + storages: &mut Storages, + descriptor: ComponentDescriptor, + ) -> ComponentId { + let index = Components::init_component_inner(&mut self.components, storages, descriptor); + ComponentId(index) + } + + #[inline] + fn init_component_inner( + components: &mut Vec, + storages: &mut Storages, + descriptor: ComponentDescriptor, + ) -> usize { + let index = components.len(); + let info = ComponentInfo::new(ComponentId(index), descriptor); + if info.descriptor.storage_type == StorageType::SparseSet { + storages.sparse_sets.get_or_insert(&info); + } + components.push(info); + index + } + #[inline] pub fn len(&self) -> usize { self.components.len() @@ -352,6 +396,10 @@ impl Components { ComponentId(*index) } + + pub fn iter(&self) -> impl Iterator + '_ { + self.components.iter() + } } /// Records when a component was added and when it was last mutably dereferenced (or added). diff --git a/crates/bevy_ecs/src/storage/blob_vec.rs b/crates/bevy_ecs/src/storage/blob_vec.rs index a56e71023eec4..f7b989de0eac4 100644 --- a/crates/bevy_ecs/src/storage/blob_vec.rs +++ b/crates/bevy_ecs/src/storage/blob_vec.rs @@ -86,6 +86,11 @@ impl BlobVec { self.capacity } + #[inline] + pub fn layout(&self) -> Layout { + self.item_layout + } + pub fn reserve_exact(&mut self, additional: usize) { let available_space = self.capacity - self.len; if available_space < additional { diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index c9b6006381a7d..bf2701714190d 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -1,7 +1,7 @@ use crate::{ archetype::{Archetype, ArchetypeId, Archetypes}, bundle::{Bundle, BundleInfo}, - change_detection::Ticks, + change_detection::{MutUntyped, Ticks}, component::{Component, ComponentId, ComponentTicks, Components, StorageType}, entity::{Entities, Entity, EntityLocation}, storage::{SparseSet, Storages}, @@ -103,7 +103,7 @@ impl<'w> EntityRef<'w> { .map(|(value, ticks)| Mut { value: value.assert_unique().deref_mut::(), ticks: Ticks { - component_ticks: &mut *ticks.get(), + component_ticks: ticks.deref_mut(), last_change_tick, change_tick, }, @@ -111,6 +111,23 @@ impl<'w> EntityRef<'w> { } } +impl<'w> EntityRef<'w> { + /// Gets the component of the given [`ComponentId`] from the entity. + /// + /// **You should prefer to use the typed API where possible and only + /// use this in cases where the actual component types are not known at + /// compile time.** + /// + /// Unlike [`EntityRef::get`], this returns a raw pointer to the component, + /// which is only valid while the `'w` borrow of the lifetime is active. + #[inline] + pub fn get_by_id(&self, component_id: ComponentId) -> Option> { + self.world.components().get_info(component_id)?; + // SAFE: entity_location is valid, component_id is valid as checked by the line above + unsafe { get_component(self.world, component_id, self.entity, self.location) } + } +} + /// A mutable reference to a particular [`Entity`] and all of its components pub struct EntityMut<'w> { world: &'w mut World, @@ -207,7 +224,7 @@ impl<'w> EntityMut<'w> { .map(|(value, ticks)| Mut { value: value.assert_unique().deref_mut::(), ticks: Ticks { - component_ticks: &mut *ticks.get(), + component_ticks: ticks.deref_mut(), last_change_tick: self.world.last_change_tick(), change_tick: self.world.read_change_tick(), }, @@ -488,14 +505,47 @@ impl<'w> EntityMut<'w> { } } +impl<'w> EntityMut<'w> { + /// Gets the component of the given [`ComponentId`] from the entity. + /// + /// **You should prefer to use the typed API [`EntityMut::get`] where possible and only + /// use this in cases where the actual component types are not known at + /// compile time.** + /// + /// Unlike [`EntityMut::get`], this returns a raw pointer to the component, + /// which is only valid while the [`EntityMut`] is alive. + #[inline] + pub fn get_by_id(&self, component_id: ComponentId) -> Option> { + self.world.components().get_info(component_id)?; + // SAFE: entity_location is valid, component_id is valid as checked by the line above + unsafe { get_component(self.world, component_id, self.entity, self.location) } + } + + /// Gets a [`MutUntyped`] of the component of the given [`ComponentId`] from the entity. + /// + /// **You should prefer to use the typed API [`EntityMut::get_mut`] where possible and only + /// use this in cases where the actual component types are not known at + /// compile time.** + /// + /// Unlike [`EntityMut::get_mut`], this returns a raw pointer to the component, + /// which is only valid while the [`EntityMut`] is alive. + #[inline] + pub fn get_mut_by_id(&mut self, component_id: ComponentId) -> Option> { + self.world.components().get_info(component_id)?; + // SAFE: entity_location is valid, component_id is valid as checked by the line above + unsafe { get_mut_by_id(self.world, self.entity, self.location, component_id) } + } +} + // TODO: move to Storages? /// Get a raw pointer to a particular [`Component`] on a particular [`Entity`] in the provided [`World`]. /// /// # Safety -/// `entity_location` must be within bounds of the given archetype and `entity` must exist inside +/// - `entity_location` must be within bounds of the given archetype and `entity` must exist inside /// the archetype +/// - `component_id` must be valid #[inline] -unsafe fn get_component( +pub(crate) unsafe fn get_component( world: &World, component_id: ComponentId, entity: Entity, @@ -808,7 +858,7 @@ pub(crate) unsafe fn get_mut( |(value, ticks)| Mut { value: value.assert_unique().deref_mut::(), ticks: Ticks { - component_ticks: &mut *ticks.get(), + component_ticks: ticks.deref_mut(), last_change_tick, change_tick, }, @@ -816,8 +866,33 @@ pub(crate) unsafe fn get_mut( ) } +// SAFETY: EntityLocation must be valid, component_id must be valid +#[inline] +pub(crate) unsafe fn get_mut_by_id( + world: &mut World, + entity: Entity, + location: EntityLocation, + component_id: ComponentId, +) -> Option { + // SAFE: world access is unique, entity location and component_id required to be valid + get_component_and_ticks(world, component_id, entity, location).map(|(value, ticks)| { + MutUntyped { + value: value.assert_unique(), + ticks: Ticks { + component_ticks: ticks.deref_mut(), + last_change_tick: world.last_change_tick(), + change_tick: world.read_change_tick(), + }, + } + }) +} + #[cfg(test)] mod tests { + use crate as bevy_ecs; + use crate::component::ComponentId; + use crate::prelude::*; // for the `#[derive(Component)]` + #[test] fn sorted_remove() { let mut a = vec![1, 2, 3, 4, 5, 6, 7]; @@ -838,4 +913,70 @@ mod tests { assert_eq!(a, vec![1]); } + + #[derive(Component)] + struct TestComponent(u32); + + #[test] + fn entity_ref_get_by_id() { + let mut world = World::new(); + let entity = world.spawn().insert(TestComponent(42)).id(); + let component_id = world + .components() + .get_id(std::any::TypeId::of::()) + .unwrap(); + + let entity = world.entity(entity); + let test_component = entity.get_by_id(component_id).unwrap(); + // SAFE: points to a valid `TestComponent` + let test_component = unsafe { test_component.deref::() }; + + assert_eq!(test_component.0, 42); + } + + #[test] + fn entity_mut_get_by_id() { + let mut world = World::new(); + let entity = world.spawn().insert(TestComponent(42)).id(); + let component_id = world + .components() + .get_id(std::any::TypeId::of::()) + .unwrap(); + + let mut entity_mut = world.entity_mut(entity); + let mut test_component = entity_mut.get_mut_by_id(component_id).unwrap(); + { + test_component.set_changed(); + // SAFE: `test_component` has unique access of the `EntityMut` and is not used afterwards + let test_component = + unsafe { test_component.into_inner().deref_mut::() }; + test_component.0 = 43; + } + + let entity = world.entity(entity); + let test_component = entity.get_by_id(component_id).unwrap(); + let test_component = unsafe { test_component.deref::() }; + + assert_eq!(test_component.0, 43); + } + + #[test] + fn entity_ref_get_by_id_invalid_component_id() { + let invalid_component_id = ComponentId::new(usize::MAX); + + let mut world = World::new(); + let entity = world.spawn().id(); + let entity = world.entity(entity); + assert!(entity.get_by_id(invalid_component_id).is_none()); + } + + #[test] + fn entity_mut_get_by_id_invalid_component_id() { + let invalid_component_id = ComponentId::new(usize::MAX); + + let mut world = World::new(); + let mut entity = world.spawn(); + assert!(entity.get_by_id(invalid_component_id).is_none()); + assert!(entity.get_mut_by_id(invalid_component_id).is_none()); + } } diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index bdcd809bd9e23..71ee4c7a3716b 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -10,14 +10,16 @@ pub use world_cell::*; use crate::{ archetype::{ArchetypeComponentId, ArchetypeComponentInfo, ArchetypeId, Archetypes}, bundle::{Bundle, BundleInserter, BundleSpawner, Bundles}, - change_detection::Ticks, - component::{Component, ComponentId, ComponentTicks, Components, StorageType}, + change_detection::{MutUntyped, Ticks}, + component::{ + Component, ComponentDescriptor, ComponentId, ComponentTicks, Components, StorageType, + }, entity::{AllocAtWithoutReplacement, Entities, Entity}, query::{QueryState, WorldQuery}, storage::{Column, SparseSet, Storages}, system::Resource, }; -use bevy_ptr::{OwningPtr, UnsafeCellDeref}; +use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref}; use bevy_utils::tracing::debug; use std::{ any::TypeId, @@ -179,6 +181,14 @@ impl World { self.components.init_component::(&mut self.storages) } + pub fn init_component_with_descriptor( + &mut self, + descriptor: ComponentDescriptor, + ) -> ComponentId { + self.components + .init_component_with_descriptor(&mut self.storages, descriptor) + } + /// Retrieves an [`EntityRef`] that exposes read-only operations for the given `entity`. /// This will panic if the `entity` does not exist. Use [`World::get_entity`] if you want /// to check for entity existence instead of implicitly panic-ing. @@ -1157,8 +1167,43 @@ impl World { } } + /// Inserts a new resource with the given `value`. Will replace the value if it already existed. + /// + /// **You should prefer to use the typed API [`World::insert_resource`] where possible and only + /// use this in cases where the actual types are not known at compile time.** + /// /// # Safety - /// `component_id` must be valid and correspond to a resource component of type `R` + /// The value referenced by `value` must be valid for the given [`ComponentId`] of this world + pub unsafe fn insert_resource_by_id( + &mut self, + component_id: ComponentId, + value: OwningPtr<'_>, + ) { + let change_tick = self.change_tick(); + + self.components().get_info(component_id).unwrap_or_else(|| { + panic!( + "insert_resource_by_id called with component id which doesn't exist in this world" + ) + }); + // SAFE: component_id is valid, checked by the lines above + let column = self.initialize_resource_internal(component_id); + if column.is_empty() { + // SAFE: column is of type R and has been allocated above + column.push(value, ComponentTicks::new(change_tick)); + } else { + let ptr = column.get_data_unchecked_mut(0); + std::ptr::copy_nonoverlapping::( + value.as_ptr(), + ptr.as_ptr(), + column.data.layout().size(), + ); + column.get_ticks_unchecked_mut(0).set_changed(change_tick); + } + } + + /// # Safety + /// `component_id` must be valid for this world #[inline] unsafe fn initialize_resource_internal(&mut self, component_id: ComponentId) -> &mut Column { // SAFE: resource archetype always exists @@ -1225,6 +1270,14 @@ impl World { ); } + pub(crate) fn validate_non_send_access_untyped(&self, name: &str) { + assert!( + self.main_thread_validator.is_main_thread(), + "attempted to access NonSend resource {} off of the main thread", + name + ); + } + /// Empties queued entities and adds them to the empty [Archetype](crate::archetype::Archetype). /// This should be called before doing operations that might operate on queued entities, /// such as inserting a [Component]. @@ -1282,6 +1335,120 @@ impl World { } } +impl World { + /// Gets a resource to the resource with the id [`ComponentId`] if it exists. + /// The returned pointer must not be used to modify the resource, and must not be + /// dereferenced after the immutable borrow of the [`World`] ends. + /// + /// **You should prefer to use the typed API [`World::get_resource`] where possible and only + /// use this in cases where the actual types are not known at compile time.** + #[inline] + pub fn get_resource_by_id(&self, component_id: ComponentId) -> Option> { + let info = self.components.get_info(component_id)?; + if !info.is_send_and_sync() { + self.validate_non_send_access_untyped(info.name()); + } + + let column = self.get_populated_resource_column(component_id)?; + Some(column.get_data_ptr()) + } + + /// Gets a resource to the resource with the id [`ComponentId`] if it exists. + /// The returned pointer may be used to modify the resource, as long as the mutable borrow + /// of the [`World`] is still valid. + /// + /// **You should prefer to use the typed API [`World::get_resource_mut`] where possible and only + /// use this in cases where the actual types are not known at compile time.** + #[inline] + pub fn get_resource_mut_by_id(&mut self, component_id: ComponentId) -> Option> { + let info = self.components.get_info(component_id)?; + if !info.is_send_and_sync() { + self.validate_non_send_access_untyped(info.name()); + } + + let column = self.get_populated_resource_column(component_id)?; + + // SAFE: get_data_ptr requires that the mutability rules are not violated, and the caller promises + // to only modify the resource while the mutable borrow of the world is valid + let ticks = Ticks { + // - index is in-bounds because the column is initialized and non-empty + // - no other reference to the ticks of the same row can exist at the same time + component_ticks: unsafe { &mut *column.get_ticks_unchecked(0).get() }, + last_change_tick: self.last_change_tick(), + change_tick: self.read_change_tick(), + }; + + Some(MutUntyped { + value: unsafe { column.get_data_ptr().assert_unique() }, + ticks, + }) + } + + /// Removes the resource of a given type, if it exists. Otherwise returns [None]. + /// + /// **You should prefer to use the typed API [`World::remove_resource`] where possible and only + /// use this in cases where the actual types are not known at compile time.** + pub fn remove_resource_by_id(&mut self, component_id: ComponentId) -> Option<()> { + let info = self.components.get_info(component_id)?; + if !info.is_send_and_sync() { + self.validate_non_send_access_untyped(info.name()); + } + + let resource_archetype = self.archetypes.resource_mut(); + let unique_components = resource_archetype.unique_components_mut(); + let column = unique_components.get_mut(component_id)?; + if column.is_empty() { + return None; + } + // SAFE: if a resource column exists, row 0 exists as well + unsafe { column.swap_remove_unchecked(0) }; + + Some(()) + } + + /// Retrieves a mutable untyped reference to the given `entity`'s [Component] of the given [`ComponentId`]. + /// Returns [None] if the `entity` does not have a [Component] of the given type. + /// + /// **You should prefer to use the typed API [`World::get_mut`] where possible and only + /// use this in cases where the actual types are not known at compile time.** + #[inline] + pub fn get_by_id(&self, entity: Entity, component_id: ComponentId) -> Option> { + self.components().get_info(component_id)?; + // SAFE: entity_location is valid, component_id is valid as checked by the line above + unsafe { + get_component( + self, + component_id, + entity, + self.get_entity(entity)?.location(), + ) + } + } + + /// Retrieves a mutable untyped reference to the given `entity`'s [Component] of the given [`ComponentId`]. + /// Returns [None] if the `entity` does not have a [Component] of the given type. + /// + /// **You should prefer to use the typed API [`World::get_mut`] where possible and only + /// use this in cases where the actual types are not known at compile time.** + #[inline] + pub fn get_mut_by_id( + &mut self, + entity: Entity, + component_id: ComponentId, + ) -> Option> { + self.components().get_info(component_id)?; + // SAFE: entity_location is valid, component_id is valid as checked by the line above + unsafe { + get_mut_by_id( + self, + entity, + self.get_entity(entity)?.location(), + component_id, + ) + } + } +} + impl fmt::Debug for World { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("World") @@ -1338,11 +1505,16 @@ impl Default for MainThreadValidator { #[cfg(test)] mod tests { use super::World; + use crate::{ + change_detection::DetectChanges, + component::{ComponentDescriptor, ComponentId, StorageType}, + ptr::OwningPtr, + }; use bevy_ecs_macros::Component; use std::{ panic, sync::{ - atomic::{AtomicBool, Ordering}, + atomic::{AtomicBool, AtomicU32, Ordering}, Arc, Mutex, }, }; @@ -1462,4 +1634,97 @@ mod tests { ] ); } + + #[derive(Component)] + struct TestResource(u32); + + #[test] + fn get_resource_by_id() { + let mut world = World::new(); + world.insert_resource(TestResource(42)); + let component_id = world + .components() + .get_resource_id(std::any::TypeId::of::()) + .unwrap(); + + let resource = world.get_resource_by_id(component_id).unwrap(); + let resource = unsafe { resource.deref::() }; + + assert_eq!(resource.0, 42); + } + + #[test] + fn get_resource_mut_by_id() { + let mut world = World::new(); + world.insert_resource(TestResource(42)); + let component_id = world + .components() + .get_resource_id(std::any::TypeId::of::()) + .unwrap(); + + { + let mut resource = world.get_resource_mut_by_id(component_id).unwrap(); + resource.set_changed(); + let resource = unsafe { resource.into_inner().deref_mut::() }; + resource.0 = 43; + } + + let resource = world.get_resource_by_id(component_id).unwrap(); + let resource = unsafe { resource.deref::() }; + + assert_eq!(resource.0, 43); + } + + #[test] + fn custom_resource_with_layout() { + static DROP_COUNT: AtomicU32 = AtomicU32::new(0); + + let mut world = World::new(); + + // SAFE: the drop function is valid for the layout and the data will be safe to access from any thread + let descriptor = unsafe { + ComponentDescriptor::new_with_layout( + "Custom Test Component".to_string(), + StorageType::Table, + std::alloc::Layout::new::<[u8; 8]>(), + Some(|ptr| { + let data = ptr.read::<[u8; 8]>(); + assert_eq!(data, [0, 1, 2, 3, 4, 5, 6, 7]); + DROP_COUNT.fetch_add(1, std::sync::atomic::Ordering::SeqCst); + }), + ) + }; + + let component_id = world.init_component_with_descriptor(descriptor); + + let value: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7]; + OwningPtr::make(value, |ptr| unsafe { + // SAFE: value is valid for the component layout + world.insert_resource_by_id(component_id, ptr); + }); + + let data = unsafe { + world + .get_resource_by_id(component_id) + .unwrap() + .deref::<[u8; 8]>() + }; + assert_eq!(*data, [0, 1, 2, 3, 4, 5, 6, 7]); + + assert!(world.remove_resource_by_id(component_id).is_some()); + + assert_eq!(DROP_COUNT.load(std::sync::atomic::Ordering::SeqCst), 1); + } + + #[test] + #[should_panic = "insert_resource_by_id called with component id which doesn't exist in this world"] + fn insert_resource_by_id_invalid_component_id() { + let invalid_component_id = ComponentId::new(usize::MAX); + + let mut world = World::new(); + OwningPtr::make((), |ptr| unsafe { + // SAFE: ptr must be valid for the component_id `invalid_component_id` which is invalid, but checked by `insert_resource_by_id` + world.insert_resource_by_id(invalid_component_id, ptr); + }); + } }