use super::{create_archetype, Archetype, Chunk, MAX_COMPONENTS};
use crate::ecs::{component_type, ComponentType};
use rustc_hash::FxHashMap;
use smallvec::{smallvec, SmallVec};
use std::{any::TypeId, cell::RefCell, ptr, sync::RwLock};
lazy_static::lazy_static! {
static ref BUNDLE_META: RwLock<FxHashMap<TypeId, &'static BundleMeta>> = RwLock::new(FxHashMap::default());
}
thread_local! {
static LOCAL_BUNDLE_META: RefCell<FxHashMap<TypeId, &'static BundleMeta>> = RefCell::new(FxHashMap::default());
}
pub(crate) struct BundleMeta {
pub archetype: Archetype,
pub columns: SmallVec<[(usize, usize); MAX_COMPONENTS]>,
}
mod sealed {
pub trait BundleSealed {}
}
fn assert_unique_types(types: &[ComponentType]) {
for (index, ty) in types.iter().enumerate() {
for other in &types[(index + 1)..] {
if ty.id() == other.id() {
panic!(
"duplicate component type `{}` is not supported in bundles",
ty.name
);
}
}
}
}
fn bundle_meta<B: 'static>(
make_types: impl FnOnce() -> SmallVec<[ComponentType; MAX_COMPONENTS]>,
) -> &'static BundleMeta {
let type_id = TypeId::of::<B>();
if let Some(meta) = LOCAL_BUNDLE_META.with(|cache| cache.borrow().get(&type_id).copied()) {
return meta;
}
if let Some(meta) = BUNDLE_META.read().unwrap().get(&type_id).copied() {
LOCAL_BUNDLE_META.with(|cache| {
cache.borrow_mut().insert(type_id, meta);
});
return meta;
}
let mut cache = BUNDLE_META.write().unwrap();
if let Some(meta) = cache.get(&type_id).copied() {
LOCAL_BUNDLE_META.with(|cache| {
cache.borrow_mut().insert(type_id, meta);
});
return meta;
}
let types = make_types();
assert_unique_types(&types);
let mut builder = create_archetype();
for ty in &types {
builder = builder.add_component(*ty);
}
let archetype = builder.build();
let columns = types
.iter()
.map(|ty| {
let component_index = archetype.query_component_index(ty).unwrap();
(component_index, ty.size)
})
.collect();
let meta = Box::leak(Box::new(BundleMeta { archetype, columns }));
let meta = *cache.entry(type_id).or_insert(meta);
LOCAL_BUNDLE_META.with(|cache| {
cache.borrow_mut().insert(type_id, meta);
});
meta
}
pub trait Bundle: sealed::BundleSealed + 'static {
fn cached_meta() -> (Archetype, &'static [(usize, usize)]);
fn archetype() -> Archetype;
unsafe fn write(self, chunk: &mut Chunk, entity_index: usize);
unsafe fn write_fast(self, chunk: &mut Chunk, entity_index: usize, columns: &[(usize, usize)]);
}
macro_rules! impl_bundle_tuple {
($(($Type:ident, $value:ident, $idx:tt)),+ $(,)?) => {
impl<$($Type: 'static),+> sealed::BundleSealed for ($($Type,)+) {}
impl<$($Type: 'static),+> Bundle for ($($Type,)+) {
fn cached_meta() -> (Archetype, &'static [(usize, usize)]) {
let meta = bundle_meta::<Self>(|| smallvec![$(component_type::<$Type>()),+]);
(meta.archetype, &meta.columns)
}
fn archetype() -> Archetype {
Self::cached_meta().0
}
unsafe fn write(self, chunk: &mut Chunk, entity_index: usize) {
let (_, columns) = Self::cached_meta();
self.write_fast(chunk, entity_index, columns);
}
unsafe fn write_fast(self, chunk: &mut Chunk, entity_index: usize, columns: &[(usize, usize)]) {
let ($($value,)+) = self;
$(
let (component_index, comp_size) = columns[$idx];
let col_ptr = chunk.column_ptr(component_index);
ptr::write(
col_ptr.add(comp_size * entity_index) as *mut $Type,
$value,
);
)+
}
}
};
}
impl_bundle_tuple!((A, a, 0));
impl_bundle_tuple!((A, a, 0), (B, b, 1));
impl_bundle_tuple!((A, a, 0), (B, b, 1), (C, c, 2));
impl_bundle_tuple!((A, a, 0), (B, b, 1), (C, c, 2), (D, d, 3));
impl_bundle_tuple!((A, a, 0), (B, b, 1), (C, c, 2), (D, d, 3), (E, e, 4));
impl_bundle_tuple!(
(A, a, 0),
(B, b, 1),
(C, c, 2),
(D, d, 3),
(E, e, 4),
(F, f, 5)
);
impl_bundle_tuple!(
(A, a, 0),
(B, b, 1),
(C, c, 2),
(D, d, 3),
(E, e, 4),
(F, f, 5),
(G, g, 6)
);
impl_bundle_tuple!(
(A, a, 0),
(B, b, 1),
(C, c, 2),
(D, d, 3),
(E, e, 4),
(F, f, 5),
(G, g, 6),
(H, h, 7)
);