Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - Extract Resources into their own dedicated storage #4809

Closed
wants to merge 40 commits into from
Closed
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
7cd3e3f
Preliminary changes
james7132 May 20, 2022
76ede2f
Fix compilation
james7132 May 20, 2022
b7b16be
Provide internal helper function
james7132 May 31, 2022
5464d47
More complete Resources impl
james7132 May 31, 2022
32c5456
Fix build
james7132 May 31, 2022
bb9a49f
Cleanup
james7132 May 31, 2022
b719c1b
Fix CI
james7132 May 31, 2022
99f14d7
Fix CI
james7132 Jun 7, 2022
4918e10
Shrink Resources
james7132 Jun 7, 2022
2b24bac
Less jank way of fetching ticks.
james7132 Jun 11, 2022
ab675d1
Better safety comment.
james7132 Jun 11, 2022
bd25ccc
Better safety comment.
james7132 Jun 11, 2022
70578fe
Correct safety comment pointing to the right parameter.
james7132 Jun 11, 2022
27ce1a3
Much simpler remove impl.
james7132 Jun 11, 2022
520fd30
Update safety comments
james7132 Jun 15, 2022
7737705
Move the unsafe into Column
james7132 Jun 15, 2022
a6b5e4b
Column::get
james7132 Jun 15, 2022
610c617
Formatting
james7132 Jun 15, 2022
e32ec9e
Address World review comments
james7132 Jun 15, 2022
515e2b3
Make storage APIs a bit more externally friendly
james7132 Jun 15, 2022
c1f1133
Remove contains
james7132 Jun 15, 2022
850b318
Merge branch 'main' into no-unique-components
james7132 Jun 16, 2022
a0731e6
Merge branch 'main' into no-unique-components
james7132 Jul 2, 2022
f4123b9
Fix test compilation
james7132 Jul 2, 2022
bb38531
Fix must_use typo
james7132 Oct 13, 2022
bb59857
Change panic message.
james7132 Oct 13, 2022
3556580
ResourceInfo -> ResourceData
james7132 Oct 13, 2022
734584d
Type doc comment for ResourceData
james7132 Oct 13, 2022
01c176f
Merge branch 'main' into no-unique-components
james7132 Oct 15, 2022
58027c4
Fix CI
james7132 Oct 16, 2022
d3d846a
Update get's doc comment.
james7132 Oct 16, 2022
40afd47
Update get_mut's doc comment.
james7132 Oct 16, 2022
7b9fbf0
Fix docs typo
james7132 Oct 17, 2022
a6c8977
Use an impl param instead of a bound
james7132 Oct 17, 2022
39be822
ArchetypeComponentInfo -> Id
james7132 Oct 17, 2022
efd5113
Remove public access to data methods
james7132 Oct 18, 2022
535bc9f
Update safety comments on mutative resource APIs
james7132 Oct 18, 2022
7932fa8
Fix CI
james7132 Oct 18, 2022
c2476ed
Update safety docs on get_ticks
james7132 Oct 18, 2022
31cd2b4
Formatting
james7132 Oct 18, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 2 additions & 41 deletions crates/bevy_ecs/src/archetype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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]
Expand Down Expand Up @@ -122,8 +121,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>,
james7132 marked this conversation as resolved.
Show resolved Hide resolved
}

impl Archetype {
Expand Down Expand Up @@ -170,7 +168,6 @@ impl Archetype {
components,
table_components,
sparse_set_components,
unique_components: SparseSet::new(),
entities: Default::default(),
edges: Default::default(),
}
Expand Down Expand Up @@ -206,16 +203,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()
Expand Down Expand Up @@ -374,17 +361,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
}
}
Expand Down Expand Up @@ -415,21 +391,6 @@ impl Archetypes {
}
}

#[inline]
pub fn resource(&self) -> &Archetype {
// SAFE: resource archetype always exists
unsafe { self.archetypes.get_unchecked(ArchetypeId::RESOURCE.index()) }
}

#[inline]
pub fn resource_mut(&mut self) -> &mut Archetype {
// SAFE: resource archetype always exists
unsafe {
self.archetypes
.get_unchecked_mut(ArchetypeId::RESOURCE.index())
}
}

#[inline]
pub fn is_empty(&self) -> bool {
self.archetypes.is_empty()
Expand Down
8 changes: 4 additions & 4 deletions crates/bevy_ecs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1014,8 +1014,8 @@ mod tests {
.get_resource_id(TypeId::of::<i32>())
.unwrap();
let archetype_component_id = world
.archetypes()
.resource()
.storages()
.resources
.get_archetype_component_id(resource_id)
.unwrap();

Expand Down Expand Up @@ -1081,8 +1081,8 @@ mod tests {
);

let current_archetype_component_id = world
.archetypes()
.resource()
.storages()
.resources
.get_archetype_component_id(current_resource_id)
.unwrap();

Expand Down
3 changes: 3 additions & 0 deletions crates/bevy_ecs/src/storage/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
//! Storage layouts for ECS data.

mod blob_vec;
mod resource;
mod sparse_set;
mod table;

pub use blob_vec::*;
pub use resource::*;
pub use sparse_set::*;
pub use table::*;

Expand All @@ -13,4 +15,5 @@ pub use table::*;
pub struct Storages {
pub sparse_sets: SparseSets,
pub tables: Tables,
pub resources: Resources,
}
153 changes: 153 additions & 0 deletions crates/bevy_ecs/src/storage/resource.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
use crate::archetype::ArchetypeComponentId;
use crate::archetype::ArchetypeComponentInfo;
use crate::component::{ComponentId, ComponentTicks};
use crate::storage::{Column, SparseSet};
use bevy_ptr::{OwningPtr, Ptr, PtrMut, UnsafeCellDeref};
use std::cell::UnsafeCell;

pub(crate) struct ResourceInfo {
pub data: Column,
james7132 marked this conversation as resolved.
Show resolved Hide resolved
pub component_info: ArchetypeComponentInfo,
}

/// The backing store for all [`Resource`]s stored in the [`World`].
///
/// [`Resource`]: crate::system::Resource
/// [`World`]: crate::world::World
#[derive(Default)]
pub struct Resources {
james7132 marked this conversation as resolved.
Show resolved Hide resolved
pub(crate) resources: SparseSet<ComponentId, ResourceInfo>,
}

impl Resources {
/// Gets the [`ArchetypeComponentId`] for a given resoruce.
#[inline]
pub fn get_archetype_component_id(
&self,
component_id: ComponentId,
) -> Option<ArchetypeComponentId> {
self.resources
.get(component_id)
.map(|info| info.component_info.archetype_component_id)
}

/// The total number of resoruces stored in the [`World`]
james7132 marked this conversation as resolved.
Show resolved Hide resolved
///
/// [`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 a read-only [`Ptr`] to a resource, if available.
james7132 marked this conversation as resolved.
Show resolved Hide resolved
#[inline]
pub fn get(&self, component_id: ComponentId) -> Option<Ptr<'_>> {
let column = &self.resources.get(component_id)?.data;
// SAFE: if a resource column exists, row 0 exists as well. caller takes ownership of the
// ptr value / drop is called when R is dropped
(!column.is_empty()).then(|| unsafe { column.get_data_unchecked(0) })
james7132 marked this conversation as resolved.
Show resolved Hide resolved
}

/// Gets a read-only [`Ptr`] to a resource, if available.
james7132 marked this conversation as resolved.
Show resolved Hide resolved
#[inline]
pub fn get_mut(&mut self, component_id: ComponentId) -> Option<PtrMut<'_>> {
let column = &mut self.resources.get_mut(component_id)?.data;
// SAFE: if a resource column exists, row 0 exists as well. caller takes ownership of the
// ptr value / drop is called when R is dropped
(!column.is_empty()).then(|| unsafe { column.get_data_unchecked_mut(0) })
james7132 marked this conversation as resolved.
Show resolved Hide resolved
}

/// Gets the [`ComponentTicks`] to a resource, if available.
#[inline]
pub fn get_ticks(&self, component_id: ComponentId) -> Option<&ComponentTicks> {
let column = &self.resources.get(component_id)?.data;
// SAFE: if a resource column exists, row 0 exists as well. caller takes ownership of the
// ptr value / drop is called when R is dropped
james7132 marked this conversation as resolved.
Show resolved Hide resolved
(!column.is_empty()).then(|| unsafe { column.get_ticks_unchecked(0).deref() })
james7132 marked this conversation as resolved.
Show resolved Hide resolved
}

/// Checks if the a resource is currently stored with a given ID.
#[inline]
pub fn contains(&self, component_id: ComponentId) -> bool {
self.resources
.get(component_id)
.map(|info| !info.data.is_empty())
.unwrap_or(false)
}

#[inline]
pub(crate) fn get_with_ticks(
&self,
component_id: ComponentId,
) -> Option<(Ptr<'_>, &UnsafeCell<ComponentTicks>)> {
let column = &self.resources.get(component_id)?.data;
// SAFE: if a resource column exists, row 0 exists as well. caller takes ownership of the
// ptr value / drop is called when R is dropped
(!column.is_empty())
.then(|| unsafe { (column.get_data_unchecked(0), column.get_ticks_unchecked(0)) })
james7132 marked this conversation as resolved.
Show resolved Hide resolved
}

/// Inserts a resource into the world.
///
/// # Safety
/// ptr must point to valid data of this column's component type which
james7132 marked this conversation as resolved.
Show resolved Hide resolved
/// must correspond to the provided ID.
#[inline]
pub unsafe fn insert(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this method needs some comments as it diverges a lot behaviour wise from what is typically "expected" from inserting into a collection. When inserting into a collection I would expect the Option return type to mean whether a component was already present and Some would return the data if it was already present. But here we:

  • return None if component_id doesnt exist in the resources hashmap (which would be reasonable to expect to get added in this function rather than in World::initialize_resource_internal)
  • panic in debug if the resource already exists instead of overwriting it

Copy link
Member Author

@james7132 james7132 Jun 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After realizing that this is pretty badly structured for external consumers of this API, I reworked the overall structure of the API to make this a bit easier to work with. Users will now need to either get_mut or initialize_with a ResourceInfo and then write to it, with ResourceInfo ensuring the invariant of "zero or one" elements is maintained. This has the added benefit of removing pretty much all references of Column related to resources from non-storage code. The safety comment should more accurately reflect what's needed here too.

&mut self,
component_id: ComponentId,
data: OwningPtr<'_>,
ticks: ComponentTicks,
) -> Option<()> {
let column = &mut self.resources.get_mut(component_id)?.data;
debug_assert!(column.is_empty());
column.push(data, ticks);
Some(())
}

/// Removes a resource from the world.
///
/// # Safety
/// ptr must point to valid data of this column's component type which
/// must correspond to the provided ID.
james7132 marked this conversation as resolved.
Show resolved Hide resolved
#[inline]
#[must_use = "The returned pointer to the removed component should be used or dropped"]
pub fn remove(&mut self, component_id: ComponentId) -> Option<(OwningPtr<'_>, ComponentTicks)> {
let column = &mut self.resources.get_mut(component_id)?.data;
if column.is_empty() {
return None;
}
// SAFE: 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 { Some(column.swap_remove_and_forget_unchecked(0)) }
james7132 marked this conversation as resolved.
Show resolved Hide resolved
}

#[inline]
pub(crate) fn remove_and_drop(&mut self, component_id: ComponentId) -> Option<()> {
let column = &mut self.resources.get_mut(component_id)?.data;
if column.is_empty() {
return None;
}
// SAFE: if a resource column exists, row 0 exists as well. The removed value is dropped
// immediately.
unsafe {
column.swap_remove_unchecked(0);
Some(())
}
james7132 marked this conversation as resolved.
Show resolved Hide resolved
}

pub fn check_change_ticks(&mut self, change_tick: u32) {
for info in self.resources.values_mut() {
info.data.check_change_ticks(change_tick);
}
}
}
Loading