Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
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
2 changes: 1 addition & 1 deletion crates/bevy_ecs/src/archetype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ impl Archetype {
},
);
// NOTE: the `table_components` are sorted AND they were inserted in the `Table` in the same
// sorted order, so the index of the `Column` in the `Table` is the same as the index of the
// sorted order, so the index of the `ThinColumn` in the `Table` is the same as the index of the
// component in the `table_components` vector
component_index
.entry(component_id)
Expand Down
113 changes: 104 additions & 9 deletions crates/bevy_ecs/src/storage/blob_array.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
use super::blob_vec::array_layout;
use crate::storage::blob_vec::array_layout_unchecked;
use alloc::alloc::handle_alloc_error;
use bevy_ptr::{OwningPtr, Ptr, PtrMut};
use bevy_utils::OnDrop;
use core::{alloc::Layout, cell::UnsafeCell, num::NonZeroUsize, ptr::NonNull};

/// A flat, type-erased data storage type similar to a [`BlobVec`](super::blob_vec::BlobVec), but with the length and capacity cut out
/// for performance reasons. This type is reliant on its owning type to store the capacity and length information.
/// A flat, type-erased data storage type.
///
/// Used to densely store homogeneous ECS data. A blob is usually just an arbitrary block of contiguous memory without any identity, and
/// could be used to represent any arbitrary data (i.e. string, arrays, etc). This type only stores meta-data about the Blob that it stores,
/// and a pointer to the location of the start of the array, similar to a C array.
/// could be used to represent any arbitrary data (i.e. string, arrays, etc). This type only stores meta-data about the blob that it stores,
/// and a pointer to the location of the start of the array, similar to a C-style `void*` array.
///
/// for performance reasons. This type is reliant on its owning type to store the capacity and length information.
#[derive(Debug)]
pub(super) struct BlobArray {
item_layout: Layout,
// the `data` ptr's layout is always `array_layout(item_layout, capacity)`
Expand Down Expand Up @@ -69,10 +69,17 @@ impl BlobArray {
self.item_layout.size() == 0
}

/// Returns the drop function for values stored in the vector,
/// or `None` if they don't need to be dropped.
#[inline]
pub fn get_drop(&self) -> Option<unsafe fn(OwningPtr<'_>)> {
self.drop
}

/// Returns a reference to the element at `index`, without doing bounds checking.
///
/// *`len` refers to the length of the array, the number of elements that have been initialized, and are safe to read.
/// Just like [`Vec::len`], or [`BlobVec::len`](super::blob_vec::BlobVec::len).*
/// Just like [`Vec::len`].*
///
/// # Safety
/// - The element at index `index` is safe to access.
Expand All @@ -95,7 +102,7 @@ impl BlobArray {
/// Returns a mutable reference to the element at `index`, without doing bounds checking.
///
/// *`len` refers to the length of the array, the number of elements that have been initialized, and are safe to read.
/// Just like [`Vec::len`], or [`BlobVec::len`](super::blob_vec::BlobVec::len).*
/// Just like [`Vec::len`].*
///
/// # Safety
/// - The element with at index `index` is safe to access.
Expand Down Expand Up @@ -133,7 +140,7 @@ impl BlobArray {
/// To get a slice to the entire array, the caller must plug `len` in `slice_len`.
///
/// *`len` refers to the length of the array, the number of elements that have been initialized, and are safe to read.
/// Just like [`Vec::len`], or [`BlobVec::len`](super::blob_vec::BlobVec::len).*
/// Just like [`Vec::len`].*
///
/// # Safety
/// - The type `T` must be the type of the items in this [`BlobArray`].
Expand Down Expand Up @@ -477,6 +484,94 @@ impl BlobArray {
}
}

/// From <https://doc.rust-lang.org/beta/src/core/alloc/layout.rs.html>
pub(super) fn array_layout(layout: &Layout, n: usize) -> Option<Layout> {
let (array_layout, offset) = repeat_layout(layout, n)?;
debug_assert_eq!(layout.size(), offset);
Some(array_layout)
}

// TODO: replace with `Layout::repeat` if/when it stabilizes
/// From <https://doc.rust-lang.org/beta/src/core/alloc/layout.rs.html>
fn repeat_layout(layout: &Layout, n: usize) -> Option<(Layout, usize)> {
// This cannot overflow. Quoting from the invariant of Layout:
// > `size`, when rounded up to the nearest multiple of `align`,
// > must not overflow (i.e., the rounded value must be less than
// > `usize::MAX`)
let padded_size = layout.size() + padding_needed_for(layout, layout.align());
let alloc_size = padded_size.checked_mul(n)?;

// SAFETY: self.align is already known to be valid and alloc_size has been
// padded already.
unsafe {
Some((
Layout::from_size_align_unchecked(alloc_size, layout.align()),
padded_size,
))
}
}

/// From <https://doc.rust-lang.org/beta/src/core/alloc/layout.rs.html>
/// # Safety
/// The caller must ensure that:
/// - The resulting [`Layout`] is valid, by ensuring that `(layout.size() + padding_needed_for(layout, layout.align())) * n` doesn't overflow.
pub(super) unsafe fn array_layout_unchecked(layout: &Layout, n: usize) -> Layout {
let (array_layout, offset) = repeat_layout_unchecked(layout, n);
debug_assert_eq!(layout.size(), offset);
array_layout
}

// TODO: replace with `Layout::repeat` if/when it stabilizes
/// From <https://doc.rust-lang.org/beta/src/core/alloc/layout.rs.html>
/// # Safety
/// The caller must ensure that:
/// - The resulting [`Layout`] is valid, by ensuring that `(layout.size() + padding_needed_for(layout, layout.align())) * n` doesn't overflow.
unsafe fn repeat_layout_unchecked(layout: &Layout, n: usize) -> (Layout, usize) {
// This cannot overflow. Quoting from the invariant of Layout:
// > `size`, when rounded up to the nearest multiple of `align`,
// > must not overflow (i.e., the rounded value must be less than
// > `usize::MAX`)
let padded_size = layout.size() + padding_needed_for(layout, layout.align());
// This may overflow in release builds, that's why this function is unsafe.
let alloc_size = padded_size * n;

// SAFETY: self.align is already known to be valid and alloc_size has been
// padded already.
unsafe {
(
Layout::from_size_align_unchecked(alloc_size, layout.align()),
padded_size,
)
}
}

/// From <https://doc.rust-lang.org/beta/src/core/alloc/layout.rs.html>
const fn padding_needed_for(layout: &Layout, align: usize) -> usize {
let len = layout.size();

// Rounded up value is:
// len_rounded_up = (len + align - 1) & !(align - 1);
// and then we return the padding difference: `len_rounded_up - len`.
//
// We use modular arithmetic throughout:
//
// 1. align is guaranteed to be > 0, so align - 1 is always
// valid.
//
// 2. `len + align - 1` can overflow by at most `align - 1`,
// so the &-mask with `!(align - 1)` will ensure that in the
// case of overflow, `len_rounded_up` will itself be 0.
// Thus the returned padding, when added to `len`, yields 0,
// which trivially satisfies the alignment `align`.
//
// (Of course, attempts to allocate blocks of memory whose
// size and padding overflow in the above manner should cause
// the allocator to yield an error anyway.)

let len_rounded_up = len.wrapping_add(align).wrapping_sub(1) & !align.wrapping_sub(1);
len_rounded_up.wrapping_sub(len)
}

#[cfg(test)]
mod tests {
use bevy_ecs::prelude::*;
Expand Down
Loading
Loading