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

Document bevy_ecs::storage #7770

Merged
merged 13 commits into from
Mar 9, 2023
39 changes: 38 additions & 1 deletion crates/bevy_ecs/src/storage/blob_vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,21 @@ impl std::fmt::Debug for BlobVec {
}

impl BlobVec {
james7132 marked this conversation as resolved.
Show resolved Hide resolved
/// Creates a new [`BlobVec`] with the specified `capacity`.
james7132 marked this conversation as resolved.
Show resolved Hide resolved
///
/// `drop` is an optional function pointer that is meant to be invoked when any element in the [`BlobVec`]
/// should be dropped. For all Rust-based types, this should match 1:1 with the implementation of [`Drop`]
/// if present, and should be `None` if `T: !Drop`. For non-Rust based types, this should match any cleanup
/// processes typically associated with the stored
james7132 marked this conversation as resolved.
Show resolved Hide resolved
///
/// # Safety
///
/// `drop` should be safe to call with an [`OwningPtr`] pointing to any item that's been pushed into this [`BlobVec`].
///
/// If `drop` is `None`, the items will be leaked. This should generally be set as None based on [`needs_drop`].
///
/// [`needs_drop`]: core::mem::needs_drop
/// [`Drop`]: core::mem::Drop
pub unsafe fn new(
item_layout: Layout,
drop: Option<unsafe fn(OwningPtr<'_>)>,
Expand Down Expand Up @@ -70,26 +78,37 @@ impl BlobVec {
}
}

/// Gets the number of elements currently stored in the [`BlobVec`].
james7132 marked this conversation as resolved.
Show resolved Hide resolved
#[inline]
pub fn len(&self) -> usize {
self.len
}

/// Checks if the [`BlobVec`] is currently empty.
james7132 marked this conversation as resolved.
Show resolved Hide resolved
#[inline]
pub fn is_empty(&self) -> bool {
self.len == 0
}

/// Gets the maximum possible number of elements the [`BlobVec`] can store without
/// reallocating its underlying buffer.
james7132 marked this conversation as resolved.
Show resolved Hide resolved
#[inline]
pub fn capacity(&self) -> usize {
self.capacity
}

/// Fetches the [`Layout`] of the underlying type stored within the [`BlobVec`].
james7132 marked this conversation as resolved.
Show resolved Hide resolved
#[inline]
pub fn layout(&self) -> Layout {
self.item_layout
}

/// Reserves the minimum capacity for at least `additional` more elements to be inserted in the given `BlobVec`.
/// After calling `reserve_exact`, capacity will be greater than or equal to `self.len() + additional`. Does nothing if
/// the capacity is already sufficient.
///
/// Note that the allocator may give the collection more space than it requests. Therefore, capacity can not be relied upon
/// to be precisely minimal.
pub fn reserve_exact(&mut self, additional: usize) {
let available_space = self.capacity - self.len;
if available_space < additional && self.item_layout.size() > 0 {
Expand Down Expand Up @@ -134,6 +153,8 @@ impl BlobVec {
self.capacity = new_capacity;
}

/// Initializes the value at `index` to `value`. This function does not do any bounds checking.
///
/// # Safety
/// - index must be in bounds
/// - the memory in the [`BlobVec`] starting at index `index`, of a size matching this [`BlobVec`]'s
Expand All @@ -145,6 +166,8 @@ impl BlobVec {
std::ptr::copy_nonoverlapping::<u8>(value.as_ptr(), ptr.as_ptr(), self.item_layout.size());
}

/// Replaces the value at `index` with `value`. This function does not do any bounds checking.
///
/// # Safety
/// - index must be in-bounds
/// - the memory in the [`BlobVec`] starting at index `index`, of a size matching this
Expand Down Expand Up @@ -201,7 +224,7 @@ impl BlobVec {
std::ptr::copy_nonoverlapping::<u8>(source, destination.as_ptr(), self.item_layout.size());
}

/// Pushes a value to the [`BlobVec`].
/// Appends a value to the back of the [`BlobVec`].
///
/// # Safety
/// `value` must be valid to add to this [`BlobVec`]
james7132 marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -213,6 +236,8 @@ impl BlobVec {
self.initialize_unchecked(index, value);
}

/// Forces the length of the vector to `len`.
///
/// # Safety
/// `len` must be <= `capacity`. if length is decreased, "out of bounds" items must be dropped.
/// Newly added items must be immediately populated with valid values and length must be
Expand Down Expand Up @@ -255,6 +280,7 @@ impl BlobVec {

/// Removes the value at `index` and copies the value stored into `ptr`.
/// Does not do any bounds checking on `index`.
/// The removed element is replaced by the last element of the `BlobVec`.
Copy link
Contributor

Choose a reason for hiding this comment

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

Good addition!

///
/// # Safety
/// It is the caller's responsibility to ensure that `index` is < `self.len()`
Expand All @@ -274,6 +300,10 @@ impl BlobVec {
self.len -= 1;
}

/// Removes the value at `index` and drops it.
/// Does not do any bounds checking on `index`.
/// The removed element is replaced by the last element of the `BlobVec`.
///
/// # Safety
/// It is the caller's responsibility to ensure that `index` is < self.len()
#[inline]
Expand All @@ -286,6 +316,9 @@ impl BlobVec {
}
}

/// Fetches a read-only reference to the value at `index`.
/// Does not do any bounds checking on `index`.
///
/// # Safety
/// It is the caller's responsibility to ensure that `index` is < self.len()
james7132 marked this conversation as resolved.
Show resolved Hide resolved
#[inline]
Expand All @@ -300,6 +333,9 @@ impl BlobVec {
self.get_ptr().byte_add(index * size)
}

/// Fetches a mutable reference to the value at `index`.
/// Does not do any bounds checking on `index`.
///
/// # Safety
/// It is the caller's responsibility to ensure that `index` is < self.len()
james7132 marked this conversation as resolved.
Show resolved Hide resolved
#[inline]
Expand Down Expand Up @@ -337,6 +373,7 @@ impl BlobVec {
std::slice::from_raw_parts(self.data.as_ptr() as *const UnsafeCell<T>, self.len)
}

/// Clears the [`BlobVec`] by removing and dropping all of its elements.
james7132 marked this conversation as resolved.
Show resolved Hide resolved
pub fn clear(&mut self) {
let len = self.len;
// We set len to 0 _before_ dropping elements for unwind safety. This ensures we don't
Expand Down
24 changes: 24 additions & 0 deletions crates/bevy_ecs/src/storage/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,24 @@
//! Storage layouts for ECS data.
//!
//! This module contains the low-level backing data stores for ECS. These all offer minimal and often
james7132 marked this conversation as resolved.
Show resolved Hide resolved
//! unsafe APIs, exposed primarily for debugging and monitoring purposes.
Copy link
Contributor

@Carter0 Carter0 Feb 21, 2023

Choose a reason for hiding this comment

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

I like that you mentioned it was for "debugging and monitoring purposes". You clarified a question I had before I even thought to ask it.

james7132 marked this conversation as resolved.
Show resolved Hide resolved
//!
//! # Fetching Storages
//! Each of the primary backing data stores can be fetched via [`Storages`], which can be fetched from a
james7132 marked this conversation as resolved.
Show resolved Hide resolved
//! [`World`] via [`World::storages`]. It exposes a top level container for each class of storage within
//! ECS:
//!
//! - [`Tables`] - columnar contiguous blocks of memory, optimized for fast iteration.
//! - [`SparseSets`] - sparse `HashMap`-like mappings from entities to components, optimized for random
//! lookup and regular insertion/removal of components.
//! - [`Resources`] - singleton storage for the resources in the world
//!
//! # Safety
//! To avoid trivially unsound use of the APIs in this module, it is explicitly impossible to get a mutable
//! reference to [`Storages`] from [`World`], and none of the types publicly expose a mutable interface.
//!
//! [`World`]: crate::world::World
//! [`World::storages`]: crate::world::World::storages

mod blob_vec;
mod resource;
Expand All @@ -12,8 +32,12 @@ pub use table::*;
/// The raw data stores of a [World](crate::world::World)
#[derive(Default)]
pub struct Storages {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it a common pattern in Bevy to want to have one world use one kind of storage and then another world use another? In my game I am inserting components one frame and then removing them in another, so I would want to use SparseSets instead of tables for that.

Copy link
Member Author

Choose a reason for hiding this comment

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

The storage type for a component is meant to be universal, defined on the Component type itself. This is managed during Component registration. Though someone could make an incorrect Component registration on different worlds. It's ill advised though.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ahh, yes ofc! That's much simpler

/// Backing storage for [`SparseSet`] components.
pub sparse_sets: SparseSets,
/// Backing storage for [`Table`] components.
pub tables: Tables,
/// Backing storage for resources.
pub resources: Resources<true>,
/// Backing storage for `!Send` resources.
pub non_send_resources: Resources<false>,
}
15 changes: 13 additions & 2 deletions crates/bevy_ecs/src/storage/resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ impl<const SEND: bool> ResourceData<SEND> {
/// The only row in the underlying column.
const ROW: TableRow = TableRow::new(0);

/// Validates the access to `!Send` resources is only done on the thread they were created from.
///
/// # Panics
/// If `SEND` is false, this will panic if called from a different thread than the one it was inserted from.
#[inline]
fn validate_access(&self) {
if SEND {
Expand Down Expand Up @@ -70,7 +74,7 @@ impl<const SEND: bool> ResourceData<SEND> {
self.id
}

/// Gets a read-only pointer to the underlying resource, if available.
/// Gets a read-only pointer to the underlying resource, if present.
james7132 marked this conversation as resolved.
Show resolved Hide resolved
///
/// # Panics
/// If `SEND` is false, this will panic if a value is present and is not accessed from the
Expand All @@ -83,12 +87,14 @@ impl<const SEND: bool> ResourceData<SEND> {
})
}

/// Gets a read-only reference to the change ticks of the underlying resource, if available.
/// Gets a read-only reference to the change ticks of the underlying resource, if present.
james7132 marked this conversation as resolved.
Show resolved Hide resolved
#[inline]
pub fn get_ticks(&self) -> Option<ComponentTicks> {
self.column.get_ticks(Self::ROW)
}

/// Gets a read-only reference and the change detection ticks for the resource, if present.
james7132 marked this conversation as resolved.
Show resolved Hide resolved
///
/// # Panics
/// If `SEND` is false, this will panic if a value is present and is not accessed from the
/// original thread it was inserted in.
Expand All @@ -100,6 +106,11 @@ impl<const SEND: bool> ResourceData<SEND> {
})
}

/// Gets a mutable reference for the resource, if present.
james7132 marked this conversation as resolved.
Show resolved Hide resolved
///
/// # Panics
/// If `SEND` is false, this will panic if a value is present and is not accessed from the
/// original thread it was inserted in.
pub(crate) fn get_mut(
&mut self,
last_change_tick: u32,
Expand Down
Loading