-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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`.
- Loading branch information
Showing
8 changed files
with
389 additions
and
248 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} | ||
} |
Oops, something went wrong.