use super::pool::{
ChunkBlock, CHUNK_BLOCK_POOL, ENTITY_BUFFER_NON_ZST_MAX_OVERSIZE,
ENTITY_BUFFER_ZST_MAX_OVERSIZE,
};
use super::{
Archetype, ChunkLayout, EntityId, CHUNK_SIZE, MAX_COMPONENTS, SMALL_CHUNK_SIZE, TINY_CHUNK_SIZE,
};
use std::alloc::Layout;
use std::mem;
use std::ptr::{self, NonNull};
pub struct Chunk {
pub entity_count: usize,
pub max_entity_count: usize,
pub archetype: Archetype,
data: NonNull<u8>,
alloc_layout: Layout,
column_offsets: [u32; MAX_COMPONENTS],
pub(super) entities: Vec<EntityId>,
}
impl Chunk {
#[inline]
fn is_zero_sized_archetype(archetype: Archetype) -> bool {
archetype
.components
.iter()
.all(|component| component.size == 0)
}
fn aligned_dangling(alignment: usize) -> NonNull<u8> {
debug_assert!(alignment != 0 && alignment.is_power_of_two());
unsafe { NonNull::new_unchecked(ptr::without_provenance_mut(alignment)) }
}
fn take_block(size: usize, alignment: usize) -> ChunkBlock {
CHUNK_BLOCK_POOL
.try_with(|pool| pool.try_borrow_mut().ok()?.acquire(size, alignment))
.ok()
.flatten()
.unwrap_or_else(|| ChunkBlock::fresh(size, alignment))
}
fn take_entities(preferred_capacity: usize, zero_sized_archetype: bool) -> Vec<EntityId> {
let max_oversize_factor = if zero_sized_archetype {
ENTITY_BUFFER_ZST_MAX_OVERSIZE
} else {
ENTITY_BUFFER_NON_ZST_MAX_OVERSIZE
};
let pooled = CHUNK_BLOCK_POOL
.try_with(|pool| {
pool.try_borrow_mut().ok()?.acquire_entities(
preferred_capacity,
zero_sized_archetype,
max_oversize_factor,
)
})
.ok()
.flatten();
pooled.unwrap_or_else(|| {
if zero_sized_archetype {
Vec::new()
} else {
Vec::with_capacity(preferred_capacity)
}
})
}
pub fn new(archetype: Archetype) -> Self {
let layout = ChunkLayout::for_archetype(archetype, CHUNK_SIZE);
Self::new_with_layout(archetype, &layout)
}
pub(crate) fn new_with_layout(archetype: Archetype, layout: &ChunkLayout) -> Self {
let alignment = archetype.alignment.max(1);
let zero_sized_archetype = Self::is_zero_sized_archetype(archetype);
let (data, alloc_layout) = if zero_sized_archetype {
(
Self::aligned_dangling(alignment),
Layout::from_size_align(layout.chunk_size(), alignment).unwrap(),
)
} else {
Self::take_block(layout.chunk_size(), alignment).into_raw_parts()
};
let column_offsets = ChunkLayout::compute_column_offsets(
archetype,
layout.max_entity_count(),
layout.chunk_size(),
)
.expect("cached chunk layout must fit its archetype");
let entities = Self::take_entities(layout.max_entity_count(), zero_sized_archetype);
Self {
entity_count: 0,
max_entity_count: layout.max_entity_count(),
archetype,
data,
alloc_layout,
column_offsets,
entities,
}
}
pub fn is_full(&self) -> bool {
self.entity_count == self.max_entity_count
}
pub fn is_empty(&self) -> bool {
self.entity_count == 0
}
#[inline(always)]
pub(crate) fn block_size(&self) -> usize {
self.alloc_layout.size()
}
pub(super) fn promote_tiny(&mut self, layout: &ChunkLayout) {
debug_assert_eq!(self.block_size(), TINY_CHUNK_SIZE);
debug_assert_eq!(layout.chunk_size(), SMALL_CHUNK_SIZE);
debug_assert!(layout.max_entity_count() >= self.entity_count);
if Self::is_zero_sized_archetype(self.archetype) {
self.max_entity_count = layout.max_entity_count();
self.alloc_layout =
Layout::from_size_align(layout.chunk_size(), self.archetype.alignment.max(1))
.unwrap();
self.column_offsets = ChunkLayout::compute_column_offsets(
self.archetype,
layout.max_entity_count(),
layout.chunk_size(),
)
.expect("cached zero-sized chunk layout must fit its archetype");
return;
}
let entity_count = self.entity_count;
let mut replacement = Self::new_with_layout(self.archetype, layout);
for (component_index, component) in self.archetype.components.iter().enumerate() {
if component.size == 0 {
continue;
}
unsafe {
ptr::copy_nonoverlapping(
self.column_ptr(component_index),
replacement.column_ptr(component_index),
component.size * entity_count,
);
}
}
replacement.entities.extend_from_slice(self.entities());
replacement.entity_count = entity_count;
self.entity_count = 0;
self.entities.clear();
*self = replacement;
}
#[inline(always)]
pub unsafe fn add_entity(&mut self, entity: EntityId) -> Option<usize> {
if self.entity_count == self.max_entity_count {
return None;
}
let entity_index = self.entity_count;
self.entity_count += 1;
self.entities.push(entity);
Some(entity_index)
}
#[inline(always)]
pub fn column_ptr(&self, component_index: usize) -> *mut u8 {
unsafe {
self.data
.as_ptr()
.add(self.column_offsets[component_index] as usize)
}
}
pub fn data_ptr(&self) -> *mut u8 {
self.data.as_ptr()
}
#[inline(always)]
unsafe fn component_ptr_unchecked(
&self,
component_index: usize,
entity_index: usize,
) -> *mut u8 {
let component = &self.archetype.components[component_index];
let column = self.column_ptr(component_index);
unsafe { column.add(entity_index * component.size) }
}
#[inline(always)]
pub fn component_ptr(&self, component_index: usize, entity_index: usize) -> *mut u8 {
if entity_index >= self.entity_count {
return ptr::null_mut();
}
unsafe { self.component_ptr_unchecked(component_index, entity_index) }
}
pub(crate) fn entities(&self) -> &[EntityId] {
&self.entities[..self.entity_count]
}
pub fn get_entity_as_ptr(&self, index: usize) -> *const u8 {
if self.archetype.components.is_empty() {
return ptr::null();
}
self.component_ptr(0, index) as *const u8
}
pub fn entity_id(&self, entity_index: usize) -> Option<EntityId> {
self.entities.get(entity_index).copied()
}
#[inline(always)]
pub(crate) fn copy_entity_within(&mut self, src_index: usize, dst_index: usize) {
if src_index == dst_index {
return;
}
for (component_index, component) in self.archetype.components.iter().enumerate() {
if component.size == 0 {
continue;
}
unsafe {
ptr::copy_nonoverlapping(
self.component_ptr_unchecked(component_index, src_index),
self.component_ptr_unchecked(component_index, dst_index),
component.size,
);
}
}
self.entities[dst_index] = self.entities[src_index];
}
#[inline(always)]
pub(crate) fn copy_entity_from(&mut self, src: &Chunk, src_index: usize, dst_index: usize) {
debug_assert_eq!(self.archetype.id(), src.archetype.id());
for (component_index, component) in self.archetype.components.iter().enumerate() {
if component.size == 0 {
continue;
}
unsafe {
ptr::copy_nonoverlapping(
src.component_ptr_unchecked(component_index, src_index),
self.component_ptr_unchecked(component_index, dst_index),
component.size,
);
}
}
self.entities[dst_index] = src.entities[src_index];
}
#[inline(always)]
pub(crate) fn remove_last_entity(&mut self) -> Option<EntityId> {
if self.entity_count == 0 {
return None;
}
self.entity_count -= 1;
self.entities.pop()
}
unsafe fn drop_all_entities_catching(&self) -> Option<Box<dyn std::any::Any + Send + 'static>> {
let mut first_panic = None;
for entity_index in 0..self.entity_count {
for &component_index in &self.archetype.drop_component_indices {
let component_index = component_index as usize;
let component = &self.archetype.components[component_index];
let drop_fn = component
.drop_fn()
.expect("drop component index must point to a droppable component");
let ptr = unsafe { self.component_ptr_unchecked(component_index, entity_index) };
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| unsafe {
drop_fn(ptr);
}));
if let Err(payload) = result {
if first_panic.is_none() {
first_panic = Some(payload);
} else {
mem::forget(payload);
}
}
}
}
first_panic
}
}
impl Drop for Chunk {
fn drop(&mut self) {
let drop_panic = unsafe { self.drop_all_entities_catching() };
self.entity_count = 0;
let mut block = (!Self::is_zero_sized_archetype(self.archetype)).then(|| ChunkBlock {
data: self.data,
alloc_layout: self.alloc_layout,
});
self.entities.clear();
let mut entities = Some(mem::take(&mut self.entities));
let _ = CHUNK_BLOCK_POOL.try_with(|pool| {
let Ok(mut pool) = pool.try_borrow_mut() else {
return;
};
if let Some(block) = block.take() {
pool.release(block);
}
pool.release_entities(entities.take().unwrap());
});
drop(block);
drop(entities);
if let Some(payload) = drop_panic {
std::panic::resume_unwind(payload);
}
}
}