Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Determinism for Serialization #364

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ row-serialize = ["serde"]
[dependencies]
hecs-macros = { path = "macros", version = "0.10.0", optional = true }
hashbrown = { version = "0.14", default-features = false, features = ["ahash", "inline-more"] }
serde = { version = "1.0.117", default-features = false, optional = true }
serde = { version = "1.0.117", default-features = false, features = ["alloc"], optional = true }
spin = { version = "0.9.8", default-features = false, features = ["mutex", "spin_mutex", "lazy"] }

[dev-dependencies]
Expand Down
74 changes: 69 additions & 5 deletions src/entities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@ impl<'de> serde::Deserialize<'de> for Entity {
}
}

#[derive(Debug)]
pub enum PendingPushError {
MetaTooSmall,
EntityNotPending
}

/// An iterator returning a sequence of Entity values from `Entities::reserve_entities`.
pub struct ReserveEntitiesIterator<'a> {
// Metas, so we can recover the current generation for anything in the freelist.
Expand Down Expand Up @@ -147,6 +153,15 @@ impl<'a> Iterator for ReserveEntitiesIterator<'a> {

impl<'a> ExactSizeIterator for ReserveEntitiesIterator<'a> {}

/// Represents a list of free/pending entities.
///
/// `pending` contains a list of ids. Entities left (index <=) of the free_cursor
/// are free and entities right of the free_cursor are pending (reserved).
pub struct Freelist<'a> {
pub pending: &'a [u32],
pub free_cursor: isize,
}

#[derive(Default)]
pub(crate) struct Entities {
pub meta: Vec<EntityMeta>,
Expand Down Expand Up @@ -192,6 +207,13 @@ pub(crate) struct Entities {
}

impl Entities {
pub fn freelist(&self) -> Freelist {
Freelist {
pending: &self.pending,
free_cursor: self.free_cursor.load(Ordering::Relaxed),
}
}

/// Reserve entity IDs concurrently
///
/// Storage for entity generation and location is lazily allocated by calling `flush`.
Expand Down Expand Up @@ -343,10 +365,7 @@ impl Entities {
self.verify_flushed();

let loc = if entity.id as usize >= self.meta.len() {
self.pending.extend((self.meta.len() as u32)..entity.id);
let new_free_cursor = self.pending.len() as isize;
*self.free_cursor.get_mut() = new_free_cursor;
self.meta.resize(entity.id as usize + 1, EntityMeta::EMPTY);
self.extend_meta((entity.id + 1) as usize, false);
self.len += 1;
None
} else if let Some(index) = self.pending.iter().position(|item| *item == entity.id) {
Expand Down Expand Up @@ -494,7 +513,7 @@ impl Entities {
}
}

fn needs_flush(&mut self) -> bool {
pub fn needs_flush(&mut self) -> bool {
*self.free_cursor.get_mut() != self.pending.len() as isize
}

Expand Down Expand Up @@ -525,6 +544,51 @@ impl Entities {
}
}

fn extend_meta(&mut self, new_len: usize, include_last: bool) {
let upper_limit = if include_last {
new_len as u32
} else {
new_len as u32 - 1
};

self.pending.extend((self.meta.len() as u32)..upper_limit);
let new_free_cursor = self.pending.len() as isize;
*self.free_cursor.get_mut() = new_free_cursor;
self.meta.resize(new_len, EntityMeta::EMPTY);
}

pub fn push_generations(&mut self, generations: &[u32]) {
if self.meta.len() < generations.len() {
self.extend_meta(generations.len(), true);
}

for (index, meta) in self.meta.iter_mut().enumerate() {
meta.generation = NonZeroU32::new(generations[index]).unwrap();
}
}

pub fn push_freelist(&mut self, freelist: Freelist)-> Result<(), PendingPushError> {
self.pending.clear();

let meta_length = self.meta.len();

for pending in freelist.pending {
if meta_length <= *pending as usize {
return Result::Err(PendingPushError::MetaTooSmall);
}

if self.meta[*pending as usize].location.index != u32::MAX {
return Result::Err(PendingPushError::EntityNotPending);
}

self.pending.push(*pending);
}

*self.free_cursor.get_mut() = freelist.free_cursor;

return Result::Ok(());
}

#[inline]
pub fn len(&self) -> u32 {
self.len
Expand Down
94 changes: 76 additions & 18 deletions src/serialize/column.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
//! archetype is a 4-tuple of an entity count `n`, component count `k`, a `k`-tuple of
//! user-controlled component IDs, and a `k+1`-tuple of `n`-tuples of components, such that the
//! first `n`-tuple contains `Entity` values and the remainder each contain components of the type
//! identified by the corresponding component ID.
//! identified by the corresponding component ID. In order to remain deterministic,
//! we additionally store the generations of all entities and the freelist.
//! The generations is a sequence of u32s where each one is the generation of the entity at that index
//! in the entity list. The freelist stores the free_cursor as an i64 and the list of pending/free entities.

use crate::alloc::vec::Vec;
use core::{any::type_name, cell::RefCell, fmt, marker::PhantomData};
Expand All @@ -23,7 +26,7 @@ use serde::{
};

use crate::{
Archetype, ColumnBatch, ColumnBatchBuilder, ColumnBatchType, Component, Entity, Query, World,
Archetype, ColumnBatch, ColumnBatchBuilder, ColumnBatchType, Component, Entity, Query, World, entities::Freelist,
};

/// Implements serialization of archetypes
Expand Down Expand Up @@ -291,14 +294,22 @@ where
}

let predicate = |x: &&Archetype| -> bool { !x.is_empty() && x.satisfies::<Q>() };
let mut seq = serializer.serialize_seq(Some(world.archetypes().filter(predicate).count()))?;
let mut seq = serializer.serialize_seq(Some(world.archetypes().filter(predicate).count() + 3))?;

let generations: Vec<u32> = world.generations();
seq.serialize_element(&generations)?;
let freelist = world.entity_freelist();
seq.serialize_element(&(freelist.free_cursor as i64))?;
seq.serialize_element(freelist.pending)?;

for archetype in world.archetypes().filter(predicate) {
seq.serialize_element(&SerializeArchetype {
world,
archetype,
ctx: RefCell::new(context),
})?;
}

seq.end()
}

Expand Down Expand Up @@ -522,12 +533,25 @@ where
{
let mut world = World::new();
let mut entities = Vec::new();

let generations = seq.next_element::<Vec<u32>>()?.unwrap();
let free_cursor = seq.next_element::<i64>()?.unwrap();
let pending = seq.next_element::<Vec<u32>>()?.unwrap();

let free_list = Freelist {
pending: &pending,
free_cursor: free_cursor as isize
};

while let Some(bundle) =
seq.next_element_seed(DeserializeArchetype(self.0, &mut entities))?
{
world.spawn_column_batch_at(&entities, bundle);
entities.clear();
}

world.push_entity_states(&generations, free_list).unwrap();

Ok(world)
}
}
Expand Down Expand Up @@ -748,7 +772,7 @@ mod tests {
use super::*;
use crate::*;

#[derive(Serialize, Deserialize, PartialEq, Debug, Copy, Clone)]
#[derive(Serialize, Deserialize, PartialEq, Debug, Copy, Clone, Default)]
struct Position([f32; 3]);
#[derive(Serialize, Deserialize, PartialEq, Debug, Copy, Clone)]
struct Velocity([f32; 3]);
Expand All @@ -760,13 +784,13 @@ mod tests {
#[derive(Serialize, Deserialize)]
enum ComponentId {
Position,
Velocity,
Velocity
}

/// Bodge into serde_test's very strict interface
#[derive(Deserialize)]
struct SerWorld<Q>(
#[serde(deserialize_with = "helpers::deserialize")] World,
#[serde(deserialize_with = "helpers::deserialize")] RefCell<World>,
#[serde(skip)] PhantomData<Q>,
);

Expand All @@ -776,7 +800,12 @@ mod tests {
x.get::<&T>().as_ref().map(|x| &**x) == y.get::<&T>().as_ref().map(|x| &**x)
}

for (x, y) in self.0.iter().zip(other.0.iter()) {
// Spawn components to make sure generations and freelists are serialized correctly.
// This is only ok because SerWorld is only used for testing.
self.0.borrow_mut().spawn((Position::default(),));
other.0.borrow_mut().spawn((Position::default(),));

for (x, y) in self.0.borrow().iter().zip(other.0.borrow().iter()) {
if x.entity() != y.entity()
|| !same_components::<Position>(&x, &y)
|| !same_components::<Velocity>(&x, &y)
Expand All @@ -791,7 +820,7 @@ mod tests {
impl<Q> fmt::Debug for SerWorld<Q> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_map()
.entries(self.0.iter().map(|e| {
.entries(self.0.borrow().iter().map(|e| {
(
e.entity(),
(
Expand All @@ -808,15 +837,15 @@ mod tests {

impl<'a, Q: Query> Serialize for SerWorldInner<'a, Q> {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
helpers::serialize::<Q, S>(&self.0, s)
helpers::serialize::<Q, S>(self.0, s)
}
}

impl<Q: Query> Serialize for SerWorld<Q> {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
use serde::ser::SerializeTupleStruct;
let mut t = s.serialize_tuple_struct("SerWorld", 1)?;
t.serialize_field(&SerWorldInner(&self.0, self.1))?;
t.serialize_field(&SerWorldInner(&*self.0.borrow(), self.1))?;
t.end()
}
}
Expand All @@ -832,13 +861,13 @@ mod tests {
s,
)
}
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<World, D::Error> {
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<RefCell::<World>, D::Error> {
crate::serialize::column::deserialize(
&mut Context {
components: Vec::new(),
},
d,
)
).and_then(|x| Ok(RefCell::new(x)))
}
}

Expand All @@ -859,7 +888,7 @@ mod tests {
}
ComponentId::Velocity => {
batch.add::<Velocity>();
}
},
}
self.components.push(id);
}
Expand All @@ -882,7 +911,7 @@ mod tests {
}
ComponentId::Velocity => {
deserialize_column::<Velocity, _>(entity_count, &mut seq, batch)?;
}
},
}
}
Ok(())
Expand Down Expand Up @@ -925,12 +954,29 @@ mod tests {
let v0 = Velocity([1.0, 1.0, 1.0]);
let p1 = Position([2.0, 2.0, 2.0]);
let e0 = world.spawn((p0, v0));
let e_despawn = world.spawn((p1,));
let e1 = world.spawn((p1,));
let e2 = world.spawn(());

assert_tokens(&SerWorld(world, PhantomData::<()>), &[
let _ = world.despawn(e_despawn).unwrap();

assert_tokens(&SerWorld(RefCell::new(world), PhantomData::<()>), &[
Token::TupleStruct { name: "SerWorld", len: 1 },
Token::Seq { len: Some(3) },
Token::Seq { len: Some(6) },

// Generaitons
Token::Seq { len: Some(4) },
Token::U32(1),
Token::U32(2),
Token::U32(1),
Token::U32(1),
Token::SeqEnd,
// FreeCursor
Token::I64(1),
// Pending List
Token::Seq { len: Some(1) },
Token::U32(1),
Token::SeqEnd,

Token::Tuple { len: 4 },
Token::U32(1),
Expand Down Expand Up @@ -1013,9 +1059,21 @@ mod tests {
let _e1 = world.spawn((p1,));
let _e2 = world.spawn(());

assert_ser_tokens(&SerWorld(world, PhantomData::<(&Velocity,)>), &[
assert_ser_tokens(&SerWorld(RefCell::new(world), PhantomData::<(&Velocity,)>), &[
Token::TupleStruct { name: "SerWorld", len: 1 },
Token::Seq { len: Some(1) },
Token::Seq { len: Some(4) },

// Generaitons
Token::Seq { len: Some(3) },
Token::U32(1),
Token::U32(1),
Token::U32(1),
Token::SeqEnd,
// FreeCursor
Token::I64(0),
// Pending List
Token::Seq { len: Some(0) },
Token::SeqEnd,

Token::Tuple { len: 4 },
Token::U32(1),
Expand Down
18 changes: 17 additions & 1 deletion src/world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use hashbrown::hash_map::{Entry, HashMap};

use crate::alloc::boxed::Box;
use crate::archetype::{Archetype, TypeIdMap, TypeInfo};
use crate::entities::{Entities, EntityMeta, Location, ReserveEntitiesIterator};
use crate::entities::{Entities, EntityMeta, Freelist, Location, PendingPushError, ReserveEntitiesIterator};
use crate::query::{assert_borrow, assert_distinct};
use crate::{
Bundle, ColumnBatch, ComponentRef, DynamicBundle, Entity, EntityRef, Fetch, MissingComponent,
Expand Down Expand Up @@ -429,6 +429,22 @@ impl World {
&self.entities.meta
}

/// Get the id's of all pending (free) entities
pub fn entity_freelist(&self) -> Freelist {
self.entities.freelist()
}

/// Returns the generations of all entities (allocated & free)
pub fn generations(&self) -> Vec<u32> {
self.entities_meta().iter().map(|m| m.generation.get()).collect()
}

/// Set a custom generations and freelist
pub fn push_entity_states(&mut self, generations: &[u32], freelist: Freelist) -> Result<(), PendingPushError>{
self.entities.push_generations(generations);
self.entities.push_freelist(freelist)
}

#[inline(always)]
pub(crate) fn archetypes_inner(&self) -> &[Archetype] {
&self.archetypes.archetypes
Expand Down