use std::any::{TypeId, type_name};
use std::fmt;
use crate::EcsError;
use crate::archetype::Archetype;
use crate::component::{Column, ColumnData, Component};
mod sealed {
pub trait Sealed {}
}
use sealed::Sealed;
pub trait Bundle: Sealed + Send + Sync + 'static {
#[doc(hidden)]
const COUNT: usize;
#[doc(hidden)]
fn describe(info: &mut BundleInfo);
#[doc(hidden)]
fn write(self, row: &mut BundleRow<'_>);
}
type Entry = (TypeId, &'static str, fn() -> Box<dyn Column>);
#[doc(hidden)]
#[derive(Default)]
pub struct BundleInfo {
components: Vec<Entry>,
}
impl fmt::Debug for BundleInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list()
.entries(self.components.iter().map(|(_, name, _)| name))
.finish()
}
}
impl BundleInfo {
pub(crate) fn of<B: Bundle>(entities: usize) -> Result<Self, EcsError> {
let mut info = Self::default();
info.components
.try_reserve_exact(B::COUNT)
.map_err(|_| EcsError::OutOfMemory { entities })?;
B::describe(&mut info);
info.components.sort_unstable_by_key(|(ty, _, _)| *ty);
let duplicate = info.components.windows(2).find_map(|pair| match pair {
[(a, name, _), (b, _, _)] if a == b => Some(*name),
_ => None,
});
match duplicate {
Some(component) => Err(EcsError::DuplicateComponent { component }),
None => Ok(info),
}
}
pub(crate) fn components(&self) -> impl Iterator<Item = (TypeId, fn() -> Box<dyn Column>)> {
self.components.iter().map(|(ty, _, new)| (*ty, *new))
}
pub(crate) fn push<T: Component>(&mut self) {
self.components
.push((TypeId::of::<T>(), type_name::<T>(), new_column::<T>));
}
}
pub(crate) fn new_column<T: Component>() -> Box<dyn Column> {
Box::new(ColumnData::<T>::default())
}
#[doc(hidden)]
pub struct BundleRow<'a> {
table: &'a mut Archetype,
row: usize,
tick: u64,
}
impl fmt::Debug for BundleRow<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("BundleRow")
.field("row", &self.row)
.finish_non_exhaustive()
}
}
impl<'a> BundleRow<'a> {
pub(crate) const fn new(table: &'a mut Archetype, row: usize, tick: u64) -> Self {
Self { table, row, tick }
}
pub(crate) fn put<T: Component>(&mut self, value: T) {
let written = self.table.put_component(self.row, value, self.tick);
debug_assert!(written, "the table has a column of every bundle type");
}
}
macro_rules! count {
() => { 0 };
($head:ident $($tail:ident)*) => { 1 + count!($($tail)*) };
}
macro_rules! bundle {
($($name:ident $value:ident),*) => {
impl<$($name: Component),*> Sealed for ($($name,)*) {}
impl<$($name: Component),*> Bundle for ($($name,)*) {
#[allow(clippy::arithmetic_side_effects, reason = "a constant of at most 12")]
const COUNT: usize = count!($($name)*);
#[allow(unused_variables)]
fn describe(info: &mut BundleInfo) {
$(info.push::<$name>();)*
}
#[allow(unused_variables)]
fn write(self, row: &mut BundleRow<'_>) {
let ($($value,)*) = self;
$(row.put($value);)*
}
}
};
}
bundle!();
bundle!(B0 b0);
bundle!(B0 b0, B1 b1);
bundle!(B0 b0, B1 b1, B2 b2);
bundle!(B0 b0, B1 b1, B2 b2, B3 b3);
bundle!(B0 b0, B1 b1, B2 b2, B3 b3, B4 b4);
bundle!(B0 b0, B1 b1, B2 b2, B3 b3, B4 b4, B5 b5);
bundle!(B0 b0, B1 b1, B2 b2, B3 b3, B4 b4, B5 b5, B6 b6);
bundle!(B0 b0, B1 b1, B2 b2, B3 b3, B4 b4, B5 b5, B6 b6, B7 b7);
bundle!(B0 b0, B1 b1, B2 b2, B3 b3, B4 b4, B5 b5, B6 b6, B7 b7, B8 b8);
bundle!(B0 b0, B1 b1, B2 b2, B3 b3, B4 b4, B5 b5, B6 b6, B7 b7, B8 b8, B9 b9);
bundle!(B0 b0, B1 b1, B2 b2, B3 b3, B4 b4, B5 b5, B6 b6, B7 b7, B8 b8, B9 b9, B10 b10);
bundle!(B0 b0, B1 b1, B2 b2, B3 b3, B4 b4, B5 b5, B6 b6, B7 b7, B8 b8, B9 b9, B10 b10, B11 b11);
#[cfg(test)]
mod tests {
use super::*;
struct A;
struct B;
#[test]
fn types_are_sorted_and_duplicates_rejected() {
let info = BundleInfo::of::<(B, A, u8)>(0).unwrap();
let mut expected = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<u8>()];
expected.sort_unstable();
assert_eq!(
info.components().map(|(ty, _)| ty).collect::<Vec<_>>(),
expected
);
assert!(format!("{info:?}").contains("u8"));
assert_eq!(<(A, B, u8)>::COUNT, 3);
assert_eq!(<()>::COUNT, 0);
let duplicate = BundleInfo::of::<(A, u8, A)>(0).map(|_| ());
assert_eq!(
duplicate,
Err(EcsError::DuplicateComponent {
component: type_name::<A>()
})
);
}
}