use rustc_hash::FxHashMap;
use std::cell::Cell;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::ops::Deref;
use std::ptr;
use std::sync::RwLock;
use smallvec::SmallVec;
use crate::ecs::{component_type, ComponentType};
pub const MAX_COMPONENTS: usize = 32;
lazy_static::lazy_static! {
static ref ARCHETYPE_CACHE: RwLock<FxHashMap<Vec<usize>, Archetype>> = RwLock::new(FxHashMap::default());
}
thread_local! {
static LAST_COMPONENT_LOOKUP: Cell<Option<(usize, usize, u8)>> = const { Cell::new(None) };
}
#[derive(Debug)]
pub struct InternalArchetype {
pub components: SmallVec<[ComponentType; MAX_COMPONENTS]>,
pub(crate) drop_component_indices: SmallVec<[u8; MAX_COMPONENTS]>,
pub alignment: usize,
}
impl InternalArchetype {
fn new() -> Self {
InternalArchetype {
components: SmallVec::new(),
drop_component_indices: SmallVec::new(),
alignment: 1,
}
}
fn add_component(mut self, ty: ComponentType) -> Self {
self.alignment = self.alignment.max(ty.align);
self.components.push(ty);
self
}
fn build(mut self) -> Self {
self.components.sort_by_key(|a| a.id());
for window in self.components.windows(2) {
if window[0].id() == window[1].id() {
panic!(
"duplicate component type `{}` is not supported in archetypes",
window[0].name
);
}
}
self.drop_component_indices.clear();
for (index, component) in self.components.iter().enumerate() {
if component.needs_drop() {
debug_assert!(index < u8::MAX as usize);
self.drop_component_indices.push(index as u8);
}
}
self
}
}
impl InternalArchetype {
#[inline(always)]
pub fn has_component(&self, ty: &ComponentType) -> bool {
self.query_component_index(ty).is_some()
}
#[inline(always)]
pub fn query_component_index(&self, ty: &ComponentType) -> Option<usize> {
let archetype_id = self as *const InternalArchetype as usize;
let component_id = ty.id();
if let Some((cached_archetype_id, cached_component_id, cached_index)) =
LAST_COMPONENT_LOOKUP.with(Cell::get)
{
if cached_archetype_id == archetype_id && cached_component_id == component_id {
return Some(cached_index as usize);
}
}
let index = self
.components
.binary_search_by(|component| component.id().cmp(&ty.id()))
.ok()?;
debug_assert!(index < u8::MAX as usize);
LAST_COMPONENT_LOOKUP.with(|cache| {
cache.set(Some((archetype_id, component_id, index as u8)));
});
Some(index)
}
}
impl fmt::Display for InternalArchetype {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Archetype[")?;
for (i, component) in self.components.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", component.name)?;
}
write!(f, "]")
}
}
impl PartialEq for Archetype {
fn eq(&self, other: &Self) -> bool {
ptr::eq(self.archetype, other.archetype)
}
}
impl Eq for Archetype {}
impl Hash for Archetype {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_usize(self.id());
}
}
#[derive(Debug, Clone, Copy)]
pub struct Archetype {
archetype: &'static InternalArchetype,
}
impl Archetype {
fn new(archetype: &'static InternalArchetype) -> Self {
Archetype { archetype }
}
#[inline(always)]
pub fn id(&self) -> usize {
self.archetype as *const InternalArchetype as usize
}
}
impl Deref for Archetype {
type Target = InternalArchetype;
fn deref(&self) -> &Self::Target {
self.archetype
}
}
pub struct ArchetypeBuilder {
internal_archetype: InternalArchetype,
}
impl ArchetypeBuilder {
fn new() -> Self {
ArchetypeBuilder {
internal_archetype: InternalArchetype::new(),
}
}
pub fn add_component(mut self, ty: ComponentType) -> Self {
self.internal_archetype = self.internal_archetype.add_component(ty);
self
}
pub fn add_rust_component<T: 'static>(self) -> Self {
self.add_component(component_type::<T>())
}
pub fn build(self) -> Archetype {
let built = self.internal_archetype.build();
let key = built
.components
.iter()
.map(|component| component.id())
.collect::<Vec<_>>();
if let Some(archetype) = ARCHETYPE_CACHE.read().unwrap().get(&key).copied() {
return archetype;
}
let mut cache = ARCHETYPE_CACHE.write().unwrap();
if let Some(archetype) = cache.get(&key).copied() {
return archetype;
}
let archetype = Archetype::new(Box::leak(Box::new(built)));
cache.insert(key, archetype);
archetype
}
}
pub fn create_archetype() -> ArchetypeBuilder {
ArchetypeBuilder::new()
}
#[cfg(test)]
mod tests {
use super::create_archetype;
use std::sync::{Arc, Barrier};
use std::thread;
#[derive(Clone, Copy)]
#[allow(dead_code)]
struct Position(f32);
#[derive(Clone, Copy)]
#[allow(dead_code)]
struct Velocity(f32);
#[test]
#[should_panic(expected = "duplicate component type")]
fn archetype_builder_rejects_duplicate_component_types() {
let _ = create_archetype()
.add_rust_component::<Position>()
.add_rust_component::<Position>()
.build();
}
#[test]
fn concurrent_archetype_builds_reuse_cached_instance() {
let thread_count = 16usize;
let barrier = Arc::new(Barrier::new(thread_count));
let mut handles = Vec::with_capacity(thread_count);
for _ in 0..thread_count {
let barrier = barrier.clone();
handles.push(thread::spawn(move || {
barrier.wait();
create_archetype()
.add_rust_component::<Position>()
.add_rust_component::<Velocity>()
.build()
.id()
}));
}
let ids = handles
.into_iter()
.map(|handle| handle.join().expect("archetype build thread panicked"))
.collect::<Vec<_>>();
assert!(ids.windows(2).all(|window| window[0] == window[1]));
}
}