use std::{convert::TryInto, fmt, mem::MaybeUninit, ptr};
use crate::engine::error::{AttributeError, AttributeInvariantViolation, PositionOutOfBoundsError};
use crate::engine::storage::PushFromOutcome;
use crate::engine::types::{ChunkID, RowID, CHUNK_CAP};
pub struct Attribute<T> {
pub(crate) chunks: Vec<Box<[MaybeUninit<T>; CHUNK_CAP]>>,
pub(crate) last_chunk_length: usize, pub(crate) length: usize,
pub(crate) spare_chunk: Option<Box<[MaybeUninit<T>; CHUNK_CAP]>>,
}
impl<T: 'static + Send + Sync> fmt::Debug for Attribute<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Attribute")
.field("length", &self.length)
.field("chunk_count", &self.chunks.len())
.field("last_chunk_length", &self.last_chunk_length)
.finish()
}
}
impl<T> Attribute<T> {
pub fn chunk_count(&self) -> usize {
self.chunks.len()
}
#[inline]
fn ensure_last_chunk(&mut self) {
if self.chunks.is_empty() || self.last_chunk_length == CHUNK_CAP {
let chunk = self.spare_chunk.take().unwrap_or_else(|| {
let mut chunk = Vec::with_capacity(CHUNK_CAP);
chunk.resize_with(CHUNK_CAP, MaybeUninit::<T>::uninit);
chunk
.into_boxed_slice()
.try_into()
.map_err(|_| ())
.expect("chunk length is fixed to CHUNK_CAP")
});
self.chunks.push(chunk);
self.last_chunk_length = 0;
}
}
#[inline]
pub(crate) fn valid_position(&self, chunk: ChunkID, row: RowID) -> bool {
let chunk = chunk as usize;
let row = row as usize;
if chunk >= self.chunk_count() {
return false;
}
if chunk + 1 == self.chunk_count() {
row < self.last_chunk_length
} else {
row < CHUNK_CAP
}
}
fn position_error(&self, chunk: ChunkID, row: RowID) -> AttributeError {
AttributeError::Position(PositionOutOfBoundsError {
chunk,
row,
chunks: self.chunks.len(),
capacity: CHUNK_CAP,
last_chunk_length: self.last_chunk_length,
})
}
fn next_push_position(&self) -> Result<(ChunkID, RowID), AttributeError> {
let chunk: ChunkID = (self.length / CHUNK_CAP)
.try_into()
.map_err(|_| AttributeError::IndexOverflow("ChunkID"))?;
let row: RowID = (self.length % CHUNK_CAP)
.try_into()
.map_err(|_| AttributeError::IndexOverflow("RowID"))?;
Ok((chunk, row))
}
#[inline]
pub(crate) unsafe fn get_slot_unchecked(
&mut self,
chunk: usize,
row: usize,
) -> &mut MaybeUninit<T> {
debug_assert!(chunk < self.chunk_count());
debug_assert!(row < CHUNK_CAP);
&mut self.chunks[chunk][row]
}
pub fn reserve_chunks(&mut self, additional: usize) {
self.chunks.reserve(additional);
}
fn fixup_after_length_decrement(&mut self) {
if self.length == 0 {
if self.spare_chunk.is_none() {
self.spare_chunk = self.chunks.pop();
}
self.chunks.clear();
self.last_chunk_length = 0;
} else {
let new_last_chunk = (self.length - 1) / CHUNK_CAP;
let new_last_row = (self.length - 1) % CHUNK_CAP;
while self.chunks.len() - 1 > new_last_chunk {
let popped = self.chunks.pop();
if self.spare_chunk.is_none() {
self.spare_chunk = popped;
}
}
self.last_chunk_length = new_last_row + 1;
}
}
pub fn get(&self, chunk: ChunkID, row: RowID) -> Option<&T> {
if !self.valid_position(chunk, row) {
return None;
}
Some(unsafe { self.chunks[chunk as usize][row as usize].assume_init_ref() })
}
pub fn get_mut(&mut self, chunk: ChunkID, row: RowID) -> Option<&mut T> {
if !self.valid_position(chunk, row) {
return None;
}
Some(unsafe { self.chunks[chunk as usize][row as usize].assume_init_mut() })
}
pub fn iter(&self) -> impl Iterator<Item = &T> {
let last_chunk_length = self.last_chunk_length;
let chunk_count = self.chunks.len();
self.chunks.iter().enumerate().flat_map(move |(i, chunk)| {
let initialized = if chunk_count == 0 {
0
} else if i == chunk_count - 1 {
last_chunk_length
} else {
CHUNK_CAP
};
chunk[..initialized]
.iter()
.map(|mu| unsafe { mu.assume_init_ref() })
})
}
pub fn push(&mut self, value: T) -> Result<(ChunkID, RowID), AttributeError> {
self.ensure_last_chunk();
let chunk_index = self.chunks.len() - 1;
let row_index = self.last_chunk_length;
let chunk_id: ChunkID = chunk_index
.try_into()
.map_err(|_| AttributeError::IndexOverflow("ChunkID"))?;
let row_id: RowID = row_index
.try_into()
.map_err(|_| AttributeError::IndexOverflow("RowID"))?;
unsafe {
self.get_slot_unchecked(chunk_index, row_index)
.as_mut_ptr()
.write(value);
}
self.last_chunk_length += 1;
self.length += 1;
Ok((chunk_id, row_id))
}
pub fn swap_remove(
&mut self,
chunk: ChunkID,
row: RowID,
) -> Result<Option<(ChunkID, RowID)>, AttributeError> {
if !self.valid_position(chunk, row) {
return Err(self.position_error(chunk, row));
}
let last_index = self.length - 1;
let last_chunk = last_index / CHUNK_CAP;
let last_row = last_index % CHUNK_CAP;
let is_last = (chunk as usize == last_chunk) && (row as usize == last_row);
if is_last {
unsafe {
self.get_slot_unchecked(chunk as usize, row as usize)
.assume_init_drop();
}
} else {
unsafe {
let last_value = ptr::read(self.get_slot_unchecked(last_chunk, last_row).as_ptr());
self.get_slot_unchecked(chunk as usize, row as usize)
.assume_init_drop();
ptr::write(
self.get_slot_unchecked(chunk as usize, row as usize)
.as_mut_ptr(),
last_value,
);
*self.get_slot_unchecked(last_chunk, last_row) = MaybeUninit::uninit();
}
}
let mut moved_from: Option<(ChunkID, RowID)> = None;
if !is_last {
moved_from = Some((
last_chunk
.try_into()
.map_err(|_| AttributeError::IndexOverflow("chunk"))?,
last_row
.try_into()
.map_err(|_| AttributeError::IndexOverflow("row"))?,
));
}
self.length -= 1;
self.fixup_after_length_decrement();
Ok(moved_from)
}
pub(crate) fn take_swap_remove(
&mut self,
chunk: ChunkID,
row: RowID,
) -> Result<(T, Option<(ChunkID, RowID)>), AttributeError> {
if !self.valid_position(chunk, row) {
return Err(self.position_error(chunk, row));
}
let last_index = self.length - 1;
let last_chunk = last_index / CHUNK_CAP;
let last_row = last_index % CHUNK_CAP;
let is_last = (chunk as usize == last_chunk) && (row as usize == last_row);
let removed = unsafe {
ptr::read(
self.get_slot_unchecked(chunk as usize, row as usize)
.as_ptr(),
)
};
let moved_from = if is_last {
unsafe {
*self.get_slot_unchecked(chunk as usize, row as usize) = MaybeUninit::uninit();
}
None
} else {
let last_value =
unsafe { ptr::read(self.get_slot_unchecked(last_chunk, last_row).as_ptr()) };
unsafe {
*self.get_slot_unchecked(last_chunk, last_row) = MaybeUninit::uninit();
ptr::write(
self.get_slot_unchecked(chunk as usize, row as usize)
.as_mut_ptr(),
last_value,
);
}
Some((
last_chunk
.try_into()
.map_err(|_| AttributeError::IndexOverflow("chunk"))?,
last_row
.try_into()
.map_err(|_| AttributeError::IndexOverflow("row"))?,
))
};
self.length -= 1;
self.fixup_after_length_decrement();
Ok((removed, moved_from))
}
pub(crate) fn restore_swap_removed(
&mut self,
chunk: ChunkID,
row: RowID,
value: T,
moved_from: Option<(ChunkID, RowID)>,
) -> Result<(), AttributeError> {
match moved_from {
Some(expected_append) => {
if self.next_push_position()? != expected_append {
return Err(AttributeError::InternalInvariant(
AttributeInvariantViolation::LengthMismatch,
));
}
if !self.valid_position(chunk, row) {
return Err(self.position_error(chunk, row));
}
let displaced = unsafe {
ptr::read(
self.get_slot_unchecked(chunk as usize, row as usize)
.as_ptr(),
)
};
unsafe {
ptr::write(
self.get_slot_unchecked(chunk as usize, row as usize)
.as_mut_ptr(),
value,
);
}
let pos = self.push(displaced)?;
debug_assert_eq!(pos, expected_append);
Ok(())
}
None => {
if self.next_push_position()? != (chunk, row) {
return Err(AttributeError::InternalInvariant(
AttributeInvariantViolation::LengthMismatch,
));
}
let pos = self.push(value)?;
debug_assert_eq!(pos, (chunk, row));
Ok(())
}
}
}
pub(crate) fn pop_last_at(&mut self, expected: (ChunkID, RowID)) -> Result<T, AttributeError> {
if self.length == 0 {
return Err(AttributeError::InternalInvariant(
AttributeInvariantViolation::SwapRemoveOnEmpty,
));
}
let last_index = self.length - 1;
let last_chunk = last_index / CHUNK_CAP;
let last_row = last_index % CHUNK_CAP;
let actual = (
last_chunk
.try_into()
.map_err(|_| AttributeError::IndexOverflow("chunk"))?,
last_row
.try_into()
.map_err(|_| AttributeError::IndexOverflow("row"))?,
);
if actual != expected {
return Err(AttributeError::InternalInvariant(
AttributeInvariantViolation::LengthMismatch,
));
}
let value = unsafe { ptr::read(self.get_slot_unchecked(last_chunk, last_row).as_ptr()) };
unsafe {
*self.get_slot_unchecked(last_chunk, last_row) = MaybeUninit::uninit();
}
self.length -= 1;
self.fixup_after_length_decrement();
Ok(value)
}
pub fn push_from(
&mut self,
source: &mut Attribute<T>,
source_chunk: ChunkID,
source_row: RowID,
) -> Result<PushFromOutcome, AttributeError> {
let source_chunk_count = source.chunks.len();
if !source.valid_position(source_chunk, source_row) {
return Err(AttributeError::Position(PositionOutOfBoundsError {
chunk: source_chunk,
row: source_row,
chunks: source_chunk_count,
capacity: CHUNK_CAP,
last_chunk_length: source.last_chunk_length,
}));
}
let moved_value = unsafe {
let value = ptr::read(
source
.get_slot_unchecked(source_chunk as usize, source_row as usize)
.as_ptr(),
);
*source.get_slot_unchecked(source_chunk as usize, source_row as usize) =
MaybeUninit::uninit();
value
};
let (destination_chunk, destination_row) = match self.push(moved_value) {
Ok(pos) => pos,
Err(e) => {
let hole_index = source_chunk as usize * CHUNK_CAP + source_row as usize;
let last_index = source.length - 1;
if hole_index != last_index {
let last_chunk = last_index / CHUNK_CAP;
let last_row = last_index % CHUNK_CAP;
unsafe {
let last_value =
ptr::read(source.get_slot_unchecked(last_chunk, last_row).as_ptr());
*source.get_slot_unchecked(last_chunk, last_row) = MaybeUninit::uninit();
ptr::write(
source
.get_slot_unchecked(source_chunk as usize, source_row as usize)
.as_mut_ptr(),
last_value,
);
}
}
source.length -= 1;
source.fixup_after_length_decrement();
return Err(e);
}
};
let last_index = source.length - 1;
let last_chunk = last_index / CHUNK_CAP;
let last_row = last_index % CHUNK_CAP;
let mut moved_from_source: Option<(ChunkID, RowID)> = None;
let source_index = source_chunk as usize * CHUNK_CAP + source_row as usize;
if source_index != last_index {
let last_value = unsafe {
let value = ptr::read(source.get_slot_unchecked(last_chunk, last_row).as_ptr());
*source.get_slot_unchecked(last_chunk, last_row) = MaybeUninit::uninit();
value
};
moved_from_source = Some((
last_chunk
.try_into()
.map_err(|_| AttributeError::IndexOverflow("chunk"))?,
last_row
.try_into()
.map_err(|_| AttributeError::IndexOverflow("row"))?,
));
unsafe {
ptr::write(
source
.get_slot_unchecked(source_chunk as usize, source_row as usize)
.as_mut_ptr(),
last_value,
);
}
}
source.length -= 1;
source.fixup_after_length_decrement();
Ok(((destination_chunk, destination_row), moved_from_source))
}
pub fn extend<I: IntoIterator<Item = T>>(&mut self, iterator: I) -> Result<(), AttributeError> {
for v in iterator {
self.push(v)?;
}
Ok(())
}
pub fn extend_from_vec(
&mut self,
mut values: Vec<T>,
) -> Result<(usize, usize), AttributeError> {
let count = values.len();
let start = self.length;
if count == 0 {
return Ok((start, 0));
}
let last_index = start + count - 1;
let _: ChunkID = (last_index / CHUNK_CAP)
.try_into()
.map_err(|_| AttributeError::IndexOverflow("ChunkID"))?;
let needed_chunks = (start + count).div_ceil(CHUNK_CAP);
self.chunks
.reserve(needed_chunks.saturating_sub(self.chunks.len()));
let source = values.as_ptr();
let mut copied = 0usize;
while copied < count {
self.ensure_last_chunk();
let chunk_index = self.chunks.len() - 1;
let row = self.last_chunk_length;
let run = (CHUNK_CAP - row).min(count - copied);
unsafe {
let destination = self.chunks[chunk_index].as_mut_ptr().add(row) as *mut T;
ptr::copy_nonoverlapping(source.add(copied), destination, run);
}
self.last_chunk_length += run;
self.length += run;
copied += run;
}
unsafe {
values.set_len(0);
}
Ok((start, count))
}
pub(crate) fn extend_from_rows(
&mut self,
source: &Attribute<T>,
rows: &[(ChunkID, RowID)],
) -> Result<(usize, usize), AttributeError> {
let count = rows.len();
let start = self.length;
if count == 0 {
return Ok((start, 0));
}
let last_index = start + count - 1;
let _: ChunkID = (last_index / CHUNK_CAP)
.try_into()
.map_err(|_| AttributeError::IndexOverflow("ChunkID"))?;
for &(chunk, row) in rows {
if !source.valid_position(chunk, row) {
return Err(source.position_error(chunk, row));
}
}
let needed_chunks = (start + count).div_ceil(CHUNK_CAP);
self.chunks
.reserve(needed_chunks.saturating_sub(self.chunks.len()));
for &(chunk, row) in rows {
self.ensure_last_chunk();
let chunk_index = self.chunks.len() - 1;
let row_index = self.last_chunk_length;
unsafe {
let src = source.chunks[chunk as usize].as_ptr().add(row as usize) as *const T;
let dst = self.chunks[chunk_index].as_mut_ptr().add(row_index) as *mut T;
ptr::copy_nonoverlapping(src, dst, 1);
}
self.last_chunk_length += 1;
self.length += 1;
}
Ok((start, count))
}
pub(crate) fn extend_permuted_from_vec(
&mut self,
mut values: Vec<T>,
order: &[usize],
) -> Result<(usize, usize), AttributeError> {
if order.len() != values.len() {
return Err(AttributeError::InternalInvariant(
AttributeInvariantViolation::LengthMismatch,
));
}
let count = order.len();
let start = self.length;
if count == 0 {
return Ok((start, 0));
}
let last_index = start + count - 1;
let _: ChunkID = (last_index / CHUNK_CAP)
.try_into()
.map_err(|_| AttributeError::IndexOverflow("ChunkID"))?;
for &index in order {
if index >= count {
return Err(AttributeError::InternalInvariant(
AttributeInvariantViolation::LengthMismatch,
));
}
}
#[cfg(debug_assertions)]
{
let mut seen = vec![false; count];
for &index in order {
debug_assert!(!seen[index], "order must not repeat indices");
seen[index] = true;
}
}
let needed_chunks = (start + count).div_ceil(CHUNK_CAP);
self.chunks
.reserve(needed_chunks.saturating_sub(self.chunks.len()));
let base = values.as_ptr();
for &index in order {
self.ensure_last_chunk();
let chunk_index = self.chunks.len() - 1;
let row_index = self.last_chunk_length;
unsafe {
let dst = self.chunks[chunk_index].as_mut_ptr().add(row_index) as *mut T;
ptr::copy_nonoverlapping(base.add(index), dst, 1);
}
self.last_chunk_length += 1;
self.length += 1;
}
unsafe {
values.set_len(0);
}
Ok((start, count))
}
pub(crate) fn truncate_forgotten(&mut self, new_length: usize) -> Result<(), AttributeError> {
if new_length > self.length {
return Err(AttributeError::InternalInvariant(
AttributeInvariantViolation::LengthMismatch,
));
}
if new_length == self.length {
return Ok(());
}
self.length = new_length;
self.fixup_after_length_decrement();
Ok(())
}
pub(crate) fn swap_remove_forgotten(
&mut self,
chunk: ChunkID,
row: RowID,
) -> Result<Option<(ChunkID, RowID)>, AttributeError> {
if !self.valid_position(chunk, row) {
return Err(self.position_error(chunk, row));
}
let last_index = self.length - 1;
let last_chunk = last_index / CHUNK_CAP;
let last_row = last_index % CHUNK_CAP;
let is_last = (chunk as usize == last_chunk) && (row as usize == last_row);
let moved_from = if is_last {
unsafe {
*self.get_slot_unchecked(chunk as usize, row as usize) = MaybeUninit::uninit();
}
None
} else {
unsafe {
let last_value = ptr::read(self.get_slot_unchecked(last_chunk, last_row).as_ptr());
ptr::write(
self.get_slot_unchecked(chunk as usize, row as usize)
.as_mut_ptr(),
last_value,
);
*self.get_slot_unchecked(last_chunk, last_row) = MaybeUninit::uninit();
}
Some((
last_chunk
.try_into()
.map_err(|_| AttributeError::IndexOverflow("chunk"))?,
last_row
.try_into()
.map_err(|_| AttributeError::IndexOverflow("row"))?,
))
};
self.length -= 1;
self.fixup_after_length_decrement();
Ok(moved_from)
}
pub(crate) fn truncate_to(&mut self, new_length: usize) -> Result<(), AttributeError> {
if new_length > self.length {
return Err(AttributeError::InternalInvariant(
AttributeInvariantViolation::LengthMismatch,
));
}
if new_length == self.length {
return Ok(());
}
for index in new_length..self.length {
let chunk = index / CHUNK_CAP;
let row = index % CHUNK_CAP;
unsafe {
self.get_slot_unchecked(chunk, row).assume_init_drop();
}
}
self.length = new_length;
self.fixup_after_length_decrement();
Ok(())
}
fn drop_all_initialized_elements(&mut self) {
if self.length == 0 {
return;
}
let mut remaining = self.length;
let chunk_count = self.chunks.len();
let last_chunk_len = self.last_chunk_length;
for (chunk_idx, chunk) in self.chunks.iter_mut().enumerate() {
let init_in_chunk = if chunk_idx + 1 == chunk_count {
last_chunk_len
} else {
CHUNK_CAP
};
let to_drop = init_in_chunk.min(remaining);
for i in 0..to_drop {
unsafe {
chunk[i].assume_init_drop();
}
}
if remaining <= init_in_chunk {
break;
}
remaining -= init_in_chunk;
}
}
pub fn clear(&mut self) {
if self.length == 0 {
return;
}
self.drop_all_initialized_elements();
self.chunks.clear();
self.spare_chunk = None;
self.length = 0;
self.last_chunk_length = 0;
}
}
impl<T> Default for Attribute<T> {
fn default() -> Self {
Self {
chunks: Vec::new(),
last_chunk_length: 0,
length: 0,
spare_chunk: None,
}
}
}
impl<T> Drop for Attribute<T> {
fn drop(&mut self) {
self.drop_all_initialized_elements();
}
}