use crate::engine::types::{ChunkID, ComponentID, RowID, ShardID, CHUNK_CAP};
use std::any::Any;
use crate::engine::commands::BatchColumn;
use crate::engine::component::DynamicBundle;
use crate::engine::entity::{Entity, EntityLocation, EntityShards};
use crate::engine::error::{
AttributeError, ECSError, ECSResult, InternalViolation, SpawnError, StaleEntityError,
TypeMismatchError,
};
use crate::engine::storage::LockedAttribute;
use super::core::Archetype;
impl Archetype {
pub fn spawn_on(
&mut self,
shards: &EntityShards,
shard_id: ShardID,
mut bundle: impl DynamicBundle,
) -> ECSResult<Entity> {
let mut pending_values: Vec<(usize, Box<dyn Any>)> =
Vec::with_capacity(self.components.len());
for idx in 0..self.components.len() {
let (component_id, ref attr) = self.components[idx];
let guard = Self::lock_write_spawn(attr)?;
let type_id = guard.as_ref().element_type_id();
let name = guard.as_ref().element_type_name();
let Some(value) = bundle.take(component_id) else {
return Err(SpawnError::MissingComponent { type_id, name }.into());
};
let actual = value.as_ref().type_id();
if actual != type_id {
return Err(
SpawnError::StoragePushFailedWith(AttributeError::TypeMismatch(
TypeMismatchError {
expected: type_id,
actual,
expected_name: name,
actual_name: "",
},
))
.into(),
);
}
let value: Box<dyn Any> = value;
pending_values.push((idx, value));
}
let mut written_positions: Vec<(usize, ChunkID, RowID)> = Vec::new();
let mut reference_position: Option<(ChunkID, RowID)> = None;
for (idx, value) in pending_values {
let (_component_id, ref attr) = self.components[idx];
let mut guard = Self::lock_write_spawn(attr)?;
let position = match guard.as_mut().push_dyn(value) {
Ok(p) => p,
Err(e) => {
Self::rollback_written_positions(&self.components, &written_positions);
return Err(SpawnError::StoragePushFailedWith(e).into());
}
};
if let Some(rp) = reference_position {
if position != rp {
written_positions.push((idx, position.0, position.1));
Self::rollback_written_positions(&self.components, &written_positions);
return Err(SpawnError::MisalignedStorage {
expected: rp,
got: position,
}
.into());
}
} else {
reference_position = Some(position);
}
written_positions.push((idx, position.0, position.1));
}
let Some((chunk, row)) = reference_position else {
return Err(SpawnError::EmptyArchetype.into());
};
{
let mut meta = self
.meta
.write()
.map_err(|_| ECSError::from(InternalViolation::ArchetypeMetaLockPoisoned))?;
Self::ensure_capacity(&mut meta, chunk as usize + 1);
if meta.entity_positions[chunk as usize][row as usize] != Entity::PLACEHOLDER {
drop(meta);
Self::rollback_written_positions(&self.components, &written_positions);
return Err(InternalViolation::SpawnSlotOccupied.into());
}
}
let location = EntityLocation {
archetype: self.archetype_id,
chunk,
row,
};
let entity = shards.spawn_on(shard_id, location).map_err(|e| {
Self::rollback_written_positions(&self.components, &written_positions);
ECSError::from(e)
})?;
{
let mut meta = self
.meta
.write()
.map_err(|_| ECSError::from(InternalViolation::ArchetypeMetaLockPoisoned))?;
meta.entity_positions[chunk as usize][row as usize] = entity;
meta.length += 1;
}
Ok(entity)
}
pub fn despawn_on(&mut self, shards: &EntityShards, entity: Entity) -> ECSResult<()> {
let Some(location) = shards.get_location(entity)? else {
return Err(SpawnError::StaleEntity(StaleEntityError).into());
};
if location.archetype != self.archetype_id {
return Err(InternalViolation::DespawnEntityNotInArchetype.into());
}
let entity_chunk = location.chunk;
let entity_row = location.row;
let mut moved_from: Option<(ChunkID, RowID)> = None;
for (_, attr) in self.components.iter() {
let mut guard = Self::lock_write_spawn(attr)?;
let pos = guard
.as_mut()
.swap_remove_dyn(entity_chunk, entity_row)
.map_err(SpawnError::StorageSwapRemoveFailed)?;
if let Some(expected) = moved_from {
if pos != Some(expected) {
return Err(InternalViolation::DespawnSwapMisalignment.into());
}
} else {
moved_from = pos;
}
}
let ok = shards.despawn(entity)?;
if !ok {
return Err(SpawnError::StaleEntity(StaleEntityError).into());
}
{
let mut meta = self
.meta
.write()
.map_err(|_| ECSError::from(InternalViolation::ArchetypeMetaLockPoisoned))?;
Self::ensure_capacity(&mut meta, entity_chunk as usize + 1);
if let Some((moved_chunk, moved_row)) = moved_from {
Self::ensure_capacity(&mut meta, moved_chunk as usize + 1);
let moved_entity = meta.entity_positions[moved_chunk as usize][moved_row as usize];
if moved_entity == Entity::PLACEHOLDER {
return Err(InternalViolation::DespawnMovedSlotMissingEntity.into());
}
meta.entity_positions[entity_chunk as usize][entity_row as usize] = moved_entity;
shards
.set_location(
moved_entity,
EntityLocation {
archetype: self.archetype_id,
chunk: entity_chunk,
row: entity_row,
},
)
.map_err(ECSError::from)?;
meta.entity_positions[moved_chunk as usize][moved_row as usize] =
Entity::PLACEHOLDER;
} else {
meta.entity_positions[entity_chunk as usize][entity_row as usize] =
Entity::PLACEHOLDER;
}
meta.length = meta.length.saturating_sub(1);
if meta.length == 0 {
meta.entity_positions.clear();
}
}
Ok(())
}
fn rollback_written_positions(
components: &[(ComponentID, LockedAttribute)],
positions: &[(usize, ChunkID, RowID)],
) {
for &(j, chunk, row) in positions.iter().rev() {
let (_, ref a) = components[j];
if let Ok(mut g) = Self::lock_write_spawn(a) {
let _ = g.as_mut().swap_remove_dyn(chunk, row);
}
}
}
pub(crate) fn append_batch_columns(
&self,
start: usize,
count: usize,
mut columns: Vec<BatchColumn>,
) -> ECSResult<()> {
columns.sort_by_key(|column| column.component_id);
let mut expected = self.signature.iterate_over_components();
for column in &columns {
match expected.next() {
Some(required) if required == column.component_id => {}
_ => {
return Err(SpawnError::BatchColumnSet {
component_id: column.component_id,
}
.into());
}
}
if column.len != count {
return Err(SpawnError::BatchColumnMismatch {
component_id: column.component_id,
expected: count,
actual: column.len,
}
.into());
}
}
if let Some(missing) = expected.next() {
return Err(SpawnError::BatchColumnSet {
component_id: missing,
}
.into());
}
let mut appended: Vec<ComponentID> = Vec::with_capacity(columns.len());
for column in columns {
let component_id = column.component_id;
let attr = self
.find_component(component_id)
.ok_or(SpawnError::BatchColumnSet { component_id })?;
let result = Self::lock_write_spawn(attr)
.map_err(ECSError::from)
.and_then(|mut guard| {
guard
.extend_from_vec_any(column.values)
.map_err(|e| ECSError::from(SpawnError::StoragePushFailedWith(e)))
});
match result {
Ok((appended_start, appended_count))
if appended_start == start && appended_count == count =>
{
appended.push(component_id);
}
Ok((appended_start, appended_count)) => {
appended.push(component_id);
self.truncate_columns_to(&appended, start);
let (expected, actual) = if appended_start != start {
(start, appended_start)
} else {
(count, appended_count)
};
return Err(SpawnError::BatchColumnMismatch {
component_id,
expected,
actual,
}
.into());
}
Err(error) => {
self.truncate_columns_to(&appended, start);
return Err(error);
}
}
}
Ok(())
}
pub(crate) fn truncate_columns_to(&self, component_ids: &[ComponentID], length: usize) {
for &component_id in component_ids {
if let Some(attr) = self.find_component(component_id) {
if let Ok(mut guard) = attr.write() {
let _ = guard.truncate_to(length);
}
}
}
}
pub(crate) fn commit_batch_rows(&self, start: usize, entities: &[Entity]) -> ECSResult<()> {
let mut meta = self
.meta
.write()
.map_err(|_| ECSError::from(InternalViolation::ArchetypeMetaLockPoisoned))?;
let end = start + entities.len();
let last_chunk = if end == 0 { 0 } else { (end - 1) / CHUNK_CAP };
Self::ensure_capacity(&mut meta, last_chunk + 1);
for (offset, &entity) in entities.iter().enumerate() {
let index = start + offset;
let chunk = index / CHUNK_CAP;
let row = index % CHUNK_CAP;
if meta.entity_positions[chunk][row] != Entity::PLACEHOLDER {
for cleared in 0..offset {
let cleared_index = start + cleared;
meta.entity_positions[cleared_index / CHUNK_CAP][cleared_index % CHUNK_CAP] =
Entity::PLACEHOLDER;
}
return Err(InternalViolation::SpawnSlotOccupied.into());
}
meta.entity_positions[chunk][row] = entity;
}
meta.length += entities.len();
Ok(())
}
pub(crate) fn despawn_rows_batch(
&mut self,
shards: &EntityShards,
targets: &[(Entity, ChunkID, RowID)],
) -> ECSResult<()> {
if targets.is_empty() {
return Ok(());
}
let mut guards = Vec::with_capacity(self.components.len());
for (component_id, attr) in self.components.iter() {
let guard = Self::lock_write_spawn(attr).map_err(ECSError::from)?;
guards.push((*component_id, guard));
}
let mut meta = self
.meta
.write()
.map_err(|_| ECSError::from(InternalViolation::ArchetypeMetaLockPoisoned))?;
let mut pending_moves: Vec<(Entity, EntityLocation)> = Vec::new();
for &(_entity, chunk, row) in targets {
let mut moved_from: Option<(ChunkID, RowID)> = None;
let mut first = true;
for (_, guard) in guards.iter_mut() {
let pos = guard
.as_mut()
.swap_remove_dyn(chunk, row)
.map_err(|e| ECSError::from(SpawnError::StorageSwapRemoveFailed(e)))?;
if first {
moved_from = pos;
first = false;
} else if pos != moved_from {
return Err(InternalViolation::DespawnSwapMisalignment.into());
}
}
Self::ensure_capacity(&mut meta, chunk as usize + 1);
if let Some((moved_chunk, moved_row)) = moved_from {
let moved_entity = meta.entity_positions[moved_chunk as usize][moved_row as usize];
if moved_entity == Entity::PLACEHOLDER {
return Err(InternalViolation::DespawnMovedSlotMissingEntity.into());
}
meta.entity_positions[chunk as usize][row as usize] = moved_entity;
pending_moves.push((
moved_entity,
EntityLocation {
archetype: self.archetype_id,
chunk,
row,
},
));
meta.entity_positions[moved_chunk as usize][moved_row as usize] =
Entity::PLACEHOLDER;
} else {
meta.entity_positions[chunk as usize][row as usize] = Entity::PLACEHOLDER;
}
meta.length = meta.length.saturating_sub(1);
}
if meta.length == 0 {
meta.entity_positions.clear();
}
shards
.set_locations_grouped(&pending_moves)
.map_err(ECSError::from)?;
let despawn_targets: Vec<Entity> = targets.iter().map(|&(entity, _, _)| entity).collect();
shards
.despawn_grouped(&despawn_targets)
.map_err(ECSError::from)?;
Ok(())
}
}