Extract Resources into their own dedicated storage (#4809)
# Objective At least partially addresses #6282. Resources are currently stored as a dedicated Resource archetype (ID 1). This allows for easy code reusability, but unnecessarily adds 72 bytes (on 64-bit systems) to the struct that is only used for that one archetype. It also requires several fields to be `pub(crate)` which isn't ideal. This should also remove one sparse-set lookup from fetching, inserting, and removing resources from a `World`. ## Solution - Add `Resources` parallel to `Tables` and `SparseSets` and extract the functionality used by `Archetype` in it. - Remove `unique_components` from `Archetype` - Remove the `pub(crate)` on `Archetype::components`. - Remove `ArchetypeId::RESOURCE` - Remove `Archetypes::resource` and `Archetypes::resource_mut` --- ## Changelog Added: `Resources` type to store resources. Added: `Storages::resource` Removed: `ArchetypeId::RESOURCE` Removed: `Archetypes::resource` and `Archetypes::resources` Removed: `Archetype::unique_components` and `Archetypes::unique_components_mut` ## Migration Guide Resources have been moved to `Resources` under `Storages` in `World`. All code dependent on `Archetype::unique_components(_mut)` should access it via `world.storages().resources()` instead. All APIs accessing the raw data of individual resources (mutable *and* read-only) have been removed as these APIs allowed for unsound unsafe code. All usages of these APIs should be changed to use `World::{get, insert, remove}_resource`.
This commit is contained in:
parent
b508b5c7c7
commit
2b96530947
@ -5,7 +5,7 @@ use crate::{
|
||||
bundle::BundleId,
|
||||
component::{ComponentId, StorageType},
|
||||
entity::{Entity, EntityLocation},
|
||||
storage::{Column, SparseArray, SparseSet, SparseSetIndex, TableId},
|
||||
storage::{SparseArray, SparseSet, SparseSetIndex, TableId},
|
||||
};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
@ -18,7 +18,6 @@ pub struct ArchetypeId(usize);
|
||||
|
||||
impl ArchetypeId {
|
||||
pub const EMPTY: ArchetypeId = ArchetypeId(0);
|
||||
pub const RESOURCE: ArchetypeId = ArchetypeId(1);
|
||||
pub const INVALID: ArchetypeId = ArchetypeId(usize::MAX);
|
||||
|
||||
#[inline]
|
||||
@ -140,8 +139,7 @@ pub struct Archetype {
|
||||
table_info: TableInfo,
|
||||
table_components: Box<[ComponentId]>,
|
||||
sparse_set_components: Box<[ComponentId]>,
|
||||
pub(crate) unique_components: SparseSet<ComponentId, Column>,
|
||||
pub(crate) components: SparseSet<ComponentId, ArchetypeComponentInfo>,
|
||||
components: SparseSet<ComponentId, ArchetypeComponentInfo>,
|
||||
}
|
||||
|
||||
impl Archetype {
|
||||
@ -188,7 +186,6 @@ impl Archetype {
|
||||
components,
|
||||
table_components,
|
||||
sparse_set_components,
|
||||
unique_components: SparseSet::new(),
|
||||
entities: Default::default(),
|
||||
edges: Default::default(),
|
||||
}
|
||||
@ -224,16 +221,6 @@ impl Archetype {
|
||||
&self.sparse_set_components
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn unique_components(&self) -> &SparseSet<ComponentId, Column> {
|
||||
&self.unique_components
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn unique_components_mut(&mut self) -> &mut SparseSet<ComponentId, Column> {
|
||||
&mut self.unique_components
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn components(&self) -> impl Iterator<Item = ComponentId> + '_ {
|
||||
self.components.indices()
|
||||
@ -392,17 +379,6 @@ impl Default for Archetypes {
|
||||
archetype_component_count: 0,
|
||||
};
|
||||
archetypes.get_id_or_insert(TableId::empty(), Vec::new(), Vec::new());
|
||||
|
||||
// adds the resource archetype. it is "special" in that it is inaccessible via a "hash",
|
||||
// which prevents entities from being added to it
|
||||
archetypes.archetypes.push(Archetype::new(
|
||||
ArchetypeId::RESOURCE,
|
||||
TableId::empty(),
|
||||
Box::new([]),
|
||||
Box::new([]),
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
));
|
||||
archetypes
|
||||
}
|
||||
}
|
||||
@ -433,21 +409,6 @@ impl Archetypes {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn resource(&self) -> &Archetype {
|
||||
// SAFETY: resource archetype always exists
|
||||
unsafe { self.archetypes.get_unchecked(ArchetypeId::RESOURCE.index()) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn resource_mut(&mut self) -> &mut Archetype {
|
||||
// SAFETY: resource archetype always exists
|
||||
unsafe {
|
||||
self.archetypes
|
||||
.get_unchecked_mut(ArchetypeId::RESOURCE.index())
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.archetypes.is_empty()
|
||||
|
||||
@ -956,11 +956,7 @@ mod tests {
|
||||
.components()
|
||||
.get_resource_id(TypeId::of::<Num>())
|
||||
.unwrap();
|
||||
let archetype_component_id = world
|
||||
.archetypes()
|
||||
.resource()
|
||||
.get_archetype_component_id(resource_id)
|
||||
.unwrap();
|
||||
let archetype_component_id = world.storages().resources.get(resource_id).unwrap().id();
|
||||
|
||||
assert_eq!(world.resource::<Num>().0, 123);
|
||||
assert!(world.contains_resource::<Num>());
|
||||
@ -1023,11 +1019,8 @@ mod tests {
|
||||
"resource id does not change after removing / re-adding"
|
||||
);
|
||||
|
||||
let current_archetype_component_id = world
|
||||
.archetypes()
|
||||
.resource()
|
||||
.get_archetype_component_id(current_resource_id)
|
||||
.unwrap();
|
||||
let current_archetype_component_id =
|
||||
world.storages().resources.get(resource_id).unwrap().id();
|
||||
|
||||
assert_eq!(
|
||||
archetype_component_id, current_archetype_component_id,
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
//! Storage layouts for ECS data.
|
||||
|
||||
mod blob_vec;
|
||||
mod resource;
|
||||
mod sparse_set;
|
||||
mod table;
|
||||
|
||||
pub use resource::*;
|
||||
pub use sparse_set::*;
|
||||
pub use table::*;
|
||||
|
||||
@ -12,4 +14,5 @@ pub use table::*;
|
||||
pub struct Storages {
|
||||
pub sparse_sets: SparseSets,
|
||||
pub tables: Tables,
|
||||
pub resources: Resources,
|
||||
}
|
||||
|
||||
185
crates/bevy_ecs/src/storage/resource.rs
Normal file
185
crates/bevy_ecs/src/storage/resource.rs
Normal file
@ -0,0 +1,185 @@
|
||||
use crate::archetype::ArchetypeComponentId;
|
||||
use crate::component::{ComponentId, ComponentTicks, Components};
|
||||
use crate::storage::{Column, SparseSet};
|
||||
use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref};
|
||||
use std::cell::UnsafeCell;
|
||||
|
||||
/// The type-erased backing storage and metadata for a single resource within a [`World`].
|
||||
///
|
||||
/// [`World`]: crate::world::World
|
||||
pub struct ResourceData {
|
||||
column: Column,
|
||||
id: ArchetypeComponentId,
|
||||
}
|
||||
|
||||
impl ResourceData {
|
||||
/// Returns true if the resource is populated.
|
||||
#[inline]
|
||||
pub fn is_present(&self) -> bool {
|
||||
!self.column.is_empty()
|
||||
}
|
||||
|
||||
/// Gets the [`ArchetypeComponentId`] for the resource.
|
||||
#[inline]
|
||||
pub fn id(&self) -> ArchetypeComponentId {
|
||||
self.id
|
||||
}
|
||||
|
||||
/// Gets a read-only pointer to the underlying resource, if available.
|
||||
#[inline]
|
||||
pub fn get_data(&self) -> Option<Ptr<'_>> {
|
||||
self.column.get_data(0)
|
||||
}
|
||||
|
||||
/// Gets a read-only reference to the change ticks of the underlying resource, if available.
|
||||
#[inline]
|
||||
pub fn get_ticks(&self) -> Option<&ComponentTicks> {
|
||||
self.column
|
||||
.get_ticks(0)
|
||||
// SAFETY:
|
||||
// - This borrow's lifetime is bounded by the lifetime on self.
|
||||
// - A read-only borrow on self can only exist while a mutable borrow doesn't
|
||||
// exist.
|
||||
.map(|ticks| unsafe { ticks.deref() })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn get_with_ticks(&self) -> Option<(Ptr<'_>, &UnsafeCell<ComponentTicks>)> {
|
||||
self.column.get(0)
|
||||
}
|
||||
|
||||
/// Inserts a value into the resource. If a value is already present
|
||||
/// it will be replaced.
|
||||
///
|
||||
/// # Safety
|
||||
/// `value` must be valid for the underlying type for the resource.
|
||||
///
|
||||
/// The underlying type must be [`Send`] or be inserted from the main thread.
|
||||
/// This can be validated with [`World::validate_non_send_access_untyped`].
|
||||
///
|
||||
/// [`World::validate_non_send_access_untyped`]: crate::world::World::validate_non_send_access_untyped
|
||||
#[inline]
|
||||
pub(crate) unsafe fn insert(&mut self, value: OwningPtr<'_>, change_tick: u32) {
|
||||
if self.is_present() {
|
||||
self.column.replace(0, value, change_tick);
|
||||
} else {
|
||||
self.column.push(value, ComponentTicks::new(change_tick));
|
||||
}
|
||||
}
|
||||
|
||||
/// Inserts a value into the resource with a pre-existing change tick. If a
|
||||
/// value is already present it will be replaced.
|
||||
///
|
||||
/// # Safety
|
||||
/// `value` must be valid for the underlying type for the resource.
|
||||
///
|
||||
/// The underlying type must be [`Send`] or be inserted from the main thread.
|
||||
/// This can be validated with [`World::validate_non_send_access_untyped`].
|
||||
///
|
||||
/// [`World::validate_non_send_access_untyped`]: crate::world::World::validate_non_send_access_untyped
|
||||
#[inline]
|
||||
pub(crate) unsafe fn insert_with_ticks(
|
||||
&mut self,
|
||||
value: OwningPtr<'_>,
|
||||
change_ticks: ComponentTicks,
|
||||
) {
|
||||
if self.is_present() {
|
||||
self.column.replace_untracked(0, value);
|
||||
*self.column.get_ticks_unchecked(0).deref_mut() = change_ticks;
|
||||
} else {
|
||||
self.column.push(value, change_ticks);
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes a value from the resource, if present.
|
||||
///
|
||||
/// # Safety
|
||||
/// The underlying type must be [`Send`] or be removed from the main thread.
|
||||
/// This can be validated with [`World::validate_non_send_access_untyped`].
|
||||
///
|
||||
/// The removed value must be used or dropped.
|
||||
///
|
||||
/// [`World::validate_non_send_access_untyped`]: crate::world::World::validate_non_send_access_untyped
|
||||
#[inline]
|
||||
#[must_use = "The returned pointer to the removed component should be used or dropped"]
|
||||
pub(crate) unsafe fn remove(&mut self) -> Option<(OwningPtr<'_>, ComponentTicks)> {
|
||||
self.column.swap_remove_and_forget(0)
|
||||
}
|
||||
|
||||
/// Removes a value from the resource, if present, and drops it.
|
||||
///
|
||||
/// # Safety
|
||||
/// The underlying type must be [`Send`] or be removed from the main thread.
|
||||
/// This can be validated with [`World::validate_non_send_access_untyped`].
|
||||
///
|
||||
/// [`World::validate_non_send_access_untyped`]: crate::world::World::validate_non_send_access_untyped
|
||||
#[inline]
|
||||
pub(crate) unsafe fn remove_and_drop(&mut self) {
|
||||
self.column.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// The backing store for all [`Resource`]s stored in the [`World`].
|
||||
///
|
||||
/// [`Resource`]: crate::system::Resource
|
||||
/// [`World`]: crate::world::World
|
||||
#[derive(Default)]
|
||||
pub struct Resources {
|
||||
resources: SparseSet<ComponentId, ResourceData>,
|
||||
}
|
||||
|
||||
impl Resources {
|
||||
/// The total number of resources stored in the [`World`]
|
||||
///
|
||||
/// [`World`]: crate::world::World
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.resources.len()
|
||||
}
|
||||
|
||||
/// Returns true if there are no resources stored in the [`World`],
|
||||
/// false otherwise.
|
||||
///
|
||||
/// [`World`]: crate::world::World
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.resources.is_empty()
|
||||
}
|
||||
|
||||
/// Gets read-only access to a resource, if it exists.
|
||||
#[inline]
|
||||
pub fn get(&self, component_id: ComponentId) -> Option<&ResourceData> {
|
||||
self.resources.get(component_id)
|
||||
}
|
||||
|
||||
/// Gets mutable access to a resource, if it exists.
|
||||
#[inline]
|
||||
pub(crate) fn get_mut(&mut self, component_id: ComponentId) -> Option<&mut ResourceData> {
|
||||
self.resources.get_mut(component_id)
|
||||
}
|
||||
|
||||
/// Fetches or initializes a new resource and returns back it's underlying column.
|
||||
///
|
||||
/// # Panics
|
||||
/// Will panic if `component_id` is not valid for the provided `components`
|
||||
pub(crate) fn initialize_with(
|
||||
&mut self,
|
||||
component_id: ComponentId,
|
||||
components: &Components,
|
||||
f: impl FnOnce() -> ArchetypeComponentId,
|
||||
) -> &mut ResourceData {
|
||||
self.resources.get_or_insert_with(component_id, || {
|
||||
let component_info = components.get_info(component_id).unwrap();
|
||||
ResourceData {
|
||||
column: Column::with_capacity(component_info, 1),
|
||||
id: f(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn check_change_ticks(&mut self, change_tick: u32) {
|
||||
for info in self.resources.values_mut() {
|
||||
info.column.check_change_ticks(change_tick);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -86,6 +86,18 @@ impl Column {
|
||||
.set_changed(change_tick);
|
||||
}
|
||||
|
||||
/// Writes component data to the column at given row.
|
||||
/// Assumes the slot is initialized, calls drop.
|
||||
/// Does not update the Component's ticks.
|
||||
///
|
||||
/// # Safety
|
||||
/// Assumes data has already been allocated for the given row.
|
||||
#[inline]
|
||||
pub(crate) unsafe fn replace_untracked(&mut self, row: usize, data: OwningPtr<'_>) {
|
||||
debug_assert!(row < self.len());
|
||||
self.data.replace_unchecked(row, data);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.data.len()
|
||||
@ -104,6 +116,22 @@ impl Column {
|
||||
self.ticks.swap_remove(row);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use = "The returned pointer should be used to drop the removed component"]
|
||||
pub(crate) fn swap_remove_and_forget(
|
||||
&mut self,
|
||||
row: usize,
|
||||
) -> Option<(OwningPtr<'_>, ComponentTicks)> {
|
||||
(row < self.data.len()).then(|| {
|
||||
// SAFETY: The row was length checked before this.
|
||||
let data = unsafe { self.data.swap_remove_and_forget_unchecked(row) };
|
||||
let ticks = self.ticks.swap_remove(row).into_inner();
|
||||
(data, ticks)
|
||||
})
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// index must be in-bounds
|
||||
#[inline]
|
||||
#[must_use = "The returned pointer should be used to dropped the removed component"]
|
||||
pub(crate) unsafe fn swap_remove_and_forget_unchecked(
|
||||
@ -168,6 +196,21 @@ impl Column {
|
||||
&self.ticks
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get(&self, row: usize) -> Option<(Ptr<'_>, &UnsafeCell<ComponentTicks>)> {
|
||||
(row < self.data.len())
|
||||
// SAFETY: The row is length checked before fetching the pointer. This is being
|
||||
// accessed through a read-only reference to the column.
|
||||
.then(|| unsafe { (self.data.get_unchecked(row), self.ticks.get_unchecked(row)) })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_data(&self, row: usize) -> Option<Ptr<'_>> {
|
||||
// SAFETY: The row is length checked before fetching the pointer. This is being
|
||||
// accessed through a read-only reference to the column.
|
||||
(row < self.data.len()).then(|| unsafe { self.data.get_unchecked(row) })
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// - index must be in-bounds
|
||||
/// - no other reference to the data of the same row can exist at the same time
|
||||
@ -177,6 +220,13 @@ impl Column {
|
||||
self.data.get_unchecked(row)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_data_mut(&mut self, row: usize) -> Option<PtrMut<'_>> {
|
||||
// SAFETY: The row is length checked before fetching the pointer. This is being
|
||||
// accessed through an exclusive reference to the column.
|
||||
(row < self.data.len()).then(|| unsafe { self.data.get_unchecked_mut(row) })
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// - index must be in-bounds
|
||||
/// - no other reference to the data of the same row can exist at the same time
|
||||
@ -186,6 +236,11 @@ impl Column {
|
||||
self.data.get_unchecked_mut(row)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_ticks(&self, row: usize) -> Option<&UnsafeCell<ComponentTicks>> {
|
||||
self.ticks.get(row)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// index must be in-bounds
|
||||
#[inline]
|
||||
|
||||
@ -369,9 +369,8 @@ unsafe impl<T: Resource> SystemParamState for ResState<T> {
|
||||
);
|
||||
combined_access.add_read(component_id);
|
||||
|
||||
let resource_archetype = world.archetypes.resource();
|
||||
let archetype_component_id = resource_archetype
|
||||
.get_archetype_component_id(component_id)
|
||||
let archetype_component_id = world
|
||||
.get_resource_archetype_component_id(component_id)
|
||||
.unwrap();
|
||||
system_meta
|
||||
.archetype_component_access
|
||||
@ -393,8 +392,8 @@ impl<'w, 's, T: Resource> SystemParamFetch<'w, 's> for ResState<T> {
|
||||
world: &'w World,
|
||||
change_tick: u32,
|
||||
) -> Self::Item {
|
||||
let column = world
|
||||
.get_populated_resource_column(state.component_id)
|
||||
let (ptr, ticks) = world
|
||||
.get_resource_with_ticks(state.component_id)
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"Resource requested by {} does not exist: {}",
|
||||
@ -403,8 +402,8 @@ impl<'w, 's, T: Resource> SystemParamFetch<'w, 's> for ResState<T> {
|
||||
)
|
||||
});
|
||||
Res {
|
||||
value: column.get_data_ptr().deref::<T>(),
|
||||
ticks: column.get_ticks_unchecked(0).deref(),
|
||||
value: ptr.deref(),
|
||||
ticks: ticks.deref(),
|
||||
last_change_tick: system_meta.last_change_tick,
|
||||
change_tick,
|
||||
}
|
||||
@ -442,10 +441,10 @@ impl<'w, 's, T: Resource> SystemParamFetch<'w, 's> for OptionResState<T> {
|
||||
change_tick: u32,
|
||||
) -> Self::Item {
|
||||
world
|
||||
.get_populated_resource_column(state.0.component_id)
|
||||
.map(|column| Res {
|
||||
value: column.get_data_ptr().deref::<T>(),
|
||||
ticks: column.get_ticks_unchecked(0).deref(),
|
||||
.get_resource_with_ticks(state.0.component_id)
|
||||
.map(|(ptr, ticks)| Res {
|
||||
value: ptr.deref(),
|
||||
ticks: ticks.deref(),
|
||||
last_change_tick: system_meta.last_change_tick,
|
||||
change_tick,
|
||||
})
|
||||
@ -480,9 +479,8 @@ unsafe impl<T: Resource> SystemParamState for ResMutState<T> {
|
||||
}
|
||||
combined_access.add_write(component_id);
|
||||
|
||||
let resource_archetype = world.archetypes.resource();
|
||||
let archetype_component_id = resource_archetype
|
||||
.get_archetype_component_id(component_id)
|
||||
let archetype_component_id = world
|
||||
.get_resource_archetype_component_id(component_id)
|
||||
.unwrap();
|
||||
system_meta
|
||||
.archetype_component_access
|
||||
@ -938,9 +936,8 @@ unsafe impl<T: 'static> SystemParamState for NonSendState<T> {
|
||||
);
|
||||
combined_access.add_read(component_id);
|
||||
|
||||
let resource_archetype = world.archetypes.resource();
|
||||
let archetype_component_id = resource_archetype
|
||||
.get_archetype_component_id(component_id)
|
||||
let archetype_component_id = world
|
||||
.get_resource_archetype_component_id(component_id)
|
||||
.unwrap();
|
||||
system_meta
|
||||
.archetype_component_access
|
||||
@ -963,8 +960,8 @@ impl<'w, 's, T: 'static> SystemParamFetch<'w, 's> for NonSendState<T> {
|
||||
change_tick: u32,
|
||||
) -> Self::Item {
|
||||
world.validate_non_send_access::<T>();
|
||||
let column = world
|
||||
.get_populated_resource_column(state.component_id)
|
||||
let (ptr, ticks) = world
|
||||
.get_resource_with_ticks(state.component_id)
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"Non-send resource requested by {} does not exist: {}",
|
||||
@ -974,8 +971,8 @@ impl<'w, 's, T: 'static> SystemParamFetch<'w, 's> for NonSendState<T> {
|
||||
});
|
||||
|
||||
NonSend {
|
||||
value: column.get_data_ptr().deref::<T>(),
|
||||
ticks: column.get_ticks_unchecked(0).read(),
|
||||
value: ptr.deref(),
|
||||
ticks: ticks.read(),
|
||||
last_change_tick: system_meta.last_change_tick,
|
||||
change_tick,
|
||||
}
|
||||
@ -1014,10 +1011,10 @@ impl<'w, 's, T: 'static> SystemParamFetch<'w, 's> for OptionNonSendState<T> {
|
||||
) -> Self::Item {
|
||||
world.validate_non_send_access::<T>();
|
||||
world
|
||||
.get_populated_resource_column(state.0.component_id)
|
||||
.map(|column| NonSend {
|
||||
value: column.get_data_ptr().deref::<T>(),
|
||||
ticks: column.get_ticks_unchecked(0).read(),
|
||||
.get_resource_with_ticks(state.0.component_id)
|
||||
.map(|(ptr, ticks)| NonSend {
|
||||
value: ptr.deref(),
|
||||
ticks: ticks.read(),
|
||||
last_change_tick: system_meta.last_change_tick,
|
||||
change_tick,
|
||||
})
|
||||
@ -1054,9 +1051,8 @@ unsafe impl<T: 'static> SystemParamState for NonSendMutState<T> {
|
||||
}
|
||||
combined_access.add_write(component_id);
|
||||
|
||||
let resource_archetype = world.archetypes.resource();
|
||||
let archetype_component_id = resource_archetype
|
||||
.get_archetype_component_id(component_id)
|
||||
let archetype_component_id = world
|
||||
.get_resource_archetype_component_id(component_id)
|
||||
.unwrap();
|
||||
system_meta
|
||||
.archetype_component_access
|
||||
@ -1079,8 +1075,8 @@ impl<'w, 's, T: 'static> SystemParamFetch<'w, 's> for NonSendMutState<T> {
|
||||
change_tick: u32,
|
||||
) -> Self::Item {
|
||||
world.validate_non_send_access::<T>();
|
||||
let column = world
|
||||
.get_populated_resource_column(state.component_id)
|
||||
let (ptr, ticks) = world
|
||||
.get_resource_with_ticks(state.component_id)
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"Non-send resource requested by {} does not exist: {}",
|
||||
@ -1089,9 +1085,9 @@ impl<'w, 's, T: 'static> SystemParamFetch<'w, 's> for NonSendMutState<T> {
|
||||
)
|
||||
});
|
||||
NonSendMut {
|
||||
value: column.get_data_ptr().assert_unique().deref_mut::<T>(),
|
||||
value: ptr.assert_unique().deref_mut(),
|
||||
ticks: Ticks {
|
||||
component_ticks: column.get_ticks_unchecked(0).deref_mut(),
|
||||
component_ticks: ticks.deref_mut(),
|
||||
last_change_tick: system_meta.last_change_tick,
|
||||
change_tick,
|
||||
},
|
||||
@ -1128,11 +1124,11 @@ impl<'w, 's, T: 'static> SystemParamFetch<'w, 's> for OptionNonSendMutState<T> {
|
||||
) -> Self::Item {
|
||||
world.validate_non_send_access::<T>();
|
||||
world
|
||||
.get_populated_resource_column(state.0.component_id)
|
||||
.map(|column| NonSendMut {
|
||||
value: column.get_data_ptr().assert_unique().deref_mut::<T>(),
|
||||
.get_resource_with_ticks(state.0.component_id)
|
||||
.map(|(ptr, ticks)| NonSendMut {
|
||||
value: ptr.assert_unique().deref_mut(),
|
||||
ticks: Ticks {
|
||||
component_ticks: column.get_ticks_unchecked(0).deref_mut(),
|
||||
component_ticks: ticks.deref_mut(),
|
||||
last_change_tick: system_meta.last_change_tick,
|
||||
change_tick,
|
||||
},
|
||||
|
||||
@ -8,22 +8,22 @@ pub use spawn_batch::*;
|
||||
pub use world_cell::*;
|
||||
|
||||
use crate::{
|
||||
archetype::{ArchetypeComponentId, ArchetypeComponentInfo, ArchetypeId, Archetypes},
|
||||
archetype::{ArchetypeComponentId, ArchetypeId, Archetypes},
|
||||
bundle::{Bundle, BundleInserter, BundleSpawner, Bundles},
|
||||
change_detection::{MutUntyped, Ticks},
|
||||
component::{
|
||||
Component, ComponentDescriptor, ComponentId, ComponentInfo, ComponentTicks, Components,
|
||||
StorageType,
|
||||
},
|
||||
entity::{AllocAtWithoutReplacement, Entities, Entity},
|
||||
query::{QueryState, ReadOnlyWorldQuery, WorldQuery},
|
||||
storage::{Column, SparseSet, Storages},
|
||||
storage::{ResourceData, SparseSet, Storages},
|
||||
system::Resource,
|
||||
};
|
||||
use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref};
|
||||
use bevy_utils::tracing::debug;
|
||||
use std::{
|
||||
any::TypeId,
|
||||
cell::UnsafeCell,
|
||||
fmt,
|
||||
sync::atomic::{AtomicU32, Ordering},
|
||||
};
|
||||
@ -796,63 +796,37 @@ impl World {
|
||||
#[allow(unused_unsafe)]
|
||||
pub unsafe fn remove_resource_unchecked<R: 'static>(&mut self) -> Option<R> {
|
||||
let component_id = self.components.get_resource_id(TypeId::of::<R>())?;
|
||||
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;
|
||||
// SAFETY: the resource is of type R and the value is returned back to the caller.
|
||||
unsafe {
|
||||
let (ptr, _) = self.storages.resources.get_mut(component_id)?.remove()?;
|
||||
Some(ptr.read::<R>())
|
||||
}
|
||||
// SAFETY: if a resource column exists, row 0 exists as well. caller takes ownership of the
|
||||
// ptr value / drop is called when R is dropped
|
||||
let (ptr, _) = unsafe { column.swap_remove_and_forget_unchecked(0) };
|
||||
// SAFETY: column is of type R
|
||||
Some(unsafe { ptr.read::<R>() })
|
||||
}
|
||||
|
||||
/// Returns `true` if a resource of type `R` exists. Otherwise returns `false`.
|
||||
#[inline]
|
||||
pub fn contains_resource<R: 'static>(&self) -> bool {
|
||||
let component_id =
|
||||
if let Some(component_id) = self.components.get_resource_id(TypeId::of::<R>()) {
|
||||
component_id
|
||||
} else {
|
||||
return false;
|
||||
};
|
||||
self.get_populated_resource_column(component_id).is_some()
|
||||
self.components
|
||||
.get_resource_id(TypeId::of::<R>())
|
||||
.and_then(|component_id| self.storages.resources.get(component_id))
|
||||
.map(|info| info.is_present())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn is_resource_added<R: Resource>(&self) -> bool {
|
||||
let component_id =
|
||||
if let Some(component_id) = self.components.get_resource_id(TypeId::of::<R>()) {
|
||||
component_id
|
||||
} else {
|
||||
return false;
|
||||
};
|
||||
let column = if let Some(column) = self.get_populated_resource_column(component_id) {
|
||||
column
|
||||
} else {
|
||||
return false;
|
||||
};
|
||||
// SAFETY: resources table always have row 0
|
||||
let ticks = unsafe { column.get_ticks_unchecked(0).deref() };
|
||||
ticks.is_added(self.last_change_tick(), self.read_change_tick())
|
||||
self.components
|
||||
.get_resource_id(TypeId::of::<R>())
|
||||
.and_then(|component_id| self.storages.resources.get(component_id)?.get_ticks())
|
||||
.map(|ticks| ticks.is_added(self.last_change_tick(), self.read_change_tick()))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn is_resource_changed<R: Resource>(&self) -> bool {
|
||||
let component_id =
|
||||
if let Some(component_id) = self.components.get_resource_id(TypeId::of::<R>()) {
|
||||
component_id
|
||||
} else {
|
||||
return false;
|
||||
};
|
||||
let column = if let Some(column) = self.get_populated_resource_column(component_id) {
|
||||
column
|
||||
} else {
|
||||
return false;
|
||||
};
|
||||
// SAFETY: resources table always have row 0
|
||||
let ticks = unsafe { column.get_ticks_unchecked(0).deref() };
|
||||
ticks.is_changed(self.last_change_tick(), self.read_change_tick())
|
||||
self.components
|
||||
.get_resource_id(TypeId::of::<R>())
|
||||
.and_then(|component_id| self.storages.resources.get(component_id)?.get_ticks())
|
||||
.map(|ticks| ticks.is_changed(self.last_change_tick(), self.read_change_tick()))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Gets a reference to the resource of the given type
|
||||
@ -1013,6 +987,25 @@ impl World {
|
||||
self.get_non_send_unchecked_mut_with_id(component_id)
|
||||
}
|
||||
|
||||
// Shorthand helper function for getting the data and change ticks for a resource.
|
||||
#[inline]
|
||||
pub(crate) fn get_resource_with_ticks(
|
||||
&self,
|
||||
component_id: ComponentId,
|
||||
) -> Option<(Ptr<'_>, &UnsafeCell<ComponentTicks>)> {
|
||||
self.storages.resources.get(component_id)?.get_with_ticks()
|
||||
}
|
||||
|
||||
// Shorthand helper function for getting the [`ArchetypeComponentId`] for a resource.
|
||||
#[inline]
|
||||
pub(crate) fn get_resource_archetype_component_id(
|
||||
&self,
|
||||
component_id: ComponentId,
|
||||
) -> Option<ArchetypeComponentId> {
|
||||
let resource = self.storages.resources.get(component_id)?;
|
||||
Some(resource.id())
|
||||
}
|
||||
|
||||
/// For a given batch of ([Entity], [Bundle]) pairs, either spawns each [Entity] with the given
|
||||
/// bundle (if the entity does not exist), or inserts the [Bundle] (if the entity already exists).
|
||||
/// This is faster than doing equivalent operations one-by-one.
|
||||
@ -1173,30 +1166,21 @@ impl World {
|
||||
.components
|
||||
.get_resource_id(TypeId::of::<R>())
|
||||
.unwrap_or_else(|| panic!("resource does not exist: {}", std::any::type_name::<R>()));
|
||||
|
||||
// If the resource isn't send and sync, validate that we are on the main thread, so that we can access it.
|
||||
let component_info = self.components().get_info(component_id).unwrap();
|
||||
if !component_info.is_send_and_sync() {
|
||||
self.validate_non_send_access::<R>();
|
||||
}
|
||||
|
||||
let (ptr, mut ticks) = {
|
||||
let resource_archetype = self.archetypes.resource_mut();
|
||||
let unique_components = resource_archetype.unique_components_mut();
|
||||
let column = unique_components.get_mut(component_id).unwrap_or_else(|| {
|
||||
panic!("resource does not exist: {}", std::any::type_name::<R>())
|
||||
});
|
||||
assert!(
|
||||
!column.is_empty(),
|
||||
"resource does not exist: {}",
|
||||
std::any::type_name::<R>()
|
||||
);
|
||||
// SAFETY: if a resource column exists, row 0 exists as well. caller takes ownership of
|
||||
// the ptr value / drop is called when R is dropped
|
||||
unsafe { column.swap_remove_and_forget_unchecked(0) }
|
||||
};
|
||||
// SAFETY: pointer is of type R
|
||||
let (ptr, mut ticks) = self
|
||||
.storages
|
||||
.resources
|
||||
.get_mut(component_id)
|
||||
// SAFETY: The type R is Send and Sync or we've already validated that we're on the main thread.
|
||||
.and_then(|info| unsafe { info.remove() })
|
||||
.unwrap_or_else(|| panic!("resource does not exist: {}", std::any::type_name::<R>()));
|
||||
// Read the value onto the stack to avoid potential mut aliasing.
|
||||
// SAFETY: pointer is of type R
|
||||
let mut value = unsafe { ptr.read::<R>() };
|
||||
let value_mut = Mut {
|
||||
value: &mut value,
|
||||
@ -1212,18 +1196,22 @@ impl World {
|
||||
This is not allowed as the original resource is reinserted to the world after the FnOnce param is invoked.",
|
||||
std::any::type_name::<R>());
|
||||
|
||||
let resource_archetype = self.archetypes.resource_mut();
|
||||
let unique_components = resource_archetype.unique_components_mut();
|
||||
let column = unique_components
|
||||
.get_mut(component_id)
|
||||
.unwrap_or_else(|| panic!("resource does not exist: {}", std::any::type_name::<R>()));
|
||||
|
||||
OwningPtr::make(value, |ptr| {
|
||||
// SAFETY: pointer is of type R
|
||||
unsafe {
|
||||
column.push(ptr, ticks);
|
||||
self.storages
|
||||
.resources
|
||||
.get_mut(component_id)
|
||||
.map(|info| info.insert_with_ticks(ptr, ticks))
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"No resource of type {} exists in the World.",
|
||||
std::any::type_name::<R>()
|
||||
)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
@ -1258,8 +1246,11 @@ impl World {
|
||||
&self,
|
||||
component_id: ComponentId,
|
||||
) -> Option<&R> {
|
||||
let column = self.get_populated_resource_column(component_id)?;
|
||||
Some(column.get_data_ptr().deref::<R>())
|
||||
self.storages
|
||||
.resources
|
||||
.get(component_id)?
|
||||
.get_data()
|
||||
.map(|ptr| ptr.deref())
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
@ -1270,11 +1261,11 @@ impl World {
|
||||
&self,
|
||||
component_id: ComponentId,
|
||||
) -> Option<Mut<'_, R>> {
|
||||
let column = self.get_populated_resource_column(component_id)?;
|
||||
let (ptr, ticks) = self.get_resource_with_ticks(component_id)?;
|
||||
Some(Mut {
|
||||
value: column.get_data_ptr().assert_unique().deref_mut(),
|
||||
value: ptr.assert_unique().deref_mut(),
|
||||
ticks: Ticks {
|
||||
component_ticks: column.get_ticks_unchecked(0).deref_mut(),
|
||||
component_ticks: ticks.deref_mut(),
|
||||
last_change_tick: self.last_change_tick(),
|
||||
change_tick: self.read_change_tick(),
|
||||
},
|
||||
@ -1321,42 +1312,24 @@ impl World {
|
||||
let change_tick = self.change_tick();
|
||||
|
||||
// SAFETY: component_id is valid, ensured by caller
|
||||
let column = self.initialize_resource_internal(component_id);
|
||||
if column.is_empty() {
|
||||
// SAFETY: column is of type R and has been allocated above
|
||||
column.push(value, ComponentTicks::new(change_tick));
|
||||
} else {
|
||||
column.replace(0, value, change_tick);
|
||||
}
|
||||
self.initialize_resource_internal(component_id)
|
||||
.insert(value, change_tick);
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// `component_id` must be valid for this world
|
||||
#[inline]
|
||||
unsafe fn initialize_resource_internal(&mut self, component_id: ComponentId) -> &mut Column {
|
||||
// SAFETY: resource archetype always exists
|
||||
let resource_archetype = self
|
||||
.archetypes
|
||||
.archetypes
|
||||
.get_unchecked_mut(ArchetypeId::RESOURCE.index());
|
||||
let resource_archetype_components = &mut resource_archetype.components;
|
||||
unsafe fn initialize_resource_internal(
|
||||
&mut self,
|
||||
component_id: ComponentId,
|
||||
) -> &mut ResourceData {
|
||||
let archetype_component_count = &mut self.archetypes.archetype_component_count;
|
||||
let components = &self.components;
|
||||
resource_archetype
|
||||
.unique_components
|
||||
.get_or_insert_with(component_id, || {
|
||||
resource_archetype_components.insert(
|
||||
component_id,
|
||||
ArchetypeComponentInfo {
|
||||
archetype_component_id: ArchetypeComponentId::new(
|
||||
*archetype_component_count,
|
||||
),
|
||||
storage_type: StorageType::Table,
|
||||
},
|
||||
);
|
||||
self.storages
|
||||
.resources
|
||||
.initialize_with(component_id, &self.components, || {
|
||||
let id = ArchetypeComponentId::new(*archetype_component_count);
|
||||
*archetype_component_count += 1;
|
||||
let component_info = components.get_info_unchecked(component_id);
|
||||
Column::with_capacity(component_info, 1)
|
||||
id
|
||||
})
|
||||
}
|
||||
|
||||
@ -1374,22 +1347,6 @@ impl World {
|
||||
component_id
|
||||
}
|
||||
|
||||
/// returns the resource column if the requested resource exists
|
||||
pub(crate) fn get_populated_resource_column(
|
||||
&self,
|
||||
component_id: ComponentId,
|
||||
) -> Option<&Column> {
|
||||
let resource_archetype = self.archetypes.resource();
|
||||
let unique_components = resource_archetype.unique_components();
|
||||
unique_components.get(component_id).and_then(|column| {
|
||||
if column.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(column)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn validate_non_send_access<T: 'static>(&self) {
|
||||
assert!(
|
||||
self.main_thread_validator.is_main_thread(),
|
||||
@ -1449,10 +1406,7 @@ impl World {
|
||||
let change_tick = self.change_tick();
|
||||
self.storages.tables.check_change_ticks(change_tick);
|
||||
self.storages.sparse_sets.check_change_ticks(change_tick);
|
||||
let resource_archetype = self.archetypes.resource_mut();
|
||||
for column in resource_archetype.unique_components.values_mut() {
|
||||
column.check_change_ticks(change_tick);
|
||||
}
|
||||
self.storages.resources.check_change_ticks(change_tick);
|
||||
}
|
||||
|
||||
pub fn clear_entities(&mut self) {
|
||||
@ -1476,9 +1430,7 @@ impl World {
|
||||
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())
|
||||
self.storages.resources.get(component_id)?.get_data()
|
||||
}
|
||||
|
||||
/// Gets a resource to the resource with the id [`ComponentId`] if it exists.
|
||||
@ -1494,22 +1446,21 @@ impl World {
|
||||
self.validate_non_send_access_untyped(info.name());
|
||||
}
|
||||
|
||||
let column = self.get_populated_resource_column(component_id)?;
|
||||
let (ptr, ticks) = self.get_resource_with_ticks(component_id)?;
|
||||
|
||||
// SAFETY: 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
|
||||
// SAFE: This function has exclusive access to the world so nothing aliases `ticks`.
|
||||
let ticks = Ticks {
|
||||
// SAFETY:
|
||||
// - 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() },
|
||||
component_ticks: unsafe { ticks.deref_mut() },
|
||||
last_change_tick: self.last_change_tick(),
|
||||
change_tick: self.read_change_tick(),
|
||||
};
|
||||
|
||||
Some(MutUntyped {
|
||||
// SAFETY: world access is unique, so no other reference can exist at the same time
|
||||
value: unsafe { column.get_data_ptr().assert_unique() },
|
||||
// SAFETY: This function has exclusive access to the world so nothing aliases `ptr`.
|
||||
value: unsafe { ptr.assert_unique() },
|
||||
ticks,
|
||||
})
|
||||
}
|
||||
@ -1523,16 +1474,13 @@ impl World {
|
||||
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;
|
||||
// SAFETY: The underlying type is Send and Sync or we've already validated we're on the main thread
|
||||
unsafe {
|
||||
self.storages
|
||||
.resources
|
||||
.get_mut(component_id)?
|
||||
.remove_and_drop();
|
||||
}
|
||||
// SAFETY: if a resource column exists, row 0 exists as well
|
||||
unsafe { column.swap_remove_unchecked(0) };
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
@ -1586,10 +1534,7 @@ impl fmt::Debug for World {
|
||||
.field("entity_count", &self.entities.len())
|
||||
.field("archetype_count", &self.archetypes.len())
|
||||
.field("component_count", &self.components.len())
|
||||
.field(
|
||||
"resource_count",
|
||||
&self.archetypes.resource().unique_components.len(),
|
||||
)
|
||||
.field("resource_count", &self.storages.resources.len())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
@ -183,8 +183,9 @@ impl<'w> WorldCell<'w> {
|
||||
/// Gets a reference to the resource of the given type
|
||||
pub fn get_resource<T: Resource>(&self) -> Option<WorldBorrow<'_, T>> {
|
||||
let component_id = self.world.components.get_resource_id(TypeId::of::<T>())?;
|
||||
let resource_archetype = self.world.archetypes.resource();
|
||||
let archetype_component_id = resource_archetype.get_archetype_component_id(component_id)?;
|
||||
let archetype_component_id = self
|
||||
.world
|
||||
.get_resource_archetype_component_id(component_id)?;
|
||||
Some(WorldBorrow::new(
|
||||
// SAFETY: ComponentId matches TypeId
|
||||
unsafe { self.world.get_resource_with_id(component_id)? },
|
||||
@ -215,8 +216,9 @@ impl<'w> WorldCell<'w> {
|
||||
/// Gets a mutable reference to the resource of the given type
|
||||
pub fn get_resource_mut<T: Resource>(&self) -> Option<WorldBorrowMut<'_, T>> {
|
||||
let component_id = self.world.components.get_resource_id(TypeId::of::<T>())?;
|
||||
let resource_archetype = self.world.archetypes.resource();
|
||||
let archetype_component_id = resource_archetype.get_archetype_component_id(component_id)?;
|
||||
let archetype_component_id = self
|
||||
.world
|
||||
.get_resource_archetype_component_id(component_id)?;
|
||||
Some(WorldBorrowMut::new(
|
||||
// SAFETY: ComponentId matches TypeId and access is checked by WorldBorrowMut
|
||||
unsafe {
|
||||
@ -250,8 +252,9 @@ impl<'w> WorldCell<'w> {
|
||||
/// Gets an immutable reference to the non-send resource of the given type, if it exists.
|
||||
pub fn get_non_send_resource<T: 'static>(&self) -> Option<WorldBorrow<'_, T>> {
|
||||
let component_id = self.world.components.get_resource_id(TypeId::of::<T>())?;
|
||||
let resource_archetype = self.world.archetypes.resource();
|
||||
let archetype_component_id = resource_archetype.get_archetype_component_id(component_id)?;
|
||||
let archetype_component_id = self
|
||||
.world
|
||||
.get_resource_archetype_component_id(component_id)?;
|
||||
Some(WorldBorrow::new(
|
||||
// SAFETY: ComponentId matches TypeId
|
||||
unsafe { self.world.get_non_send_with_id(component_id)? },
|
||||
@ -282,8 +285,9 @@ impl<'w> WorldCell<'w> {
|
||||
/// Gets a mutable reference to the non-send resource of the given type, if it exists.
|
||||
pub fn get_non_send_resource_mut<T: 'static>(&self) -> Option<WorldBorrowMut<'_, T>> {
|
||||
let component_id = self.world.components.get_resource_id(TypeId::of::<T>())?;
|
||||
let resource_archetype = self.world.archetypes.resource();
|
||||
let archetype_component_id = resource_archetype.get_archetype_component_id(component_id)?;
|
||||
let archetype_component_id = self
|
||||
.world
|
||||
.get_resource_archetype_component_id(component_id)?;
|
||||
Some(WorldBorrowMut::new(
|
||||
// SAFETY: ComponentId matches TypeId and access is checked by WorldBorrowMut
|
||||
unsafe {
|
||||
@ -319,7 +323,7 @@ impl<'w> WorldCell<'w> {
|
||||
mod tests {
|
||||
use super::BASE_ACCESS;
|
||||
use crate as bevy_ecs;
|
||||
use crate::{archetype::ArchetypeId, system::Resource, world::World};
|
||||
use crate::{system::Resource, world::World};
|
||||
use std::any::TypeId;
|
||||
|
||||
#[derive(Resource)]
|
||||
@ -377,10 +381,9 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
let resource_id = world.components.get_resource_id(TypeId::of::<A>()).unwrap();
|
||||
let resource_archetype = world.archetypes.get(ArchetypeId::RESOURCE).unwrap();
|
||||
let u32_archetype_component_id = resource_archetype
|
||||
.get_archetype_component_id(resource_id)
|
||||
let u32_component_id = world.components.get_resource_id(TypeId::of::<A>()).unwrap();
|
||||
let u32_archetype_component_id = world
|
||||
.get_resource_archetype_component_id(u32_component_id)
|
||||
.unwrap();
|
||||
assert_eq!(world.archetype_component_access.access.len(), 1);
|
||||
assert_eq!(
|
||||
|
||||
Loading…
Reference in New Issue
Block a user