Skip to content

Commit 2b96530

Browse files
committed
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`.
1 parent b508b5c commit 2b96530

File tree

8 files changed

+389
-248
lines changed

8 files changed

+389
-248
lines changed

crates/bevy_ecs/src/archetype.rs

Lines changed: 2 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::{
55
bundle::BundleId,
66
component::{ComponentId, StorageType},
77
entity::{Entity, EntityLocation},
8-
storage::{Column, SparseArray, SparseSet, SparseSetIndex, TableId},
8+
storage::{SparseArray, SparseSet, SparseSetIndex, TableId},
99
};
1010
use std::{
1111
collections::HashMap,
@@ -18,7 +18,6 @@ pub struct ArchetypeId(usize);
1818

1919
impl ArchetypeId {
2020
pub const EMPTY: ArchetypeId = ArchetypeId(0);
21-
pub const RESOURCE: ArchetypeId = ArchetypeId(1);
2221
pub const INVALID: ArchetypeId = ArchetypeId(usize::MAX);
2322

2423
#[inline]
@@ -140,8 +139,7 @@ pub struct Archetype {
140139
table_info: TableInfo,
141140
table_components: Box<[ComponentId]>,
142141
sparse_set_components: Box<[ComponentId]>,
143-
pub(crate) unique_components: SparseSet<ComponentId, Column>,
144-
pub(crate) components: SparseSet<ComponentId, ArchetypeComponentInfo>,
142+
components: SparseSet<ComponentId, ArchetypeComponentInfo>,
145143
}
146144

147145
impl Archetype {
@@ -188,7 +186,6 @@ impl Archetype {
188186
components,
189187
table_components,
190188
sparse_set_components,
191-
unique_components: SparseSet::new(),
192189
entities: Default::default(),
193190
edges: Default::default(),
194191
}
@@ -224,16 +221,6 @@ impl Archetype {
224221
&self.sparse_set_components
225222
}
226223

227-
#[inline]
228-
pub fn unique_components(&self) -> &SparseSet<ComponentId, Column> {
229-
&self.unique_components
230-
}
231-
232-
#[inline]
233-
pub fn unique_components_mut(&mut self) -> &mut SparseSet<ComponentId, Column> {
234-
&mut self.unique_components
235-
}
236-
237224
#[inline]
238225
pub fn components(&self) -> impl Iterator<Item = ComponentId> + '_ {
239226
self.components.indices()
@@ -392,17 +379,6 @@ impl Default for Archetypes {
392379
archetype_component_count: 0,
393380
};
394381
archetypes.get_id_or_insert(TableId::empty(), Vec::new(), Vec::new());
395-
396-
// adds the resource archetype. it is "special" in that it is inaccessible via a "hash",
397-
// which prevents entities from being added to it
398-
archetypes.archetypes.push(Archetype::new(
399-
ArchetypeId::RESOURCE,
400-
TableId::empty(),
401-
Box::new([]),
402-
Box::new([]),
403-
Vec::new(),
404-
Vec::new(),
405-
));
406382
archetypes
407383
}
408384
}
@@ -433,21 +409,6 @@ impl Archetypes {
433409
}
434410
}
435411

436-
#[inline]
437-
pub fn resource(&self) -> &Archetype {
438-
// SAFETY: resource archetype always exists
439-
unsafe { self.archetypes.get_unchecked(ArchetypeId::RESOURCE.index()) }
440-
}
441-
442-
#[inline]
443-
pub(crate) fn resource_mut(&mut self) -> &mut Archetype {
444-
// SAFETY: resource archetype always exists
445-
unsafe {
446-
self.archetypes
447-
.get_unchecked_mut(ArchetypeId::RESOURCE.index())
448-
}
449-
}
450-
451412
#[inline]
452413
pub fn is_empty(&self) -> bool {
453414
self.archetypes.is_empty()

crates/bevy_ecs/src/lib.rs

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -956,11 +956,7 @@ mod tests {
956956
.components()
957957
.get_resource_id(TypeId::of::<Num>())
958958
.unwrap();
959-
let archetype_component_id = world
960-
.archetypes()
961-
.resource()
962-
.get_archetype_component_id(resource_id)
963-
.unwrap();
959+
let archetype_component_id = world.storages().resources.get(resource_id).unwrap().id();
964960

965961
assert_eq!(world.resource::<Num>().0, 123);
966962
assert!(world.contains_resource::<Num>());
@@ -1023,11 +1019,8 @@ mod tests {
10231019
"resource id does not change after removing / re-adding"
10241020
);
10251021

1026-
let current_archetype_component_id = world
1027-
.archetypes()
1028-
.resource()
1029-
.get_archetype_component_id(current_resource_id)
1030-
.unwrap();
1022+
let current_archetype_component_id =
1023+
world.storages().resources.get(resource_id).unwrap().id();
10311024

10321025
assert_eq!(
10331026
archetype_component_id, current_archetype_component_id,

crates/bevy_ecs/src/storage/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
//! Storage layouts for ECS data.
22
33
mod blob_vec;
4+
mod resource;
45
mod sparse_set;
56
mod table;
67

8+
pub use resource::*;
79
pub use sparse_set::*;
810
pub use table::*;
911

@@ -12,4 +14,5 @@ pub use table::*;
1214
pub struct Storages {
1315
pub sparse_sets: SparseSets,
1416
pub tables: Tables,
17+
pub resources: Resources,
1518
}
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
use crate::archetype::ArchetypeComponentId;
2+
use crate::component::{ComponentId, ComponentTicks, Components};
3+
use crate::storage::{Column, SparseSet};
4+
use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref};
5+
use std::cell::UnsafeCell;
6+
7+
/// The type-erased backing storage and metadata for a single resource within a [`World`].
8+
///
9+
/// [`World`]: crate::world::World
10+
pub struct ResourceData {
11+
column: Column,
12+
id: ArchetypeComponentId,
13+
}
14+
15+
impl ResourceData {
16+
/// Returns true if the resource is populated.
17+
#[inline]
18+
pub fn is_present(&self) -> bool {
19+
!self.column.is_empty()
20+
}
21+
22+
/// Gets the [`ArchetypeComponentId`] for the resource.
23+
#[inline]
24+
pub fn id(&self) -> ArchetypeComponentId {
25+
self.id
26+
}
27+
28+
/// Gets a read-only pointer to the underlying resource, if available.
29+
#[inline]
30+
pub fn get_data(&self) -> Option<Ptr<'_>> {
31+
self.column.get_data(0)
32+
}
33+
34+
/// Gets a read-only reference to the change ticks of the underlying resource, if available.
35+
#[inline]
36+
pub fn get_ticks(&self) -> Option<&ComponentTicks> {
37+
self.column
38+
.get_ticks(0)
39+
// SAFETY:
40+
// - This borrow's lifetime is bounded by the lifetime on self.
41+
// - A read-only borrow on self can only exist while a mutable borrow doesn't
42+
// exist.
43+
.map(|ticks| unsafe { ticks.deref() })
44+
}
45+
46+
#[inline]
47+
pub(crate) fn get_with_ticks(&self) -> Option<(Ptr<'_>, &UnsafeCell<ComponentTicks>)> {
48+
self.column.get(0)
49+
}
50+
51+
/// Inserts a value into the resource. If a value is already present
52+
/// it will be replaced.
53+
///
54+
/// # Safety
55+
/// `value` must be valid for the underlying type for the resource.
56+
///
57+
/// The underlying type must be [`Send`] or be inserted from the main thread.
58+
/// This can be validated with [`World::validate_non_send_access_untyped`].
59+
///
60+
/// [`World::validate_non_send_access_untyped`]: crate::world::World::validate_non_send_access_untyped
61+
#[inline]
62+
pub(crate) unsafe fn insert(&mut self, value: OwningPtr<'_>, change_tick: u32) {
63+
if self.is_present() {
64+
self.column.replace(0, value, change_tick);
65+
} else {
66+
self.column.push(value, ComponentTicks::new(change_tick));
67+
}
68+
}
69+
70+
/// Inserts a value into the resource with a pre-existing change tick. If a
71+
/// value is already present it will be replaced.
72+
///
73+
/// # Safety
74+
/// `value` must be valid for the underlying type for the resource.
75+
///
76+
/// The underlying type must be [`Send`] or be inserted from the main thread.
77+
/// This can be validated with [`World::validate_non_send_access_untyped`].
78+
///
79+
/// [`World::validate_non_send_access_untyped`]: crate::world::World::validate_non_send_access_untyped
80+
#[inline]
81+
pub(crate) unsafe fn insert_with_ticks(
82+
&mut self,
83+
value: OwningPtr<'_>,
84+
change_ticks: ComponentTicks,
85+
) {
86+
if self.is_present() {
87+
self.column.replace_untracked(0, value);
88+
*self.column.get_ticks_unchecked(0).deref_mut() = change_ticks;
89+
} else {
90+
self.column.push(value, change_ticks);
91+
}
92+
}
93+
94+
/// Removes a value from the resource, if present.
95+
///
96+
/// # Safety
97+
/// The underlying type must be [`Send`] or be removed from the main thread.
98+
/// This can be validated with [`World::validate_non_send_access_untyped`].
99+
///
100+
/// The removed value must be used or dropped.
101+
///
102+
/// [`World::validate_non_send_access_untyped`]: crate::world::World::validate_non_send_access_untyped
103+
#[inline]
104+
#[must_use = "The returned pointer to the removed component should be used or dropped"]
105+
pub(crate) unsafe fn remove(&mut self) -> Option<(OwningPtr<'_>, ComponentTicks)> {
106+
self.column.swap_remove_and_forget(0)
107+
}
108+
109+
/// Removes a value from the resource, if present, and drops it.
110+
///
111+
/// # Safety
112+
/// The underlying type must be [`Send`] or be removed from the main thread.
113+
/// This can be validated with [`World::validate_non_send_access_untyped`].
114+
///
115+
/// [`World::validate_non_send_access_untyped`]: crate::world::World::validate_non_send_access_untyped
116+
#[inline]
117+
pub(crate) unsafe fn remove_and_drop(&mut self) {
118+
self.column.clear();
119+
}
120+
}
121+
122+
/// The backing store for all [`Resource`]s stored in the [`World`].
123+
///
124+
/// [`Resource`]: crate::system::Resource
125+
/// [`World`]: crate::world::World
126+
#[derive(Default)]
127+
pub struct Resources {
128+
resources: SparseSet<ComponentId, ResourceData>,
129+
}
130+
131+
impl Resources {
132+
/// The total number of resources stored in the [`World`]
133+
///
134+
/// [`World`]: crate::world::World
135+
#[inline]
136+
pub fn len(&self) -> usize {
137+
self.resources.len()
138+
}
139+
140+
/// Returns true if there are no resources stored in the [`World`],
141+
/// false otherwise.
142+
///
143+
/// [`World`]: crate::world::World
144+
#[inline]
145+
pub fn is_empty(&self) -> bool {
146+
self.resources.is_empty()
147+
}
148+
149+
/// Gets read-only access to a resource, if it exists.
150+
#[inline]
151+
pub fn get(&self, component_id: ComponentId) -> Option<&ResourceData> {
152+
self.resources.get(component_id)
153+
}
154+
155+
/// Gets mutable access to a resource, if it exists.
156+
#[inline]
157+
pub(crate) fn get_mut(&mut self, component_id: ComponentId) -> Option<&mut ResourceData> {
158+
self.resources.get_mut(component_id)
159+
}
160+
161+
/// Fetches or initializes a new resource and returns back it's underlying column.
162+
///
163+
/// # Panics
164+
/// Will panic if `component_id` is not valid for the provided `components`
165+
pub(crate) fn initialize_with(
166+
&mut self,
167+
component_id: ComponentId,
168+
components: &Components,
169+
f: impl FnOnce() -> ArchetypeComponentId,
170+
) -> &mut ResourceData {
171+
self.resources.get_or_insert_with(component_id, || {
172+
let component_info = components.get_info(component_id).unwrap();
173+
ResourceData {
174+
column: Column::with_capacity(component_info, 1),
175+
id: f(),
176+
}
177+
})
178+
}
179+
180+
pub(crate) fn check_change_ticks(&mut self, change_tick: u32) {
181+
for info in self.resources.values_mut() {
182+
info.column.check_change_ticks(change_tick);
183+
}
184+
}
185+
}

0 commit comments

Comments
 (0)