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:
James Liu 2022-10-24 13:46:36 +00:00
parent b508b5c7c7
commit 2b96530947
8 changed files with 389 additions and 248 deletions

View File

@ -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()

View File

@ -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,

View File

@ -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,
}

View 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);
}
}
}

View File

@ -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]

View File

@ -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,
},

View File

@ -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()
}
}

View File

@ -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!(