mod query;
use core::marker::PhantomData;
use crate::{
action::ActionEncoder, component::Component, entity::EntityId, epoch::EpochId,
query::SendImmutableQuery, world::World, EntityError,
};
pub use self::query::DumpItem;
use self::query::DumpQuery;
#[cfg(feature = "alkahest")]
pub mod alkahest;
#[cfg(feature = "nanoserde")]
pub mod nanoserde;
#[cfg(feature = "serde")]
pub mod serde;
pub struct EntityDump(pub [u64; 3]);
pub trait Dumper<T: DumpSet + ?Sized> {
type Error;
fn dump(&mut self, entity: EntityDump, tuple: T::DumpSlots<'_>) -> Result<(), Self::Error>;
}
pub enum DumpSlot<'a, T> {
Skipped,
Component(&'a T),
}
pub enum LoadSlot<'a, T> {
Skipped,
Missing,
Existing(&'a mut T),
Created(T),
}
pub trait Loader<T: LoadSet + ?Sized> {
type Error;
fn next(&mut self) -> Result<Option<EntityDump>, Self::Error>;
fn load(&mut self, slots: &mut T::LoadSlots<'_>) -> Result<(), Self::Error>;
}
pub trait Mark: Copy + Send + 'static {
fn mark(&self, world: &mut World, id: EntityId);
}
impl<F> Mark for F
where
F: Fn(&mut World, EntityId) + Copy + Send + 'static,
{
#[inline(always)]
fn mark(&self, world: &mut World, id: EntityId) {
self(world, id)
}
}
#[derive(Clone, Copy)]
pub struct NoMark;
impl Mark for NoMark {
#[inline(always)]
fn mark(&self, _: &mut World, _: EntityId) {}
}
pub trait DumpSet {
type DumpSlots<'a>;
fn dump_world<F, D, E>(
world: &World,
filter: F,
after_epoch: EpochId,
dumper: &mut D,
) -> Result<(), E>
where
F: SendImmutableQuery,
D: for<'a> Dumper<Self, Error = E>;
}
pub trait LoadSet {
type LoadSlots<'a>;
fn load_world<L, E, M>(
world: &World,
marker: M,
actions: &mut ActionEncoder,
loader: &mut L,
) -> Result<(), E>
where
L: for<'a> Loader<Self, Error = E>,
M: Mark;
}
pub struct WorldDump<'a, T, F> {
pub world: &'a World,
pub filter: F,
pub epoch: EpochId,
marker: PhantomData<fn() -> T>,
}
impl<'a, T, F> WorldDump<'a, T, F> {
pub fn new(world: &'a World, filter: F, epoch: EpochId) -> Self {
WorldDump {
world,
filter,
epoch,
marker: PhantomData,
}
}
}
pub struct WorldLoad<'a, T, M> {
pub world: &'a World,
pub marker: M,
_marker: PhantomData<fn() -> T>,
}
impl<'a, T, M> WorldLoad<'a, T, M> {
pub fn new(world: &'a World, marker: M) -> Self {
WorldLoad {
world,
marker,
_marker: PhantomData,
}
}
}
macro_rules! set {
() => {
};
($($a:ident)+) => {
#[allow(non_snake_case)]
#[allow(unused_assignments)]
#[allow(unused_parens)]
impl<$($a),+> DumpSet for ($($a,)+)
where
$($a: Sync + 'static,)+
{
type DumpSlots<'a> = ($(DumpSlot<'a, $a>,)+);
#[inline(always)]
fn dump_world<Fi, Du, Er>(world: &World, filter: Fi, after_epoch: EpochId, dumper: &mut Du) -> Result<(), Er>
where
Fi: SendImmutableQuery,
Du: for<'a> Dumper<($($a,)+), Error = Er>,
{
let view = world.view_with(DumpQuery::<($($a,)+)>::new(after_epoch)).filter(filter);
let mut iter = view.iter();
iter.try_for_each(|(e, ($($a),+))| {
let mut present = 0;
let mut modified = 0;
let slots = indexed_tuple!(idx => $(match $a {
DumpItem::Missing => DumpSlot::Skipped,
DumpItem::Modified(comp) => {
modified |= (1 << idx);
DumpSlot::Component(comp)
}
DumpItem::Unmodified => {
present |= (1 << idx);
DumpSlot::Skipped
}
}),+);
let bits = e.id().bits();
dumper.dump(EntityDump([bits, present | modified, modified]), slots)
})
}
}
#[allow(non_snake_case)]
#[allow(unused_assignments)]
#[allow(unused_parens)]
impl<$($a),+> LoadSet for ($($a,)+)
where
$($a: Component + Send + 'static,)+
{
type LoadSlots<'a> = ($(LoadSlot<'a, $a>,)+);
fn load_world<Lo, Er, Ma>(
world: &World,
marker: Ma,
actions: &mut ActionEncoder,
loader: &mut Lo,
) -> Result<(), Er>
where
Lo: for<'a> Loader<($($a,)+), Error = Er>,
Ma: Mark,
{
let mut view = world.view::<($(Option<&mut $a>,)+)>();
while let Some(next) = loader.next()? {
let EntityDump([bits, present, modified]) = next;
let Some(id) = EntityId::from_bits(bits) else {
continue;
};
let mut slots = match view.try_get_mut(id) {
Ok(($($a),+)) => {
indexed_tuple!(idx => $(
if modified & (1 << idx) == 0 {
LoadSlot::Skipped
} else {
match $a {
Some(comp) => LoadSlot::Existing(comp),
None => LoadSlot::Missing,
}
}
),+)
}
Err(EntityError::Mismatch) => unreachable!("Tuple of options is always satisfied"),
Err(EntityError::NoSuchEntity) => {
indexed_tuple!(idx => $(
if modified & (1 << idx) != 0 {
LoadSlot::<$a>::Missing
} else {
LoadSlot::<$a>::Skipped
}
),+)
}
};
loader.load(&mut slots)?;
let ($($a,)+) = slots;
$(
let $a = match $a {
LoadSlot::Skipped => None,
LoadSlot::Missing => {
unreachable!("Must be created by loader");
}
LoadSlot::Existing(_) => None,
LoadSlot::Created(comp) => Some(comp),
};
)+
let marker = marker.clone();
actions.closure(move |world| {
world.spawn_or_insert(id, ());
marker.mark(world, id);
indexed_tuple!(idx => $(match $a {
Some(comp) => { let _ = world.insert(id, comp); }
None => if present & (1 << idx) != 0 {
let _ = world.drop::<$a>(id);
}
}),+);
});
}
Ok(())
}
}
};
}
for_tuple!(set);