diff --git a/Cargo.toml b/Cargo.toml index 5216af9d..01d652d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ serde = {version = "1.0.143", default-features = false, features = ["alloc"], op [dev-dependencies] claim = "0.5.0" rustversion = "1.0.9" +serde_derive = "1.0.143" serde_test = "1.0.143" # Temporarily use this fork until https://github.com/dtolnay/trybuild/issues/171 is resolved. trybuild = {git = "https://github.com/Anders429/trybuild"} diff --git a/src/archetype/identifier/impl_serde.rs b/src/archetype/identifier/impl_serde.rs index 8fc8ad9e..ba703f72 100644 --- a/src/archetype/identifier/impl_serde.rs +++ b/src/archetype/identifier/impl_serde.rs @@ -1,5 +1,5 @@ use crate::{archetype::Identifier, registry::Registry}; -use alloc::vec::Vec; +use alloc::{format, vec::Vec}; use core::{fmt, marker::PhantomData, mem::ManuallyDrop}; use serde::{ de, @@ -66,15 +66,17 @@ where } // Check that trailing bits are not set. - // SAFETY: `buffer` is guaranteed to have `(R::LEN + 7) / 8` elements, so this will - // always be within the bounds of `buffer.` - let byte = unsafe { buffer.get_unchecked((R::LEN + 7) / 8 - 1) }; - let bit = R::LEN % 8; - if bit != 0 && byte & (255 << bit) != 0 { - return Err(de::Error::invalid_value( - Unexpected::Unsigned(u64::from(*byte)), - &self, - )); + if R::LEN != 0 { + // SAFETY: `buffer` is guaranteed to have `(R::LEN + 7) / 8` elements, so this will + // always be within the bounds of `buffer.` + let byte = unsafe { buffer.get_unchecked((R::LEN + 7) / 8 - 1) }; + let bit = R::LEN % 8; + if bit != 0 && byte & (255 << bit) != 0 { + return Err(de::Error::invalid_value( + Unexpected::Other(&format!("byte array {:?}", &buffer)), + &self, + )); + } } let mut buffer = ManuallyDrop::new(buffer); @@ -96,3 +98,79 @@ where ) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::registry; + use alloc::vec; + use serde_test::{assert_de_tokens_error, assert_tokens, Token}; + + macro_rules! create_components { + ($( $variants:ident ),*) => { + $( + struct $variants(f32); + )* + }; + } + + create_components!( + A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z + ); + + type Registry = + registry!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z); + + #[test] + fn serialize_deserialize() { + let identifier = unsafe { Identifier::::new(vec![1, 2, 3, 0]) }; + + assert_tokens( + &identifier, + &[ + Token::Tuple { len: 4 }, + Token::U8(1), + Token::U8(2), + Token::U8(3), + Token::U8(0), + Token::TupleEnd, + ], + ); + } + + #[test] + fn serialize_deserialize_empty() { + let identifier = unsafe { Identifier::::new(vec![]) }; + + assert_tokens(&identifier, &[Token::Tuple { len: 0 }, Token::TupleEnd]); + } + + #[test] + fn deserialize_from_too_many_bits() { + assert_de_tokens_error::>( + &[ + Token::Tuple { len: 4 }, + Token::U8(1), + Token::U8(2), + Token::U8(3), + Token::U8(255), + Token::TupleEnd, + ], + "invalid value: byte array [1, 2, 3, 255], expected 26 bits corresponding to components, with prefixed 0s padded on the last byte to round up to 4 bytes" + ); + } + + #[test] + fn deserialize_from_too_few_bytes() { + assert_de_tokens_error::>( + &[ + Token::Tuple { len: 3 }, + Token::U8(1), + Token::U8(2), + Token::U8(3), + Token::TupleEnd, + ], + "invalid length 3, expected 26 bits corresponding to components, with prefixed 0s padded on the last byte to round up to 4 bytes" + ); + } +} diff --git a/src/archetype/identifier/mod.rs b/src/archetype/identifier/mod.rs index 856d201a..46c66465 100644 --- a/src/archetype/identifier/mod.rs +++ b/src/archetype/identifier/mod.rs @@ -116,6 +116,16 @@ where unsafe { Iter::::new(self.pointer) } } + /// Returns the number of components identified by this identifier. + /// + /// This is not a cheap operation. It is O(N), looping over the bits individually and counting + /// them. + #[must_use] + pub(crate) fn count(&self) -> usize { + // SAFETY: The identifier here will outlive the derived `Iter`. + unsafe { self.iter() }.filter(|b| *b).count() + } + /// Returns the size of the components within the canonical entity represented by this /// identifier. /// @@ -241,6 +251,17 @@ where unsafe { Iter::::new(self.pointer) } } + /// Returns the number of components identified by this identifier. + /// + /// This is not a cheap operation. It is O(N), looping over the bits individually and counting + /// them. + #[cfg(feature = "serde")] + #[must_use] + pub(crate) fn count(self) -> usize { + // SAFETY: The identifier here will outlive the derived `Iter`. + unsafe { self.iter() }.filter(|b| *b).count() + } + /// Returns a copy of the bytes defining this identifier. pub(crate) fn as_vec(self) -> Vec { // SAFETY: The reference created here will always live longer than the referenced @@ -379,6 +400,14 @@ mod tests { ); } + #[cfg(feature = "serde")] + #[test] + fn buffer_count() { + let buffer = unsafe { Identifier::::new(vec![1, 2, 3, 0]) }; + + assert_eq!(buffer.count(), 4); + } + #[test] fn buffer_size_of_components() { let buffer = unsafe { Identifier::::new(vec![7]) }; @@ -417,6 +446,15 @@ mod tests { ); } + #[cfg(feature = "serde")] + #[test] + fn identifier_count() { + let buffer = unsafe { Identifier::::new(vec![1, 2, 3, 0]) }; + let identifier = unsafe { buffer.as_ref() }; + + assert_eq!(identifier.count(), 4); + } + #[test] fn identifier_as_vec() { let buffer = unsafe { Identifier::::new(vec![1, 2, 3, 0]) }; diff --git a/src/archetype/impl_serde.rs b/src/archetype/impl_serde.rs index 2d8ede40..fd4c6b25 100644 --- a/src/archetype/impl_serde.rs +++ b/src/archetype/impl_serde.rs @@ -5,7 +5,7 @@ use crate::{ entity, registry::{RegistryDeserialize, RegistrySerialize}, }; -use alloc::vec::Vec; +use alloc::{string::String, vec::Vec}; use core::{ any::type_name, fmt, @@ -54,10 +54,7 @@ where where S: Serializer, { - let mut tuple = serializer.serialize_tuple( - // SAFETY: The identifier here will outlive the derived `Iter`. - unsafe { self.0.identifier.iter() }.filter(|b| *b).count() + 1, - )?; + let mut tuple = serializer.serialize_tuple(self.0.identifier.count() + 1)?; tuple.serialize_element(&SerializeColumn(&ManuallyDrop::new( // SAFETY: `entity_identifiers` is guaranteed to contain the raw parts for a valid // `Vec` of length `length`. @@ -120,13 +117,7 @@ where where S: Serializer, { - let mut tuple = serializer.serialize_tuple( - // SAFETY: The identifier here will outlive the derived `Iter`. - unsafe { self.archetype.identifier.iter() } - .filter(|b| *b) - .count() - + 1, - )?; + let mut tuple = serializer.serialize_tuple(self.archetype.identifier.count() + 1)?; tuple.serialize_element( // SAFETY: `entity_identifiers` is guaranteed to contain the raw parts for a valid @@ -280,7 +271,15 @@ where type Value = (); fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("row of (entity::Identifier, components...)") + write!(formatter, "(entity::Identifier{})", { + let mut names = String::new(); + // SAFETY: The identifier iter passed here contains the same amount of bits as + // there are components in `R`. + unsafe { + R::expected_row_component_names(&mut names, self.0.identifier.iter()); + } + names + }) } fn visit_seq(self, mut seq: A) -> Result @@ -298,10 +297,10 @@ where ) }, ); - entity_identifiers - .push(seq.next_element()?.ok_or_else(|| { - de::Error::invalid_length(0, &"number of components + 1") - })?); + entity_identifiers.push( + seq.next_element()? + .ok_or_else(|| de::Error::invalid_length(0, &self))?, + ); *self.0.entity_identifiers = ( entity_identifiers.as_mut_ptr(), entity_identifiers.capacity(), @@ -317,6 +316,8 @@ where self.0.length, &mut seq, self.0.identifier.iter(), + 0, + self.0.identifier, ) }?; @@ -324,11 +325,7 @@ where } } - deserializer.deserialize_tuple( - // SAFETY: The identifier here will outlive the derived `Iter`. - unsafe { self.identifier.iter() }.filter(|b| *b).count() + 1, - DeserializeRowVisitor(self), - ) + deserializer.deserialize_tuple(self.identifier.count() + 1, DeserializeRowVisitor(self)) } } @@ -348,6 +345,7 @@ where { type Value = Archetype; + #[allow(clippy::too_many_lines)] // This is fine for a Deserialize impl. fn deserialize(self, deserializer: D) -> Result where D: Deserializer<'de>, @@ -365,8 +363,17 @@ where fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { write!( formatter, - "{} rows of (entity::Identifier, components...)", - self.0.length + "{} rows of (entity::Identifier{})", + self.0.length, + { + let mut names = String::new(); + // SAFETY: The identifier iter passed here contains the same amount of bits + // as there are components in `R`. + unsafe { + R::expected_row_component_names(&mut names, self.0.identifier.iter()); + } + names + }, ) } @@ -381,9 +388,7 @@ where entity_identifiers_vec.capacity(), ); - let components_len = - // SAFETY: The identifier here will outlive the derived `Iter`. - unsafe { self.0.identifier.iter() }.filter(|b| *b).count(); + let components_len = self.0.identifier.count(); let mut components = Vec::with_capacity(components_len); // SAFETY: The registry `R` over which `self.0.identifier` is generic is the same // `R` on which this function is called. @@ -544,7 +549,7 @@ where for i in 0..self.0.length { v.push( seq.next_element()? - .ok_or_else(|| de::Error::invalid_length(i, &"`length` components"))?, + .ok_or_else(|| de::Error::invalid_length(i, &self))?, ); } @@ -589,7 +594,15 @@ where type Value = Archetype; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("component columns") + write!(formatter, "columns for each of (entity::Identifier{})", { + let mut names = String::new(); + // SAFETY: The identifier iter passed here contains the same amount of bits as + // there are components in `R`. + unsafe { + R::expected_row_component_names(&mut names, self.0.identifier.iter()); + } + names + }) } fn visit_seq(self, mut seq: A) -> Result @@ -598,12 +611,9 @@ where { let entity_identifiers = seq .next_element_seed(DeserializeColumn::new(self.0.length))? - .ok_or_else(|| de::Error::invalid_length(2, &self))?; + .ok_or_else(|| de::Error::invalid_length(0, &self))?; - let mut components = Vec::with_capacity( - // SAFETY: The identifier here will outlive the derived `Iter`. - unsafe { self.0.identifier.iter() }.filter(|b| *b).count(), - ); + let mut components = Vec::with_capacity(self.0.identifier.count()); let result = // SAFETY: The `R` over which `self.0.identifier` is generic is the same `R` on // which this function is being called. @@ -613,6 +623,8 @@ where self.0.length, &mut seq, self.0.identifier.iter(), + 0, + self.0.identifier.as_ref(), ) }; if let Err(error) = result { @@ -663,7 +675,7 @@ where deserializer.deserialize_tuple( // SAFETY: The identifier here will outlive the derived `Iter`. - unsafe { self.identifier.iter() }.filter(|b| *b).count() + 1, + self.identifier.count() + 1, DeserializeColumnsVisitor(self), ) } @@ -803,3 +815,574 @@ where ) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{archetype::Identifier, entity, registry}; + use alloc::{format, vec}; + use core::any::type_name; + use serde_derive::{Deserialize, Serialize}; + use serde_test::{assert_de_tokens_error, assert_tokens, Compact, Configure, Readable, Token}; + + #[derive(Debug, Deserialize, Eq, PartialEq, Serialize)] + struct A(u32); + + #[derive(Debug, Deserialize, Eq, PartialEq, Serialize)] + struct B(char); + + type Registry = registry!(A, B); + + #[test] + fn serialize_deserialize_by_column() { + let mut archetype = Archetype::new(unsafe { Identifier::::new(vec![3]) }); + let mut entity_allocator = entity::Allocator::new(); + unsafe { + archetype.push(entity!(A(1), B('a')), &mut entity_allocator); + archetype.push(entity!(A(2), B('b')), &mut entity_allocator); + archetype.push(entity!(A(3), B('c')), &mut entity_allocator); + } + + assert_tokens( + &archetype.compact(), + &[ + Token::NewtypeStruct { name: "Archetype" }, + Token::Tuple { len: 3 }, + // Identifier + Token::Tuple { len: 1 }, + Token::U8(3), + Token::TupleEnd, + // Length + Token::U64(3), + // Columns + Token::Tuple { len: 3 }, + // Entity identifiers + Token::Tuple { len: 3 }, + Token::Struct { + name: "Identifier", + len: 2, + }, + Token::String("index"), + Token::U64(0), + Token::String("generation"), + Token::U64(0), + Token::StructEnd, + Token::Struct { + name: "Identifier", + len: 2, + }, + Token::String("index"), + Token::U64(1), + Token::String("generation"), + Token::U64(0), + Token::StructEnd, + Token::Struct { + name: "Identifier", + len: 2, + }, + Token::String("index"), + Token::U64(2), + Token::String("generation"), + Token::U64(0), + Token::StructEnd, + Token::TupleEnd, + // A column + Token::Tuple { len: 3 }, + Token::NewtypeStruct { name: "A" }, + Token::U32(1), + Token::NewtypeStruct { name: "A" }, + Token::U32(2), + Token::NewtypeStruct { name: "A" }, + Token::U32(3), + Token::TupleEnd, + // B column + Token::Tuple { len: 3 }, + Token::NewtypeStruct { name: "B" }, + Token::Char('a'), + Token::NewtypeStruct { name: "B" }, + Token::Char('b'), + Token::NewtypeStruct { name: "B" }, + Token::Char('c'), + Token::TupleEnd, + Token::TupleEnd, + Token::TupleEnd, + ], + ); + } + + #[test] + fn deserialize_by_column_missing_entity_identifiers() { + assert_de_tokens_error::>>( + &[ + Token::NewtypeStruct { name: "Archetype" }, + Token::Tuple { len: 3 }, + // Identifier + Token::Tuple { len: 1 }, + Token::U8(3), + Token::TupleEnd, + // Length + Token::U64(3), + // Columns + Token::Tuple { len: 3 }, + // Entity identifiers + Token::Tuple { len: 1 }, + Token::Struct { + name: "Identifier", + len: 2, + }, + Token::String("index"), + Token::U64(0), + Token::String("generation"), + Token::U64(0), + Token::StructEnd, + Token::TupleEnd, + ], + &format!( + "invalid length 1, expected column of 3 `{}`s", + type_name::() + ), + ); + } + + #[test] + fn deserialize_by_column_missing_components() { + assert_de_tokens_error::>>( + &[ + Token::NewtypeStruct { name: "Archetype" }, + Token::Tuple { len: 3 }, + // Identifier + Token::Tuple { len: 1 }, + Token::U8(3), + Token::TupleEnd, + // Length + Token::U64(3), + // Columns + Token::Tuple { len: 3 }, + // Entity identifiers + Token::Tuple { len: 3 }, + Token::Struct { + name: "Identifier", + len: 2, + }, + Token::String("index"), + Token::U64(0), + Token::String("generation"), + Token::U64(0), + Token::StructEnd, + Token::Struct { + name: "Identifier", + len: 2, + }, + Token::String("index"), + Token::U64(1), + Token::String("generation"), + Token::U64(0), + Token::StructEnd, + Token::Struct { + name: "Identifier", + len: 2, + }, + Token::String("index"), + Token::U64(2), + Token::String("generation"), + Token::U64(0), + Token::StructEnd, + Token::TupleEnd, + // A column + Token::Tuple { len: 3 }, + Token::NewtypeStruct { name: "A" }, + Token::U32(1), + Token::NewtypeStruct { name: "A" }, + Token::U32(2), + Token::TupleEnd, + ], + &format!( + "invalid length 2, expected column of 3 `{}`s", + type_name::() + ), + ); + } + + #[test] + fn deserialize_by_column_missing_entity_identifier_column() { + assert_de_tokens_error::>>( + &[ + Token::NewtypeStruct { name: "Archetype" }, + Token::Tuple { len: 3 }, + // Identifier + Token::Tuple { len: 1 }, + Token::U8(3), + Token::TupleEnd, + // Length + Token::U64(3), + // Columns + Token::Tuple { len: 0 }, + Token::TupleEnd, + ], + &format!( + "invalid length 0, expected columns for each of (entity::Identifier, {}, {})", + type_name::(), + type_name::() + ), + ); + } + + #[test] + fn deserialize_by_column_missing_component_column() { + assert_de_tokens_error::>>( + &[ + Token::NewtypeStruct { name: "Archetype" }, + Token::Tuple { len: 3 }, + // Identifier + Token::Tuple { len: 1 }, + Token::U8(3), + Token::TupleEnd, + // Length + Token::U64(3), + // Columns + Token::Tuple { len: 2 }, + // Entity identifiers + Token::Tuple { len: 3 }, + Token::Struct { + name: "Identifier", + len: 2, + }, + Token::String("index"), + Token::U64(0), + Token::String("generation"), + Token::U64(0), + Token::StructEnd, + Token::Struct { + name: "Identifier", + len: 2, + }, + Token::String("index"), + Token::U64(1), + Token::String("generation"), + Token::U64(0), + Token::StructEnd, + Token::Struct { + name: "Identifier", + len: 2, + }, + Token::String("index"), + Token::U64(2), + Token::String("generation"), + Token::U64(0), + Token::StructEnd, + Token::TupleEnd, + // A column + Token::Tuple { len: 3 }, + Token::NewtypeStruct { name: "A" }, + Token::U32(1), + Token::NewtypeStruct { name: "A" }, + Token::U32(2), + Token::NewtypeStruct { name: "A" }, + Token::U32(3), + Token::TupleEnd, + Token::TupleEnd, + ], + &format!( + "invalid length 2, expected columns for each of (entity::Identifier, {}, {})", + type_name::(), + type_name::() + ), + ); + } + + #[test] + fn deserialize_by_column_missing_identifier() { + assert_de_tokens_error::>>( + &[ + Token::NewtypeStruct { name: "Archetype" }, + Token::Tuple { len: 0 }, + Token::TupleEnd, + ], + "invalid length 0, expected column-serialized Archetype", + ); + } + + #[test] + fn deserialize_by_column_missing_length() { + assert_de_tokens_error::>>( + &[ + Token::NewtypeStruct { name: "Archetype" }, + Token::Tuple { len: 1 }, + // Identifier + Token::Tuple { len: 1 }, + Token::U8(3), + Token::TupleEnd, + Token::TupleEnd, + ], + "invalid length 1, expected column-serialized Archetype", + ); + } + + #[test] + fn deserialize_by_column_missing_columns() { + assert_de_tokens_error::>>( + &[ + Token::NewtypeStruct { name: "Archetype" }, + Token::Tuple { len: 2 }, + // Identifier + Token::Tuple { len: 1 }, + Token::U8(3), + Token::TupleEnd, + // Length + Token::U64(3), + Token::TupleEnd, + ], + "invalid length 2, expected column-serialized Archetype", + ); + } + + #[test] + fn serialize_deserialize_by_row() { + let mut archetype = Archetype::new(unsafe { Identifier::::new(vec![3]) }); + let mut entity_allocator = entity::Allocator::new(); + unsafe { + archetype.push(entity!(A(1), B('a')), &mut entity_allocator); + archetype.push(entity!(A(2), B('b')), &mut entity_allocator); + archetype.push(entity!(A(3), B('c')), &mut entity_allocator); + } + + assert_tokens( + &archetype.readable(), + &[ + Token::NewtypeStruct { name: "Archetype" }, + Token::Tuple { len: 3 }, + // Identifier + Token::Tuple { len: 1 }, + Token::U8(3), + Token::TupleEnd, + // Length + Token::U64(3), + // Rows + Token::Tuple { len: 3 }, + // Row 1 + Token::Tuple { len: 3 }, + Token::Struct { + name: "Identifier", + len: 2, + }, + Token::String("index"), + Token::U64(0), + Token::String("generation"), + Token::U64(0), + Token::StructEnd, + Token::NewtypeStruct { name: "A" }, + Token::U32(1), + Token::NewtypeStruct { name: "B" }, + Token::Char('a'), + Token::TupleEnd, + // Row 2 + Token::Tuple { len: 3 }, + Token::Struct { + name: "Identifier", + len: 2, + }, + Token::String("index"), + Token::U64(1), + Token::String("generation"), + Token::U64(0), + Token::StructEnd, + Token::NewtypeStruct { name: "A" }, + Token::U32(2), + Token::NewtypeStruct { name: "B" }, + Token::Char('b'), + Token::TupleEnd, + // Row 3 + Token::Tuple { len: 3 }, + Token::Struct { + name: "Identifier", + len: 2, + }, + Token::String("index"), + Token::U64(2), + Token::String("generation"), + Token::U64(0), + Token::StructEnd, + Token::NewtypeStruct { name: "A" }, + Token::U32(3), + Token::NewtypeStruct { name: "B" }, + Token::Char('c'), + Token::TupleEnd, + Token::TupleEnd, + Token::TupleEnd, + ], + ); + } + + #[test] + fn deserialize_by_row_no_entity_identifier() { + assert_de_tokens_error::>>( + &[ + Token::NewtypeStruct { name: "Archetype" }, + Token::Tuple { len: 3 }, + // Identifier + Token::Tuple { len: 1 }, + Token::U8(3), + Token::TupleEnd, + // Length + Token::U64(1), + // Rows + Token::Tuple { len: 1 }, + // Row 1 + Token::Tuple { len: 0 }, + Token::TupleEnd, + ], + &format!( + "invalid length 0, expected (entity::Identifier, {}, {})", + type_name::(), + type_name::() + ), + ); + } + + #[test] + fn deserialize_by_row_missing_component() { + assert_de_tokens_error::>>( + &[ + Token::NewtypeStruct { name: "Archetype" }, + Token::Tuple { len: 3 }, + // Identifier + Token::Tuple { len: 1 }, + Token::U8(3), + Token::TupleEnd, + // Length + Token::U64(1), + // Rows + Token::Tuple { len: 1 }, + // Row 1 + Token::Tuple { len: 2 }, + Token::Struct { + name: "Identifier", + len: 2, + }, + Token::String("index"), + Token::U64(0), + Token::String("generation"), + Token::U64(0), + Token::StructEnd, + Token::NewtypeStruct { name: "A" }, + Token::U32(1), + Token::TupleEnd, + ], + &format!( + "invalid length 2, expected (entity::Identifier, {}, {})", + type_name::(), + type_name::() + ), + ); + } + + #[test] + fn deserialize_by_row_no_rows() { + assert_de_tokens_error::>>( + &[ + Token::NewtypeStruct { name: "Archetype" }, + Token::Tuple { len: 3 }, + // Identifier + Token::Tuple { len: 1 }, + Token::U8(3), + Token::TupleEnd, + // Length + Token::U64(1), + // Rows + Token::Tuple { len: 0 }, + Token::TupleEnd, + ], + &format!( + "invalid length 0, expected 1 rows of (entity::Identifier, {}, {})", + type_name::(), + type_name::() + ), + ); + } + + #[test] + fn deserialize_by_row_missing_rows() { + assert_de_tokens_error::>>( + &[ + Token::NewtypeStruct { name: "Archetype" }, + Token::Tuple { len: 3 }, + // Identifier + Token::Tuple { len: 1 }, + Token::U8(3), + Token::TupleEnd, + // Length + Token::U64(3), + // Rows + Token::Tuple { len: 1 }, + // Row 1 + Token::Tuple { len: 3 }, + Token::Struct { + name: "Identifier", + len: 2, + }, + Token::String("index"), + Token::U64(0), + Token::String("generation"), + Token::U64(0), + Token::StructEnd, + Token::NewtypeStruct { name: "A" }, + Token::U32(1), + Token::NewtypeStruct { name: "B" }, + Token::Char('a'), + Token::TupleEnd, + Token::TupleEnd, + ], + &format!( + "invalid length 1, expected 3 rows of (entity::Identifier, {}, {})", + type_name::(), + type_name::() + ), + ); + } + + #[test] + fn deserialize_by_row_missing_identifier() { + assert_de_tokens_error::>>( + &[ + Token::NewtypeStruct { name: "Archetype" }, + Token::Tuple { len: 0 }, + Token::TupleEnd, + ], + "invalid length 0, expected row-serialized Archetype", + ); + } + + #[test] + fn deserialize_by_row_missing_length() { + assert_de_tokens_error::>>( + &[ + Token::NewtypeStruct { name: "Archetype" }, + Token::Tuple { len: 1 }, + // Identifier + Token::Tuple { len: 1 }, + Token::U8(3), + Token::TupleEnd, + Token::TupleEnd, + ], + "invalid length 1, expected row-serialized Archetype", + ); + } + + #[test] + fn deserialize_by_row_missing_rows_completely() { + assert_de_tokens_error::>>( + &[ + Token::NewtypeStruct { name: "Archetype" }, + Token::Tuple { len: 2 }, + // Identifier + Token::Tuple { len: 1 }, + Token::U8(3), + Token::TupleEnd, + // Length + Token::U64(3), + Token::TupleEnd, + ], + "invalid length 2, expected row-serialized Archetype", + ); + } +} diff --git a/src/archetype/mod.rs b/src/archetype/mod.rs index c5acfff7..0d33a10a 100644 --- a/src/archetype/mod.rs +++ b/src/archetype/mod.rs @@ -84,13 +84,12 @@ where pub(crate) fn new(identifier: Identifier) -> Self { let mut entity_identifiers = ManuallyDrop::new(Vec::new()); - let entity_len = - // SAFETY: The iterator returned here is outlived by `identifier`. - unsafe { identifier.iter() }.filter(|b| *b).count(); - let mut components = Vec::with_capacity(entity_len); - for _ in 0..entity_len { - let mut v = ManuallyDrop::new(Vec::new()); - components.push((v.as_mut_ptr(), v.capacity())); + let components_len = identifier.count(); + let mut components = Vec::with_capacity(components_len); + // SAFETY: The registry `R` over which `identifier` is generic is the same + // `R` on which this function is called. + unsafe { + R::new_components_with_capacity(&mut components, 0, identifier.iter()); } // SAFETY: `entity_identifiers` is an empty `Vec`, which matches the provided `length` of @@ -333,6 +332,7 @@ where ) }, ); + // Update swapped index if this isn't the last row. if index < self.length - 1 { // SAFETY: `entity_allocator` contains an entry for the entity identifiers stored in diff --git a/src/archetypes/impl_serde.rs b/src/archetypes/impl_serde.rs index 705fcad9..7f5bf207 100644 --- a/src/archetypes/impl_serde.rs +++ b/src/archetypes/impl_serde.rs @@ -72,9 +72,11 @@ where Archetypes::with_capacity(cmp::min(seq.size_hint().unwrap_or(0), 4096)); while let Some(archetype) = seq.next_element::>()? { *self.len += archetype.len(); - if !archetypes.insert(archetype) { + if let Err(archetype) = archetypes.insert(archetype) { return Err(de::Error::custom(format_args!( - "non-unique `Identifier`, expected {}", + "non-unique `Identifier` {:?}, expected {}", + // SAFETY: This identifier will not outlive the archetype. + unsafe { archetype.identifier() }, (&self as &dyn Expected) ))); } @@ -89,3 +91,328 @@ where }) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + archetype::Identifier, + entity, registry, + registry::{RegistryDebug, RegistryEq, RegistryPartialEq}, + }; + use alloc::{format, vec}; + use claim::assert_ok; + use core::{any::type_name, fmt, fmt::Debug}; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + use serde_derive::{Deserialize, Serialize}; + use serde_test::{assert_de_tokens_error, assert_tokens, Compact, Configure, Token}; + + struct SeededArchetypes + where + R: crate::registry::Registry, + { + archetypes: Archetypes, + len: usize, + } + + impl PartialEq for SeededArchetypes + where + R: RegistryPartialEq, + { + fn eq(&self, other: &Self) -> bool { + self.archetypes == other.archetypes && self.len == other.len + } + } + + impl Eq for SeededArchetypes where R: RegistryEq {} + + impl Debug for SeededArchetypes + where + R: RegistryDebug, + { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("SeededArchetypes") + .field("archetypes", &self.archetypes) + .field("len", &self.len) + .finish() + } + } + + impl Serialize for SeededArchetypes + where + R: RegistrySerialize, + { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.archetypes.serialize(serializer) + } + } + + impl<'de, R> Deserialize<'de> for SeededArchetypes + where + R: RegistryDeserialize<'de>, + { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let mut len = 0; + let archetypes = DeserializeArchetypes::::new(&mut len).deserialize(deserializer)?; + Ok(Self { archetypes, len }) + } + } + + #[derive(Debug, Deserialize, Eq, PartialEq, Serialize)] + struct A(u32); + + #[derive(Debug, Deserialize, Eq, PartialEq, Serialize)] + struct B(char); + + type Registry = registry!(A, B); + + #[test] + fn serialize_deserialize_empty() { + let archetypes = Archetypes::::new(); + + assert_tokens( + &SeededArchetypes { archetypes, len: 0 }, + &[Token::Seq { len: Some(0) }, Token::SeqEnd], + ); + } + + #[test] + fn serialize_deserialize_multiple_archetypes() { + let mut archetypes = Archetypes::::new(); + let mut entity_allocator = entity::Allocator::new(); + + let mut ab_archetype = Archetype::new(unsafe { Identifier::::new(vec![3]) }); + unsafe { + ab_archetype.push(entity!(A(1), B('a')), &mut entity_allocator); + ab_archetype.push(entity!(A(2), B('b')), &mut entity_allocator); + ab_archetype.push(entity!(A(3), B('c')), &mut entity_allocator); + } + assert_ok!(archetypes.insert(ab_archetype)); + + let mut a_archetype = Archetype::new(unsafe { Identifier::::new(vec![1]) }); + unsafe { + a_archetype.push(entity!(A(4)), &mut entity_allocator); + a_archetype.push(entity!(A(5)), &mut entity_allocator); + } + assert_ok!(archetypes.insert(a_archetype)); + + let b_archetype = Archetype::new(unsafe { Identifier::::new(vec![2]) }); + assert_ok!(archetypes.insert(b_archetype)); + + let mut no_component_archetype = + Archetype::new(unsafe { Identifier::::new(vec![0]) }); + unsafe { + no_component_archetype.push(entity!(), &mut entity_allocator); + } + assert_ok!(archetypes.insert(no_component_archetype)); + + assert_tokens( + &SeededArchetypes { archetypes, len: 6 }.compact(), + // The order here should stay constant, because the fnv hasher uses the same seed every time. + &[ + Token::Seq { len: Some(4) }, + // B Archetype + Token::NewtypeStruct { name: "Archetype" }, + Token::Tuple { len: 3 }, + // Identifier + Token::Tuple { len: 1 }, + Token::U8(2), + Token::TupleEnd, + // Length + Token::U64(0), + // Columns + Token::Tuple { len: 2 }, + // Entity identifiers + Token::Tuple { len: 0 }, + Token::TupleEnd, + // B column + Token::Tuple { len: 0 }, + Token::TupleEnd, + Token::TupleEnd, + Token::TupleEnd, + // No component Archetype + Token::NewtypeStruct { name: "Archetype" }, + Token::Tuple { len: 3 }, + // Identifier + Token::Tuple { len: 1 }, + Token::U8(0), + Token::TupleEnd, + // Length + Token::U64(1), + // Columns + Token::Tuple { len: 1 }, + // Entity identifiers + Token::Tuple { len: 1 }, + Token::Struct { + name: "Identifier", + len: 2, + }, + Token::String("index"), + Token::U64(5), + Token::String("generation"), + Token::U64(0), + Token::StructEnd, + Token::TupleEnd, + Token::TupleEnd, + Token::TupleEnd, + // AB Archetype + Token::NewtypeStruct { name: "Archetype" }, + Token::Tuple { len: 3 }, + // Identifier + Token::Tuple { len: 1 }, + Token::U8(3), + Token::TupleEnd, + // Length + Token::U64(3), + // Columns + Token::Tuple { len: 3 }, + // Entity identifiers + Token::Tuple { len: 3 }, + Token::Struct { + name: "Identifier", + len: 2, + }, + Token::String("index"), + Token::U64(0), + Token::String("generation"), + Token::U64(0), + Token::StructEnd, + Token::Struct { + name: "Identifier", + len: 2, + }, + Token::String("index"), + Token::U64(1), + Token::String("generation"), + Token::U64(0), + Token::StructEnd, + Token::Struct { + name: "Identifier", + len: 2, + }, + Token::String("index"), + Token::U64(2), + Token::String("generation"), + Token::U64(0), + Token::StructEnd, + Token::TupleEnd, + // A column + Token::Tuple { len: 3 }, + Token::NewtypeStruct { name: "A" }, + Token::U32(1), + Token::NewtypeStruct { name: "A" }, + Token::U32(2), + Token::NewtypeStruct { name: "A" }, + Token::U32(3), + Token::TupleEnd, + // B column + Token::Tuple { len: 3 }, + Token::NewtypeStruct { name: "B" }, + Token::Char('a'), + Token::NewtypeStruct { name: "B" }, + Token::Char('b'), + Token::NewtypeStruct { name: "B" }, + Token::Char('c'), + Token::TupleEnd, + Token::TupleEnd, + Token::TupleEnd, + // A Archetype + Token::NewtypeStruct { name: "Archetype" }, + Token::Tuple { len: 3 }, + // Identifier + Token::Tuple { len: 1 }, + Token::U8(1), + Token::TupleEnd, + // Length + Token::U64(2), + // Columns + Token::Tuple { len: 2 }, + // Entity identifiers + Token::Tuple { len: 2 }, + Token::Struct { + name: "Identifier", + len: 2, + }, + Token::String("index"), + Token::U64(3), + Token::String("generation"), + Token::U64(0), + Token::StructEnd, + Token::Struct { + name: "Identifier", + len: 2, + }, + Token::String("index"), + Token::U64(4), + Token::String("generation"), + Token::U64(0), + Token::StructEnd, + Token::TupleEnd, + // A column + Token::Tuple { len: 2 }, + Token::NewtypeStruct { name: "A" }, + Token::U32(4), + Token::NewtypeStruct { name: "A" }, + Token::U32(5), + Token::TupleEnd, + Token::TupleEnd, + Token::TupleEnd, + Token::SeqEnd, + ], + ); + } + + #[test] + fn deserialize_duplicate_archetype_identifiers() { + assert_de_tokens_error::>>( + &[ + Token::Seq { len: Some(4) }, + // B Archetype + Token::NewtypeStruct { name: "Archetype" }, + Token::Tuple { len: 3 }, + // Identifier + Token::Tuple { len: 1 }, + Token::U8(2), + Token::TupleEnd, + // Length + Token::U64(0), + // Columns + Token::Tuple { len: 2 }, + // Entity identifiers + Token::Tuple { len: 0 }, + Token::TupleEnd, + // B column + Token::Tuple { len: 0 }, + Token::TupleEnd, + Token::TupleEnd, + Token::TupleEnd, + // Second B Archetype + Token::NewtypeStruct { name: "Archetype" }, + Token::Tuple { len: 3 }, + // Identifier + Token::Tuple { len: 1 }, + Token::U8(2), + Token::TupleEnd, + // Length + Token::U64(0), + // Columns + Token::Tuple { len: 2 }, + // Entity identifiers + Token::Tuple { len: 0 }, + Token::TupleEnd, + // B column + Token::Tuple { len: 0 }, + Token::TupleEnd, + Token::TupleEnd, + Token::TupleEnd, + ], + &format!("non-unique `Identifier` [\"{}\"], expected sequence of `Archetype`s with unique `Identifier`s", type_name::()), + ); + } +} diff --git a/src/archetypes/mod.rs b/src/archetypes/mod.rs index 37afb29b..9c9fdf4f 100644 --- a/src/archetypes/mod.rs +++ b/src/archetypes/mod.rs @@ -130,7 +130,7 @@ where } #[cfg(feature = "serde")] - pub(crate) fn insert(&mut self, archetype: Archetype) -> bool { + pub(crate) fn insert(&mut self, archetype: Archetype) -> Result<(), Archetype> { let hash = Self::make_hash( // SAFETY: The `IdentifierRef` obtained here does not live longer than the `archetype`. unsafe { archetype.identifier() }, @@ -144,11 +144,11 @@ where unsafe { archetype.identifier() }, ), ) { - false + Err(archetype) } else { self.raw_archetypes .insert(hash, archetype, Self::make_hasher(&self.hash_builder)); - true + Ok(()) } } diff --git a/src/entities/mod.rs b/src/entities/mod.rs index 708529aa..926165f9 100644 --- a/src/entities/mod.rs +++ b/src/entities/mod.rs @@ -104,6 +104,7 @@ where /// /// [`Entities`]: crate::entities::Entities /// [`entities!`]: crate::entities! +#[derive(Debug, Eq, PartialEq)] pub struct Batch where E: Entities, @@ -283,3 +284,34 @@ macro_rules! entities { $crate::entities::Null }; } + +#[cfg(test)] +mod tests { + use super::*; + use alloc::vec; + + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + struct A(u64); + + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + struct B(char); + + #[test] + fn entities() { + assert_eq!( + entities!((A(42), B('f')); 100), + Batch::new((vec![A(42); 100], (vec![B('f'); 100], Null))) + ); + } + + #[test] + fn entities_len() { + assert_eq!(entities!((A(42), B('f')); 100).len(), 100); + } + + #[test] + #[should_panic] + fn batch_new_unequal_lengths() { + Batch::new((vec![A(42); 100], (vec![B('f'); 99], Null))); + } +} diff --git a/src/entities/seal/length.rs b/src/entities/seal/length.rs index 64eb246c..45e0a814 100644 --- a/src/entities/seal/length.rs +++ b/src/entities/seal/length.rs @@ -38,3 +38,31 @@ where self.component_len() == len && self.1.check_len_against(len) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::entities; + use alloc::vec; + + #[derive(Clone, Copy)] + struct A; + + #[derive(Clone, Copy)] + struct B; + + #[test] + fn component_len() { + assert_eq!(entities!((A, B); 100).entities.component_len(), 100); + } + + #[test] + fn check_len_passes() { + assert!(entities!((A, B); 100).entities.check_len()); + } + + #[test] + fn check_len_fails() { + assert!(!(vec![A; 100], (vec![B; 99], entities::Null)).check_len()); + } +} diff --git a/src/entity/allocator/impl_serde.rs b/src/entity/allocator/impl_serde.rs index 4cadc94e..20502a91 100644 --- a/src/entity/allocator/impl_serde.rs +++ b/src/entity/allocator/impl_serde.rs @@ -54,7 +54,16 @@ pub(crate) struct DeserializeAllocator<'a, R> where R: Registry, { - pub(crate) archetypes: &'a Archetypes, + archetypes: &'a Archetypes, +} + +impl<'a, R> DeserializeAllocator<'a, R> +where + R: Registry, +{ + pub(crate) fn new(archetypes: &'a Archetypes) -> Self { + Self { archetypes } + } } impl<'de, R> DeserializeSeed<'de> for DeserializeAllocator<'_, R> @@ -191,13 +200,13 @@ where for entity_identifier in &free { let slot = slots.get_mut(entity_identifier.index).ok_or_else(|| { de::Error::custom(format!( - "entity index {} is out of bounds", + "freed entity index {} is out of bounds", entity_identifier.index )) })?; match slot { Some(_) => Err(de::Error::custom(format!( - "duplicate entity index {}", + "duplicate freed entity index {}", entity_identifier.index ))), None => { @@ -215,13 +224,13 @@ where for (i, entity_identifier) in archetype.entity_identifiers().enumerate() { let slot = slots.get_mut(entity_identifier.index).ok_or_else(|| { de::Error::custom(format!( - "entity index {} is out of bounds", + "archetype entity index {} is out of bounds", entity_identifier.index )) })?; match slot { Some(_) => Err(de::Error::custom(format!( - "duplicate entity index {}", + "duplicate archetype entity index {}", entity_identifier.index ))), None => { @@ -263,3 +272,604 @@ where }) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + archetype, + archetype::Archetype, + entity, registry, + registry::{ + RegistryDebug, RegistryDeserialize, RegistryEq, RegistryPartialEq, RegistrySerialize, + }, + }; + use claim::assert_ok; + use core::{fmt, fmt::Debug, marker::PhantomData}; + use serde_derive::{Deserialize, Serialize}; + use serde_test::{assert_de_tokens, assert_de_tokens_error, assert_tokens, Token}; + + #[derive(Debug, Deserialize, Eq, PartialEq, Serialize)] + struct A; + #[derive(Debug, Deserialize, Eq, PartialEq, Serialize)] + struct B; + + type Registry = registry!(A, B); + + trait Seed + where + R: crate::registry::Registry, + { + fn archetypes() -> Archetypes; + } + + struct SeededAllocator(Allocator, Option>, PhantomData) + where + R: crate::registry::Registry; + + impl SeededAllocator + where + R: crate::registry::Registry, + { + fn new(allocator: Allocator) -> Self { + Self(allocator, None, PhantomData) + } + } + + impl PartialEq for SeededAllocator + where + R: RegistryPartialEq, + { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } + } + + impl Eq for SeededAllocator where R: RegistryEq {} + + impl Debug for SeededAllocator + where + R: RegistryDebug, + { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } + } + + impl Serialize for SeededAllocator + where + R: RegistrySerialize, + { + fn serialize(&self, serializer: T) -> Result + where + T: Serializer, + { + self.0.serialize(serializer) + } + } + + impl<'de, R, S> Deserialize<'de> for SeededAllocator + where + R: RegistryDeserialize<'de>, + S: Seed, + { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let archetypes = S::archetypes(); + let allocator = DeserializeAllocator::new(&archetypes).deserialize(deserializer)?; + Ok(Self(allocator, Some(archetypes), PhantomData)) + } + } + + #[test] + fn serialize_deserialize_empty() { + let allocator = Allocator::new(); + + struct EmptySeed; + + impl Seed for EmptySeed + where + R: crate::registry::Registry, + { + fn archetypes() -> Archetypes { + Archetypes::new() + } + } + + assert_tokens( + &SeededAllocator::::new(allocator), + &[ + Token::Struct { + name: "Allocator", + len: 2, + }, + Token::String("length"), + Token::U64(0), + Token::String("free"), + Token::Seq { len: Some(0) }, + Token::SeqEnd, + Token::StructEnd, + ], + ); + } + + #[test] + fn serialize_deserialize_with_values() { + let mut allocator = Allocator::new(); + let archetype_identifier = unsafe { archetype::Identifier::::new(vec![3]) }; + + let entity_identifier = allocator.allocate(Location { + identifier: unsafe { archetype_identifier.as_ref() }, + index: 0, + }); + unsafe { allocator.free_unchecked(entity_identifier) }; + allocator.allocate(Location { + identifier: unsafe { archetype_identifier.as_ref() }, + index: 0, + }); + allocator.allocate(Location { + identifier: unsafe { archetype_identifier.as_ref() }, + index: 1, + }); + let entity_identifier = allocator.allocate(Location { + identifier: unsafe { archetype_identifier.as_ref() }, + index: 2, + }); + unsafe { allocator.free_unchecked(entity_identifier) }; + let entity_identifier = allocator.allocate(Location { + identifier: unsafe { archetype_identifier.as_ref() }, + index: 2, + }); + unsafe { allocator.free_unchecked(entity_identifier) }; + + struct PopulatedSeed; + + impl Seed for PopulatedSeed { + fn archetypes() -> Archetypes { + let mut archetypes = Archetypes::new(); + let mut allocator = Allocator::new(); + + let mut archetype = + Archetype::new(unsafe { archetype::Identifier::::new(vec![3]) }); + unsafe { + let entity_identifier = archetype.push(entity!(A, B), &mut allocator); // index 0. + archetype.remove_row_unchecked(entity_identifier.index, &mut allocator); // remove index 0. + allocator.free_unchecked(entity_identifier); + archetype.push(entity!(A, B), &mut allocator); // index 0. + archetype.push(entity!(A, B), &mut allocator); // index 1. + let entity_identifier = archetype.push(entity!(A, B), &mut allocator); // index 2. + archetype.remove_row_unchecked(entity_identifier.index, &mut allocator); // remove index 2. + allocator.free_unchecked(entity_identifier); + let entity_identifier = archetype.push(entity!(A, B), &mut allocator); // index 2. + archetype.remove_row_unchecked(entity_identifier.index, &mut allocator); // remove index 2. + allocator.free_unchecked(entity_identifier); + } + assert_ok!(archetypes.insert(archetype)); + + archetypes + } + } + + assert_tokens( + &SeededAllocator::::new(allocator), + &[ + Token::Struct { + name: "Allocator", + len: 2, + }, + Token::String("length"), + Token::U64(3), + Token::String("free"), + Token::Seq { len: Some(1) }, + Token::Struct { + name: "Identifier", + len: 2, + }, + Token::String("index"), + Token::U64(2), + Token::String("generation"), + Token::U64(1), + Token::StructEnd, + Token::SeqEnd, + Token::StructEnd, + ], + ); + } + + #[test] + fn deserialize_from_seq() { + let mut allocator = Allocator::new(); + let archetype_identifier = unsafe { archetype::Identifier::::new(vec![3]) }; + + let entity_identifier = allocator.allocate(Location { + identifier: unsafe { archetype_identifier.as_ref() }, + index: 0, + }); + unsafe { allocator.free_unchecked(entity_identifier) }; + allocator.allocate(Location { + identifier: unsafe { archetype_identifier.as_ref() }, + index: 0, + }); + allocator.allocate(Location { + identifier: unsafe { archetype_identifier.as_ref() }, + index: 1, + }); + let entity_identifier = allocator.allocate(Location { + identifier: unsafe { archetype_identifier.as_ref() }, + index: 2, + }); + unsafe { allocator.free_unchecked(entity_identifier) }; + let entity_identifier = allocator.allocate(Location { + identifier: unsafe { archetype_identifier.as_ref() }, + index: 2, + }); + unsafe { allocator.free_unchecked(entity_identifier) }; + + struct PopulatedSeed; + + impl Seed for PopulatedSeed { + fn archetypes() -> Archetypes { + let mut archetypes = Archetypes::new(); + let mut allocator = Allocator::new(); + + let mut archetype = + Archetype::new(unsafe { archetype::Identifier::::new(vec![3]) }); + unsafe { + let entity_identifier = archetype.push(entity!(A, B), &mut allocator); // index 0. + archetype.remove_row_unchecked(entity_identifier.index, &mut allocator); // remove index 0. + allocator.free_unchecked(entity_identifier); + archetype.push(entity!(A, B), &mut allocator); // index 0. + archetype.push(entity!(A, B), &mut allocator); // index 1. + let entity_identifier = archetype.push(entity!(A, B), &mut allocator); // index 2. + archetype.remove_row_unchecked(entity_identifier.index, &mut allocator); // remove index 2. + allocator.free_unchecked(entity_identifier); + let entity_identifier = archetype.push(entity!(A, B), &mut allocator); // index 2. + archetype.remove_row_unchecked(entity_identifier.index, &mut allocator); // remove index 2. + allocator.free_unchecked(entity_identifier); + } + assert_ok!(archetypes.insert(archetype)); + + archetypes + } + } + + assert_de_tokens( + &SeededAllocator::::new(allocator), + &[ + Token::Seq { len: Some(2) }, + Token::U64(3), + Token::Seq { len: Some(1) }, + Token::Struct { + name: "Identifier", + len: 2, + }, + Token::String("index"), + Token::U64(2), + Token::String("generation"), + Token::U64(1), + Token::StructEnd, + Token::SeqEnd, + Token::SeqEnd, + ], + ); + } + + #[test] + fn deserialize_missing_field_length() { + struct EmptySeed; + + impl Seed for EmptySeed + where + R: crate::registry::Registry, + { + fn archetypes() -> Archetypes { + Archetypes::new() + } + } + + assert_de_tokens_error::>( + &[ + Token::Struct { + name: "Allocator", + len: 2, + }, + Token::String("free"), + Token::Seq { len: Some(0) }, + Token::SeqEnd, + Token::StructEnd, + ], + "missing field `length`", + ); + } + + #[test] + fn deserialize_missing_field_free() { + struct EmptySeed; + + impl Seed for EmptySeed + where + R: crate::registry::Registry, + { + fn archetypes() -> Archetypes { + Archetypes::new() + } + } + + assert_de_tokens_error::>( + &[ + Token::Struct { + name: "Allocator", + len: 2, + }, + Token::String("length"), + Token::U64(0), + Token::StructEnd, + ], + "missing field `free`", + ); + } + + #[test] + fn deserialize_duplicate_field_length() { + struct EmptySeed; + + impl Seed for EmptySeed + where + R: crate::registry::Registry, + { + fn archetypes() -> Archetypes { + Archetypes::new() + } + } + + assert_de_tokens_error::>( + &[ + Token::Struct { + name: "Allocator", + len: 2, + }, + Token::String("length"), + Token::U64(0), + Token::String("free"), + Token::Seq { len: Some(0) }, + Token::SeqEnd, + Token::String("length"), + Token::U64(0), + ], + "duplicate field `length`", + ); + } + + #[test] + fn deserialize_duplicate_field_free() { + struct EmptySeed; + + impl Seed for EmptySeed + where + R: crate::registry::Registry, + { + fn archetypes() -> Archetypes { + Archetypes::new() + } + } + + assert_de_tokens_error::>( + &[ + Token::Struct { + name: "Allocator", + len: 2, + }, + Token::String("free"), + Token::Seq { len: Some(0) }, + Token::SeqEnd, + Token::String("length"), + Token::U64(0), + Token::String("free"), + Token::Seq { len: Some(0) }, + ], + "duplicate field `free`", + ); + } + + #[test] + fn deserialize_out_of_bounds_free_index() { + struct EmptySeed; + + impl Seed for EmptySeed + where + R: crate::registry::Registry, + { + fn archetypes() -> Archetypes { + Archetypes::new() + } + } + + assert_de_tokens_error::>( + &[ + Token::Struct { + name: "Allocator", + len: 2, + }, + Token::String("length"), + Token::U64(0), + Token::String("free"), + Token::Seq { len: Some(1) }, + Token::Struct { + name: "Identifier", + len: 2, + }, + Token::String("index"), + Token::U64(42), + Token::String("generation"), + Token::U64(0), + Token::StructEnd, + Token::SeqEnd, + Token::StructEnd, + ], + "freed entity index 42 is out of bounds", + ); + } + + #[test] + fn deserialize_duplicate_free_index() { + struct EmptySeed; + + impl Seed for EmptySeed + where + R: crate::registry::Registry, + { + fn archetypes() -> Archetypes { + Archetypes::new() + } + } + + assert_de_tokens_error::>( + &[ + Token::Struct { + name: "Allocator", + len: 2, + }, + Token::String("length"), + Token::U64(1), + Token::String("free"), + Token::Seq { len: Some(2) }, + Token::Struct { + name: "Identifier", + len: 2, + }, + Token::String("index"), + Token::U64(0), + Token::String("generation"), + Token::U64(0), + Token::StructEnd, + Token::Struct { + name: "Identifier", + len: 2, + }, + Token::String("index"), + Token::U64(0), + Token::String("generation"), + Token::U64(1), + Token::StructEnd, + Token::SeqEnd, + Token::StructEnd, + ], + "duplicate freed entity index 0", + ); + } + + #[test] + fn deserialize_out_of_bounds_archetype_index() { + struct PopulatedSeed; + + impl Seed for PopulatedSeed { + fn archetypes() -> Archetypes { + let mut archetypes = Archetypes::new(); + let mut allocator = Allocator::new(); + + let mut archetype = + Archetype::new(unsafe { archetype::Identifier::::new(vec![3]) }); + unsafe { + archetype.push(entity!(A, B), &mut allocator); + } + assert_ok!(archetypes.insert(archetype)); + + archetypes + } + } + + assert_de_tokens_error::>( + &[ + Token::Struct { + name: "Allocator", + len: 2, + }, + Token::String("length"), + Token::U64(0), + Token::String("free"), + Token::Seq { len: Some(0) }, + Token::SeqEnd, + Token::StructEnd, + ], + "archetype entity index 0 is out of bounds", + ); + } + + #[test] + fn deserialize_duplicate_archetype_index() { + struct PopulatedSeed; + + impl Seed for PopulatedSeed { + fn archetypes() -> Archetypes { + let mut archetypes = Archetypes::new(); + let mut allocator = Allocator::new(); + + let mut archetype = + Archetype::new(unsafe { archetype::Identifier::::new(vec![3]) }); + unsafe { + archetype.push(entity!(A, B), &mut allocator); + } + assert_ok!(archetypes.insert(archetype)); + + archetypes + } + } + + assert_de_tokens_error::>( + &[ + Token::Struct { + name: "Allocator", + len: 2, + }, + Token::String("length"), + Token::U64(1), + Token::String("free"), + Token::Seq { len: Some(1) }, + Token::Struct { + name: "Identifier", + len: 2, + }, + Token::String("index"), + Token::U64(0), + Token::String("generation"), + Token::U64(0), + Token::StructEnd, + Token::SeqEnd, + Token::StructEnd, + ], + "duplicate archetype entity index 0", + ); + } + + #[test] + fn deserialize_missing_index() { + struct EmptySeed; + + impl Seed for EmptySeed + where + R: crate::registry::Registry, + { + fn archetypes() -> Archetypes { + Archetypes::new() + } + } + + assert_de_tokens_error::>( + &[ + Token::Struct { + name: "Allocator", + len: 2, + }, + Token::String("length"), + Token::U64(1), + Token::String("free"), + Token::Seq { len: Some(0) }, + Token::SeqEnd, + Token::StructEnd, + ], + "missing entity index 0", + ); + } +} diff --git a/src/entity/allocator/location.rs b/src/entity/allocator/location.rs index 5768b251..d704bbbc 100644 --- a/src/entity/allocator/location.rs +++ b/src/entity/allocator/location.rs @@ -1,11 +1,16 @@ use crate::{archetype, registry::Registry}; use core::{fmt, fmt::Debug}; +/// Defines an entity's location. +/// +/// This is used by the entity allocator to map from an entity identifier to the actual entity. pub(crate) struct Location where R: Registry, { + /// The identifier of the archetype currently storing this entity. pub(crate) identifier: archetype::IdentifierRef, + /// The index of the entity within its archetype. pub(crate) index: usize, } @@ -13,6 +18,7 @@ impl Location where R: Registry, { + /// Creates a new location from an archetype identifier and an index within that archetype. pub(crate) fn new(identifier: archetype::IdentifierRef, index: usize) -> Self { Self { identifier, index } } @@ -52,3 +58,34 @@ where self.identifier == other.identifier && self.index == other.index } } + +#[cfg(test)] +mod tests { + use super::Location; + use crate::{archetype::Identifier, registry}; + use alloc::vec; + + macro_rules! create_components { + ($( $variants:ident ),*) => { + $( + struct $variants(f32); + )* + }; + } + + create_components!( + A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z + ); + + type Registry = + registry!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z); + + #[test] + fn new() { + let identifier = unsafe { Identifier::::new(vec![1, 2, 3, 0]) }; + let location = Location::new(unsafe { identifier.as_ref() }, 42); + + assert_eq!(location.identifier, unsafe { identifier.as_ref() }); + assert_eq!(location.index, 42); + } +} diff --git a/src/entity/allocator/slot.rs b/src/entity/allocator/slot.rs index 7abae8d7..e728b518 100644 --- a/src/entity/allocator/slot.rs +++ b/src/entity/allocator/slot.rs @@ -1,11 +1,24 @@ use crate::{entity::allocator::Location, registry::Registry}; use core::{fmt, fmt::Debug}; +/// An entry for a possibly allocated entity. +/// +/// If this slot has a stored location, then an entity is allocated at that location. If the +/// location is `None`, then the slot is free and can be used to store a new entity. When the slot +/// has a stored location, it is called "active". +/// +/// Slots are reused. To differentiate between different allocations that have shared the same +/// slot, a unique generation is used. Therefore, a unique entity is determined both by its slot +/// index and its slot's generation. pub(crate) struct Slot where R: Registry, { + /// The currently stored entity's generation. pub(crate) generation: u64, + /// The location of the entity, if one is currently allocated. + /// + /// A `None` value indicates no entity is allocated in this slot. pub(crate) location: Option>, } @@ -76,3 +89,68 @@ where self.generation == other.generation && self.location == other.location } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{archetype::Identifier, registry}; + use alloc::vec; + use claim::{assert_none, assert_some_eq}; + + macro_rules! create_components { + ($( $variants:ident ),*) => { + $( + struct $variants(f32); + )* + }; + } + + create_components!( + A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z + ); + + type Registry = + registry!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z); + + #[test] + fn new() { + let identifier = unsafe { Identifier::::new(vec![1, 2, 3, 0]) }; + let location = Location::new(unsafe { identifier.as_ref() }, 42); + let slot = Slot::new(location); + + assert_eq!(slot.generation, 0); + assert_some_eq!(slot.location, location); + assert!(slot.is_active()); + } + + #[test] + fn deactivate() { + let identifier = unsafe { Identifier::::new(vec![1, 2, 3, 0]) }; + let location = Location::new(unsafe { identifier.as_ref() }, 42); + let mut slot = Slot::new(location); + + slot.deactivate(); + + assert_eq!(slot.generation, 0); + assert_none!(slot.location); + assert!(!slot.is_active()); + } + + #[test] + fn activate_unchecked() { + let identifier = unsafe { Identifier::::new(vec![1, 2, 3, 0]) }; + let location = Location::new(unsafe { identifier.as_ref() }, 42); + let mut slot = Slot::new(location); + + slot.deactivate(); + let new_identifier = unsafe { Identifier::::new(vec![3, 2, 1, 0]) }; + let new_location = Location::new(unsafe { new_identifier.as_ref() }, 42); + unsafe { + slot.activate_unchecked(new_location); + } + + assert_eq!(slot.generation, 1); + assert_some_eq!(slot.location, new_location); + assert!(slot.is_active()); + } +} diff --git a/src/entity/mod.rs b/src/entity/mod.rs index 670da177..90bfdda8 100644 --- a/src/entity/mod.rs +++ b/src/entity/mod.rs @@ -111,3 +111,19 @@ macro_rules! entity { $crate::entity::Null }; } + +#[cfg(test)] +mod tests { + use super::*; + + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + struct A(u64); + + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + struct B(char); + + #[test] + fn entity() { + assert_eq!(entity!(B('f'), A(42)), (B('f'), (A(42), Null))); + } +} diff --git a/src/hlist.rs b/src/hlist.rs index 50b9d9a3..84094ba6 100644 --- a/src/hlist.rs +++ b/src/hlist.rs @@ -70,3 +70,33 @@ macro_rules! define_null_uninstantiable { pub(crate) use define_null; pub(crate) use define_null_uninstantiable; + +#[cfg(test)] +mod tests { + use super::*; + #[cfg(feature = "serde")] + use serde_test::{assert_de_tokens, assert_de_tokens_error, assert_tokens, Token}; + + define_null!(); + + #[cfg(feature = "serde")] + #[test] + fn serialize_deserialize() { + assert_tokens(&Null, &[Token::UnitStruct { name: "Null" }]); + } + + #[cfg(feature = "serde")] + #[test] + fn deserialize_from_unit() { + assert_de_tokens(&Null, &[Token::Unit]); + } + + #[cfg(feature = "serde")] + #[test] + fn deserialize_from_invalid_type() { + assert_de_tokens_error::( + &[Token::U32(42)], + "invalid type: integer `42`, expected struct Null", + ); + } +} diff --git a/src/query/claim.rs b/src/query/claim.rs index 2c5bf3d8..76660362 100644 --- a/src/query/claim.rs +++ b/src/query/claim.rs @@ -87,3 +87,109 @@ where W::claim(mutable_claims, immutable_claims); } } + +#[cfg(test)] +mod tests { + use super::*; + use hashbrown::HashSet; + + struct A; + struct B; + + #[test] + fn claim_ref() { + let mut mutable_claims = HashSet::with_hasher(FnvBuildHasher::default()); + let mut immutable_claims = HashSet::with_hasher(FnvBuildHasher::default()); + + <&A>::claim(&mut mutable_claims, &mut immutable_claims); + + let expected_mutable_claims = HashSet::with_hasher(FnvBuildHasher::default()); + let mut expected_immutable_claims = HashSet::with_hasher(FnvBuildHasher::default()); + expected_immutable_claims.insert(TypeId::of::()); + assert_eq!(mutable_claims, expected_mutable_claims); + assert_eq!(immutable_claims, expected_immutable_claims); + } + + #[test] + fn claim_mut_ref() { + let mut mutable_claims = HashSet::with_hasher(FnvBuildHasher::default()); + let mut immutable_claims = HashSet::with_hasher(FnvBuildHasher::default()); + + <&mut A>::claim(&mut mutable_claims, &mut immutable_claims); + + let mut expected_mutable_claims = HashSet::with_hasher(FnvBuildHasher::default()); + let expected_immutable_claims = HashSet::with_hasher(FnvBuildHasher::default()); + expected_mutable_claims.insert(TypeId::of::()); + assert_eq!(mutable_claims, expected_mutable_claims); + assert_eq!(immutable_claims, expected_immutable_claims); + } + + #[test] + fn claim_option() { + let mut mutable_claims = HashSet::with_hasher(FnvBuildHasher::default()); + let mut immutable_claims = HashSet::with_hasher(FnvBuildHasher::default()); + + >::claim(&mut mutable_claims, &mut immutable_claims); + + let expected_mutable_claims = HashSet::with_hasher(FnvBuildHasher::default()); + let mut expected_immutable_claims = HashSet::with_hasher(FnvBuildHasher::default()); + expected_immutable_claims.insert(TypeId::of::()); + assert_eq!(mutable_claims, expected_mutable_claims); + assert_eq!(immutable_claims, expected_immutable_claims); + } + + #[test] + fn claim_mut_option() { + let mut mutable_claims = HashSet::with_hasher(FnvBuildHasher::default()); + let mut immutable_claims = HashSet::with_hasher(FnvBuildHasher::default()); + + >::claim(&mut mutable_claims, &mut immutable_claims); + + let mut expected_mutable_claims = HashSet::with_hasher(FnvBuildHasher::default()); + let expected_immutable_claims = HashSet::with_hasher(FnvBuildHasher::default()); + expected_mutable_claims.insert(TypeId::of::()); + assert_eq!(mutable_claims, expected_mutable_claims); + assert_eq!(immutable_claims, expected_immutable_claims); + } + + #[test] + fn claim_entity_identifier() { + let mut mutable_claims = HashSet::with_hasher(FnvBuildHasher::default()); + let mut immutable_claims = HashSet::with_hasher(FnvBuildHasher::default()); + + ::claim(&mut mutable_claims, &mut immutable_claims); + + let expected_mutable_claims = HashSet::with_hasher(FnvBuildHasher::default()); + let expected_immutable_claims = HashSet::with_hasher(FnvBuildHasher::default()); + assert_eq!(mutable_claims, expected_mutable_claims); + assert_eq!(immutable_claims, expected_immutable_claims); + } + + #[test] + fn claim_view_null() { + let mut mutable_claims = HashSet::with_hasher(FnvBuildHasher::default()); + let mut immutable_claims = HashSet::with_hasher(FnvBuildHasher::default()); + + ::claim(&mut mutable_claims, &mut immutable_claims); + + let expected_mutable_claims = HashSet::with_hasher(FnvBuildHasher::default()); + let expected_immutable_claims = HashSet::with_hasher(FnvBuildHasher::default()); + assert_eq!(mutable_claims, expected_mutable_claims); + assert_eq!(immutable_claims, expected_immutable_claims); + } + + #[test] + fn claim_hlist() { + let mut mutable_claims = HashSet::with_hasher(FnvBuildHasher::default()); + let mut immutable_claims = HashSet::with_hasher(FnvBuildHasher::default()); + + <(&A, (&mut B, view::Null))>::claim(&mut mutable_claims, &mut immutable_claims); + + let mut expected_mutable_claims = HashSet::with_hasher(FnvBuildHasher::default()); + let mut expected_immutable_claims = HashSet::with_hasher(FnvBuildHasher::default()); + expected_immutable_claims.insert(TypeId::of::()); + expected_mutable_claims.insert(TypeId::of::()); + assert_eq!(mutable_claims, expected_mutable_claims); + assert_eq!(immutable_claims, expected_immutable_claims); + } +} diff --git a/src/query/filter/seal.rs b/src/query/filter/seal.rs index 6e3486ba..77c711c6 100644 --- a/src/query/filter/seal.rs +++ b/src/query/filter/seal.rs @@ -217,3 +217,231 @@ where unsafe { And::::filter(identifier, component_map) } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{query::views, registry}; + use alloc::vec; + + struct A; + struct B; + + type Registry = registry!(A, B); + + #[test] + fn filter_none() { + assert!(unsafe { + None::filter::( + archetype::Identifier::new(vec![1]).as_ref(), + &HashMap::with_hasher(FnvBuildHasher::default()), + ) + }); + } + + #[test] + fn filter_has_true() { + let mut component_map = HashMap::with_hasher(FnvBuildHasher::default()); + component_map.insert(TypeId::of::(), 0); + + assert!(unsafe { + Has::::filter::( + archetype::Identifier::new(vec![1]).as_ref(), + &component_map, + ) + }); + } + + #[test] + fn filter_has_false() { + let mut component_map = HashMap::with_hasher(FnvBuildHasher::default()); + component_map.insert(TypeId::of::(), 0); + + assert!(!unsafe { + Has::::filter::( + archetype::Identifier::new(vec![1]).as_ref(), + &component_map, + ) + }); + } + + #[test] + fn not() { + assert!(!unsafe { + Not::::filter::( + archetype::Identifier::new(vec![1]).as_ref(), + &HashMap::with_hasher(FnvBuildHasher::default()), + ) + }); + } + + #[test] + fn and() { + let mut component_map = HashMap::with_hasher(FnvBuildHasher::default()); + component_map.insert(TypeId::of::(), 0); + + assert!(unsafe { + And::>::filter::( + archetype::Identifier::new(vec![1]).as_ref(), + &component_map, + ) + }); + } + + #[test] + fn or() { + let mut component_map = HashMap::with_hasher(FnvBuildHasher::default()); + component_map.insert(TypeId::of::(), 0); + + assert!(unsafe { + Or::, Has>::filter::( + archetype::Identifier::new(vec![1]).as_ref(), + &component_map, + ) + }); + } + + #[test] + fn ref_true() { + let mut component_map = HashMap::with_hasher(FnvBuildHasher::default()); + component_map.insert(TypeId::of::(), 0); + + assert!(unsafe { + <&A>::filter::(archetype::Identifier::new(vec![1]).as_ref(), &component_map) + }); + } + + #[test] + fn ref_false() { + let mut component_map = HashMap::with_hasher(FnvBuildHasher::default()); + component_map.insert(TypeId::of::(), 0); + + assert!(!unsafe { + <&B>::filter::(archetype::Identifier::new(vec![1]).as_ref(), &component_map) + }); + } + + #[test] + fn mut_ref_true() { + let mut component_map = HashMap::with_hasher(FnvBuildHasher::default()); + component_map.insert(TypeId::of::(), 0); + + assert!(unsafe { + <&mut A>::filter::( + archetype::Identifier::new(vec![1]).as_ref(), + &component_map, + ) + }); + } + + #[test] + fn mut_ref_false() { + let mut component_map = HashMap::with_hasher(FnvBuildHasher::default()); + component_map.insert(TypeId::of::(), 0); + + assert!(!unsafe { + <&mut B>::filter::( + archetype::Identifier::new(vec![1]).as_ref(), + &component_map, + ) + }); + } + + #[test] + fn option_contains() { + let mut component_map = HashMap::with_hasher(FnvBuildHasher::default()); + component_map.insert(TypeId::of::(), 0); + + assert!(unsafe { + as Seal>::filter::( + archetype::Identifier::new(vec![1]).as_ref(), + &component_map, + ) + }); + } + + #[test] + fn option_not_contains() { + let mut component_map = HashMap::with_hasher(FnvBuildHasher::default()); + component_map.insert(TypeId::of::(), 0); + + assert!(unsafe { + as Seal>::filter::( + archetype::Identifier::new(vec![1]).as_ref(), + &component_map, + ) + }); + } + + #[test] + fn option_mut_contains() { + let mut component_map = HashMap::with_hasher(FnvBuildHasher::default()); + component_map.insert(TypeId::of::(), 0); + + assert!(unsafe { + as Seal>::filter::( + archetype::Identifier::new(vec![1]).as_ref(), + &component_map, + ) + }); + } + + #[test] + fn option_mut_not_contains() { + let mut component_map = HashMap::with_hasher(FnvBuildHasher::default()); + component_map.insert(TypeId::of::(), 0); + + assert!(unsafe { + as Seal>::filter::( + archetype::Identifier::new(vec![1]).as_ref(), + &component_map, + ) + }); + } + + #[test] + fn entity_identifier() { + assert!(unsafe { + entity::Identifier::filter::( + archetype::Identifier::new(vec![1]).as_ref(), + &HashMap::with_hasher(FnvBuildHasher::default()), + ) + }); + } + + #[test] + fn view_null() { + assert!(unsafe { + view::Null::filter::( + archetype::Identifier::new(vec![1]).as_ref(), + &HashMap::with_hasher(FnvBuildHasher::default()), + ) + }); + } + + #[test] + fn views_true() { + let mut component_map = HashMap::with_hasher(FnvBuildHasher::default()); + component_map.insert(TypeId::of::(), 0); + + assert!(unsafe { + ::filter::( + archetype::Identifier::new(vec![1]).as_ref(), + &component_map, + ) + }); + } + + #[test] + fn views_false() { + let mut component_map = HashMap::with_hasher(FnvBuildHasher::default()); + component_map.insert(TypeId::of::(), 0); + + assert!(!unsafe { + ::filter::( + archetype::Identifier::new(vec![1]).as_ref(), + &component_map, + ) + }); + } +} diff --git a/src/query/view/mod.rs b/src/query/view/mod.rs index e4a38d85..c1c9f71d 100644 --- a/src/query/view/mod.rs +++ b/src/query/view/mod.rs @@ -186,7 +186,7 @@ doc::non_root_macro! { /// [`World`]: crate::world::World macro_rules! views { ($view:ty $(,$views:ty)* $(,)?) => ( - ($view, $crate::views!($($views,)*)) + ($view, $crate::query::view::views!($($views,)*)) ); () => ( $crate::query::view::Null diff --git a/src/registry/eq.rs b/src/registry/eq.rs index 5d8b99be..9ce41f94 100644 --- a/src/registry/eq.rs +++ b/src/registry/eq.rs @@ -189,7 +189,7 @@ mod tests { let mut a_column_a = vec![A(0), A(1), A(2)]; let mut b_column_a = vec![B(false), B(true), B(true)]; let mut c_column_a = vec![C, C, C]; - let mut components_a = vec![ + let components_a = vec![ (a_column_a.as_mut_ptr().cast::(), a_column_a.capacity()), (b_column_a.as_mut_ptr().cast::(), b_column_a.capacity()), (c_column_a.as_mut_ptr().cast::(), c_column_a.capacity()), @@ -197,7 +197,7 @@ mod tests { let mut a_column_b = vec![A(0), A(1), A(2)]; let mut b_column_b = vec![B(false), B(true), B(true)]; let mut c_column_b = vec![C, C, C]; - let mut components_b = vec![ + let components_b = vec![ (a_column_b.as_mut_ptr().cast::(), a_column_b.capacity()), (b_column_b.as_mut_ptr().cast::(), b_column_b.capacity()), (c_column_b.as_mut_ptr().cast::(), c_column_b.capacity()), @@ -221,7 +221,7 @@ mod tests { let mut a_column_a = vec![A(0), A(1), A(2)]; let mut b_column_a = vec![B(false), B(true), B(true)]; let mut c_column_a = vec![C, C, C]; - let mut components_a = vec![ + let components_a = vec![ (a_column_a.as_mut_ptr().cast::(), a_column_a.capacity()), (b_column_a.as_mut_ptr().cast::(), b_column_a.capacity()), (c_column_a.as_mut_ptr().cast::(), c_column_a.capacity()), @@ -229,7 +229,7 @@ mod tests { let mut a_column_b = vec![A(0), A(1), A(2)]; let mut b_column_b = vec![B(false), B(false), B(true)]; let mut c_column_b = vec![C, C, C]; - let mut components_b = vec![ + let components_b = vec![ (a_column_b.as_mut_ptr().cast::(), a_column_b.capacity()), (b_column_b.as_mut_ptr().cast::(), b_column_b.capacity()), (c_column_b.as_mut_ptr().cast::(), c_column_b.capacity()), diff --git a/src/registry/serde.rs b/src/registry/serde.rs index 8416b55f..15f6c7b8 100644 --- a/src/registry/serde.rs +++ b/src/registry/serde.rs @@ -4,9 +4,9 @@ use crate::{ component::Component, registry::{Null, Registry}, }; -use ::serde::{de, de::SeqAccess, ser::SerializeTuple, Deserialize, Serialize}; -use alloc::{format, vec::Vec}; +use alloc::{format, string::String, vec::Vec}; use core::{any::type_name, mem::ManuallyDrop}; +use serde::{de, de::SeqAccess, ser::SerializeTuple, Deserialize, Serialize}; #[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))] pub trait RegistrySerialize: Registry { @@ -232,9 +232,11 @@ pub trait RegistryDeserialize<'de>: Registry + 'de { length: usize, seq: &mut V, identifier_iter: archetype::identifier::Iter, + current_index: usize, + identifier: archetype::IdentifierRef, ) -> Result<(), V::Error> where - R: Registry, + R: RegistryDeserialize<'de>, V: SeqAccess<'de>; /// # Safety @@ -255,10 +257,24 @@ pub trait RegistryDeserialize<'de>: Registry + 'de { length: usize, seq: &mut V, identifier_iter: archetype::identifier::Iter, + current_index: usize, + identifier: archetype::IdentifierRef, ) -> Result<(), V::Error> where - R: Registry, + R: RegistryDeserialize<'de>, V: SeqAccess<'de>; + + /// # Safety + /// When called externally, the `Registry` `R` provided to the method must by the same as the + /// `Registry` on which this method is being called. + /// + /// When called internally, the `identifier_iter` must have the same amount of bits left as + /// there are components remaining. + unsafe fn expected_row_component_names( + names: &mut String, + identifier_iter: archetype::identifier::Iter, + ) where + R: Registry; } impl<'de> RegistryDeserialize<'de> for Null { @@ -267,9 +283,11 @@ impl<'de> RegistryDeserialize<'de> for Null { _length: usize, _seq: &mut V, _identifier_iter: archetype::identifier::Iter, + _current_index: usize, + _identifier: archetype::IdentifierRef, ) -> Result<(), V::Error> where - R: Registry, + R: RegistryDeserialize<'de>, V: SeqAccess<'de>, { Ok(()) @@ -280,13 +298,23 @@ impl<'de> RegistryDeserialize<'de> for Null { _length: usize, _seq: &mut V, _identifier_iter: archetype::identifier::Iter, + _current_index: usize, + _identifier: archetype::IdentifierRef, ) -> Result<(), V::Error> where - R: Registry, + R: RegistryDeserialize<'de>, V: SeqAccess<'de>, { Ok(()) } + + unsafe fn expected_row_component_names( + _names: &mut String, + _identifier_iter: archetype::identifier::Iter, + ) where + R: Registry, + { + } } impl<'de, C, R> RegistryDeserialize<'de> for (C, R) @@ -299,9 +327,11 @@ where length: usize, seq: &mut V, mut identifier_iter: archetype::identifier::Iter, + current_index: usize, + identifier: archetype::IdentifierRef, ) -> Result<(), V::Error> where - R_: Registry, + R_: RegistryDeserialize<'de>, V: SeqAccess<'de>, { if @@ -312,14 +342,35 @@ where let component_column = seq .next_element_seed(DeserializeColumn::::new(length))? .ok_or_else(|| { - de::Error::custom(format!("expected a column of type `{}`", type_name::())) + de::Error::invalid_length( + current_index + 1, + &format!("columns for each of (entity::Identifier{})", { + let mut names = String::new(); + // SAFETY: The identifier iter passed here contains the same amount of + // bits as there are components in `R_`. + unsafe { + R_::expected_row_component_names(&mut names, identifier.iter()); + } + names + }) + .as_str(), + ) })?; components.push((component_column.0.cast::(), component_column.1)); } // SAFETY: Since one bit was consumed from `identifier_iter`, it still has the same number // of bits remaining as there are components remaining. - unsafe { R::deserialize_components_by_column(components, length, seq, identifier_iter) } + unsafe { + R::deserialize_components_by_column( + components, + length, + seq, + identifier_iter, + current_index + 1, + identifier, + ) + } } unsafe fn deserialize_components_by_row( @@ -327,9 +378,11 @@ where length: usize, seq: &mut V, mut identifier_iter: archetype::identifier::Iter, + current_index: usize, + identifier: archetype::IdentifierRef, ) -> Result<(), V::Error> where - R_: Registry, + R_: RegistryDeserialize<'de>, V: SeqAccess<'de>, { if @@ -355,7 +408,17 @@ where v.push(seq.next_element()?.ok_or_else(|| { // TODO: the length returned here is incorrect. - de::Error::invalid_length(0, &"`length` components for each column") + de::Error::invalid_length( + current_index + 1, + &format!("(entity::Identifier{})", { + let mut names = String::new(); + // SAFETY: The identifier iter passed here contains the same amount of bits + // as there are components in `R_`. + unsafe { R_::expected_row_component_names(&mut names, identifier.iter()) }; + names + }) + .as_str(), + ) })?); component_column.0 = v.as_mut_ptr().cast::(); component_column.1 = v.capacity(); @@ -384,6 +447,73 @@ where // Furthermore, regardless of whether the bit was set or not, `R` is one component smaller // than `(C, R)`, and since `identifier_iter` has had one bit consumed, it still has the // same number of bits remaining as `R` has components remaining. - unsafe { R::deserialize_components_by_row(components, length, seq, identifier_iter) } + unsafe { + R::deserialize_components_by_row( + components, + length, + seq, + identifier_iter, + current_index + 1, + identifier, + ) + } + } + + unsafe fn expected_row_component_names( + names: &mut String, + mut identifier_iter: archetype::identifier::Iter, + ) where + R_: Registry, + { + if + // SAFETY: `identifier_iter` is guaranteed by the safety contract of this method to + // return a value for every component within the registry. + unsafe { identifier_iter.next().unwrap_unchecked() } { + names.push_str(", "); + names.push_str(type_name::()); + } + + // SAFETY: Since one bit was consumed in `identifier_iter`, there are the same number of + // bits remaining as there are components in `R`. + unsafe { R::expected_row_component_names(names, identifier_iter) } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::registry; + use alloc::vec; + use serde_derive::Deserialize; + + #[derive(Deserialize)] + struct A; + #[derive(Deserialize)] + struct B; + #[derive(Deserialize)] + struct C; + type Registry = registry!(A, B, C); + + #[test] + fn expected_row_component_names_empty() { + let identifier = unsafe { archetype::Identifier::::new(vec![0]) }; + + let mut result = String::new(); + unsafe { Registry::expected_row_component_names(&mut result, identifier.iter()) }; + + assert_eq!(result, ""); + } + + #[test] + fn expected_row_component_names_some() { + let identifier = unsafe { archetype::Identifier::::new(vec![5]) }; + + let mut result = String::new(); + unsafe { Registry::expected_row_component_names(&mut result, identifier.iter()) }; + + assert_eq!( + result, + format!(", {}, {}", type_name::(), type_name::()) + ); } } diff --git a/src/world/entry.rs b/src/world/entry.rs index b808bb84..321de886 100644 --- a/src/world/entry.rs +++ b/src/world/entry.rs @@ -1,7 +1,11 @@ use crate::{ - archetype, component::Component, entity::allocator::Location, registry::Registry, world::World, + archetype, + component::Component, + entity::allocator::Location, + registry::{Registry, RegistryDebug}, + world::World, }; -use core::any::TypeId; +use core::{any::TypeId, fmt, fmt::Debug}; /// A view into a single entity in a [`World`]. /// @@ -248,3 +252,15 @@ where // todo!() // } } + +impl<'a, R> Debug for Entry<'a, R> +where + R: RegistryDebug, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Entry") + .field("world", self.world) + .field("location", &self.location) + .finish() + } +} diff --git a/src/world/impl_default.rs b/src/world/impl_default.rs index f05ace78..65feafd5 100644 --- a/src/world/impl_default.rs +++ b/src/world/impl_default.rs @@ -8,3 +8,16 @@ where Self::new() } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::registry; + + type Registry = registry!(); + + #[test] + fn default() { + assert_eq!(World::::default(), World::::new()); + } +} diff --git a/src/world/impl_eq.rs b/src/world/impl_eq.rs index ebcdd3bd..efcc40e6 100644 --- a/src/world/impl_eq.rs +++ b/src/world/impl_eq.rs @@ -16,5 +16,72 @@ impl Eq for World where R: RegistryEq {} #[cfg(test)] mod tests { - // TODO + use super::*; + use crate::{entity, registry}; + + #[derive(Debug, Eq, PartialEq)] + struct A(u32); + + #[derive(Debug, Eq, PartialEq)] + struct B(char); + + type Registry = registry!(A, B); + + #[test] + fn empty_eq() { + assert_eq!(World::::new(), World::::new()); + } + + #[test] + fn with_entities_eq() { + let mut world_a = World::::new(); + let mut world_b = World::::new(); + + world_a.insert(entity!(A(1), B('a'))); + world_a.insert(entity!(A(2), B('b'))); + world_a.insert(entity!(A(3), B('c'))); + world_a.insert(entity!(A(4))); + world_a.insert(entity!(A(5))); + world_a.insert(entity!()); + + world_b.insert(entity!(A(1), B('a'))); + world_b.insert(entity!(A(2), B('b'))); + world_b.insert(entity!(A(3), B('c'))); + world_b.insert(entity!(A(4))); + world_b.insert(entity!(A(5))); + world_b.insert(entity!()); + + assert_eq!(world_a, world_b); + } + + #[test] + fn archetypes_not_equal() { + let mut world_a = World::::new(); + let mut world_b = World::::new(); + + world_a.insert(entity!(A(1), B('a'))); + world_a.insert(entity!(A(2), B('b'))); + world_a.insert(entity!(A(3), B('c'))); + + world_b.insert(entity!(A(1))); + world_b.insert(entity!(A(2))); + world_b.insert(entity!(A(3))); + + assert_ne!(world_a, world_b); + } + + #[test] + fn allocators_not_equal() { + let mut world_a = World::::new(); + let mut world_b = World::::new(); + + world_a.insert(entity!(A(1), B('a'))); + + let entity_identifier = world_b.insert(entity!(A(1), B('a'))); + world_b.remove(entity_identifier); + world_b.insert(entity!(A(1), B('a'))); + + // The generational index of the entities will be different. + assert_ne!(world_a, world_b); + } } diff --git a/src/world/impl_serde.rs b/src/world/impl_serde.rs index 94043077..fd37d49f 100644 --- a/src/world/impl_serde.rs +++ b/src/world/impl_serde.rs @@ -61,9 +61,7 @@ where .next_element_seed(DeserializeArchetypes::new(&mut len))? .ok_or_else(|| de::Error::invalid_length(0, &self))?; let entity_allocator = seq - .next_element_seed(DeserializeAllocator { - archetypes: &archetypes, - })? + .next_element_seed(DeserializeAllocator::new(&archetypes))? .ok_or_else(|| de::Error::invalid_length(1, &self))?; Ok(World::from_raw_parts(archetypes, entity_allocator, len)) } @@ -77,3 +75,263 @@ where ) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{entity, registry}; + use serde_derive::{Deserialize, Serialize}; + use serde_test::{assert_de_tokens_error, assert_tokens, Compact, Configure, Token}; + + #[derive(Debug, Deserialize, Eq, PartialEq, Serialize)] + struct A(u32); + + #[derive(Debug, Deserialize, Eq, PartialEq, Serialize)] + struct B(char); + + type Registry = registry!(A, B); + + #[test] + fn serialize_deserialize_empty() { + let world = World::::new(); + + assert_tokens( + &world, + &[ + Token::Tuple { len: 2 }, + // Archetypes + Token::Seq { len: Some(0) }, + Token::SeqEnd, + // Entity Allocator + Token::Struct { + name: "Allocator", + len: 2, + }, + Token::String("length"), + Token::U64(0), + Token::String("free"), + Token::Seq { len: Some(0) }, + Token::SeqEnd, + Token::StructEnd, + Token::TupleEnd, + ], + ); + } + + #[test] + fn serialize_deserialize_after_mutation() { + let mut world = World::::new(); + + let entity_identifier = world.insert(entity!(A(1), B('a'))); + world.remove(entity_identifier); + world.insert(entity!(A(2), B('b'))); + world.insert(entity!(A(3), B('c'))); + world.insert(entity!(A(4), B('d'))); + world.insert(entity!(A(5))); + world.insert(entity!(A(6))); + world.insert(entity!()); + let entity_identifier = world.insert(entity!(B('g'))); + world.remove(entity_identifier); + let entity_identifier = world.insert(entity!(B('h'))); + world.remove(entity_identifier); + + assert_tokens( + &world.compact(), + &[ + Token::Tuple { len: 2 }, + // Archetypes + Token::Seq { len: Some(4) }, + // B Archetype + Token::NewtypeStruct { name: "Archetype" }, + Token::Tuple { len: 3 }, + // Identifier + Token::Tuple { len: 1 }, + Token::U8(2), + Token::TupleEnd, + // Length + Token::U64(0), + // Columns + Token::Tuple { len: 2 }, + // Entity identifiers + Token::Tuple { len: 0 }, + Token::TupleEnd, + // B column + Token::Tuple { len: 0 }, + Token::TupleEnd, + Token::TupleEnd, + Token::TupleEnd, + // No component Archetype + Token::NewtypeStruct { name: "Archetype" }, + Token::Tuple { len: 3 }, + // Identifier + Token::Tuple { len: 1 }, + Token::U8(0), + Token::TupleEnd, + // Length + Token::U64(1), + // Columns + Token::Tuple { len: 1 }, + // Entity identifiers + Token::Tuple { len: 1 }, + Token::Struct { + name: "Identifier", + len: 2, + }, + Token::String("index"), + Token::U64(5), + Token::String("generation"), + Token::U64(0), + Token::StructEnd, + Token::TupleEnd, + Token::TupleEnd, + Token::TupleEnd, + // AB Archetype + Token::NewtypeStruct { name: "Archetype" }, + Token::Tuple { len: 3 }, + // Identifier + Token::Tuple { len: 1 }, + Token::U8(3), + Token::TupleEnd, + // Length + Token::U64(3), + // Columns + Token::Tuple { len: 3 }, + // Entity identifiers + Token::Tuple { len: 3 }, + Token::Struct { + name: "Identifier", + len: 2, + }, + Token::String("index"), + Token::U64(0), + Token::String("generation"), + Token::U64(1), + Token::StructEnd, + Token::Struct { + name: "Identifier", + len: 2, + }, + Token::String("index"), + Token::U64(1), + Token::String("generation"), + Token::U64(0), + Token::StructEnd, + Token::Struct { + name: "Identifier", + len: 2, + }, + Token::String("index"), + Token::U64(2), + Token::String("generation"), + Token::U64(0), + Token::StructEnd, + Token::TupleEnd, + // A column + Token::Tuple { len: 3 }, + Token::NewtypeStruct { name: "A" }, + Token::U32(2), + Token::NewtypeStruct { name: "A" }, + Token::U32(3), + Token::NewtypeStruct { name: "A" }, + Token::U32(4), + Token::TupleEnd, + // B column + Token::Tuple { len: 3 }, + Token::NewtypeStruct { name: "B" }, + Token::Char('b'), + Token::NewtypeStruct { name: "B" }, + Token::Char('c'), + Token::NewtypeStruct { name: "B" }, + Token::Char('d'), + Token::TupleEnd, + Token::TupleEnd, + Token::TupleEnd, + // A Archetype + Token::NewtypeStruct { name: "Archetype" }, + Token::Tuple { len: 3 }, + // Identifier + Token::Tuple { len: 1 }, + Token::U8(1), + Token::TupleEnd, + // Length + Token::U64(2), + // Columns + Token::Tuple { len: 2 }, + // Entity identifiers + Token::Tuple { len: 2 }, + Token::Struct { + name: "Identifier", + len: 2, + }, + Token::String("index"), + Token::U64(3), + Token::String("generation"), + Token::U64(0), + Token::StructEnd, + Token::Struct { + name: "Identifier", + len: 2, + }, + Token::String("index"), + Token::U64(4), + Token::String("generation"), + Token::U64(0), + Token::StructEnd, + Token::TupleEnd, + // A column + Token::Tuple { len: 2 }, + Token::NewtypeStruct { name: "A" }, + Token::U32(5), + Token::NewtypeStruct { name: "A" }, + Token::U32(6), + Token::TupleEnd, + Token::TupleEnd, + Token::TupleEnd, + Token::SeqEnd, + // Entity Allocator + Token::Struct { + name: "Allocator", + len: 2, + }, + Token::String("length"), + Token::U64(7), + Token::String("free"), + Token::Seq { len: Some(1) }, + Token::Struct { + name: "Identifier", + len: 2, + }, + Token::String("index"), + Token::U64(6), + Token::String("generation"), + Token::U64(1), + Token::StructEnd, + Token::SeqEnd, + Token::StructEnd, + Token::TupleEnd, + ], + ); + } + + #[test] + fn deserialize_missing_archetypes() { + assert_de_tokens_error::>>( + &[Token::Tuple { len: 0 }, Token::TupleEnd], + "invalid length 0, expected serialized World", + ); + } + + #[test] + fn deserialize_missing_entity_allocator() { + assert_de_tokens_error::>>( + &[ + Token::Tuple { len: 0 }, + // Archetypes + Token::Seq { len: Some(0) }, + Token::SeqEnd, + Token::TupleEnd, + ], + "invalid length 1, expected serialized World", + ); + } +} diff --git a/src/world/mod.rs b/src/world/mod.rs index a300eb49..c12f5a93 100644 --- a/src/world/mod.rs +++ b/src/world/mod.rs @@ -627,9 +627,9 @@ where unsafe { self.entity_allocator.free_unchecked(entity_identifier); } - } - self.len -= 1; + self.len -= 1; + } } /// Removes all entities. @@ -706,3 +706,1141 @@ where self.len() == 0 } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + entities, entity, + query::{filter, result, views}, + registry, + }; + use claim::{assert_none, assert_some}; + #[cfg(feature = "rayon")] + use rayon::iter::ParallelIterator; + + #[derive(Clone, Debug)] + struct A(u32); + + #[derive(Clone, Debug)] + struct B(char); + + type Registry = registry!(A, B); + + #[test] + fn insert() { + let mut world = World::::new(); + + world.insert(entity!(A(42), B('f'))); + } + + #[test] + fn insert_different_entity_types() { + let mut world = World::::new(); + + world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + } + + #[test] + #[should_panic] + fn insert_with_nonexistant_component() { + struct C; + + let mut world = World::::new(); + + // `C` isn't in the `Registry`. + world.insert(entity!(C)); + } + + #[test] + fn extend() { + let mut world = World::::new(); + + world.extend(entities!((A(42), B('f')); 100)); + } + + #[test] + fn extend_different_entity_types() { + let mut world = World::::new(); + + world.extend(entities!((A(1), B('a')); 100)); + world.extend(entities!((A(2)); 200)); + world.extend(entities!((B('b')); 300)); + world.extend(entities!((); 400)); + } + + #[test] + #[should_panic] + fn extend_with_nonexistant_component() { + #[derive(Clone)] + struct C; + + let mut world = World::::new(); + + // `C` isn't in the `Registry`. + world.extend(entities!((C); 100)); + } + + #[test] + fn query_refs() { + let mut world = World::::new(); + + world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + let mut result = world + .query::() + .map(|result!(a)| a.0) + .collect::>(); + result.sort(); + assert_eq!(result, vec![1, 2]); + } + + #[test] + fn query_mut_refs() { + let mut world = World::::new(); + + world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + let mut result = world + .query::() + .map(|result!(b)| b.0) + .collect::>(); + result.sort(); + assert_eq!(result, vec!['a', 'b']); + } + + #[test] + fn query_option_refs() { + let mut world = World::::new(); + + world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + let mut result = world + .query::), filter::None>() + .map(|result!(a)| a.map(|a| a.0)) + .collect::>(); + result.sort(); + assert_eq!(result, vec![None, None, Some(1), Some(2)]); + } + + #[test] + fn query_option_mut_refs() { + let mut world = World::::new(); + + world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + let mut result = world + .query::), filter::None>() + .map(|result!(b)| b.map(|b| b.0)) + .collect::>(); + result.sort(); + assert_eq!(result, vec![None, None, Some('a'), Some('b')]); + } + + #[test] + fn query_entity_identifiers() { + let mut world = World::::new(); + + let entity_identifier = world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + let result = world + .query::, filter::Has>>() + .map(|result!(identifier)| identifier) + .collect::>(); + assert_eq!(result, vec![entity_identifier]); + } + + #[test] + fn query_has_filter() { + let mut world = World::::new(); + + world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + let result = world + .query::>() + .map(|result!(a)| a.0) + .collect::>(); + assert_eq!(result, vec![1]); + } + + #[test] + fn query_not_filter() { + let mut world = World::::new(); + + world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + let result = world + .query::>>() + .map(|result!(a)| a.0) + .collect::>(); + assert_eq!(result, vec![2]); + } + + #[test] + fn query_and_filter() { + let mut world = World::::new(); + + world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + let result = world + .query::, filter::Has>>() + .map(|result!(a)| a.0) + .collect::>(); + assert_eq!(result, vec![1]); + } + + #[test] + fn query_or_filter() { + let mut world = World::::new(); + + world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + let mut result = world + .query::, filter::Has>>() + .map(|result!(a)| a.0) + .collect::>(); + result.sort(); + assert_eq!(result, vec![1, 2]); + } + + #[cfg(feature = "rayon")] + #[test] + fn par_query_refs() { + let mut world = World::::new(); + + world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + let mut result = world + .par_query::() + .map(|result!(a)| a.0) + .collect::>(); + result.sort(); + assert_eq!(result, vec![1, 2]); + } + + #[cfg(feature = "rayon")] + #[test] + fn par_query_mut_refs() { + let mut world = World::::new(); + + world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + let mut result = world + .par_query::() + .map(|result!(b)| b.0) + .collect::>(); + result.sort(); + assert_eq!(result, vec!['a', 'b']); + } + + #[cfg(feature = "rayon")] + #[test] + fn par_query_option_refs() { + let mut world = World::::new(); + + world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + let mut result = world + .par_query::), filter::None>() + .map(|result!(a)| a.map(|a| a.0)) + .collect::>(); + result.sort(); + assert_eq!(result, vec![None, None, Some(1), Some(2)]); + } + + #[cfg(feature = "rayon")] + #[test] + fn par_query_option_mut_refs() { + let mut world = World::::new(); + + world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + let mut result = world + .par_query::), filter::None>() + .map(|result!(b)| b.map(|b| b.0)) + .collect::>(); + result.sort(); + assert_eq!(result, vec![None, None, Some('a'), Some('b')]); + } + + #[cfg(feature = "rayon")] + #[test] + fn par_query_entity_identifiers() { + let mut world = World::::new(); + + let entity_identifier = world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + let result = world + .par_query::, filter::Has>>() + .map(|result!(identifier)| identifier) + .collect::>(); + assert_eq!(result, vec![entity_identifier]); + } + + #[cfg(feature = "rayon")] + #[test] + fn par_query_has_filter() { + let mut world = World::::new(); + + world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + let result = world + .par_query::>() + .map(|result!(a)| a.0) + .collect::>(); + assert_eq!(result, vec![1]); + } + + #[cfg(feature = "rayon")] + #[test] + fn par_query_not_filter() { + let mut world = World::::new(); + + world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + let result = world + .par_query::>>() + .map(|result!(a)| a.0) + .collect::>(); + assert_eq!(result, vec![2]); + } + + #[cfg(feature = "rayon")] + #[test] + fn par_query_and_filter() { + let mut world = World::::new(); + + world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + let result = world + .par_query::, filter::Has>>() + .map(|result!(a)| a.0) + .collect::>(); + assert_eq!(result, vec![1]); + } + + #[cfg(feature = "rayon")] + #[test] + fn par_query_or_filter() { + let mut world = World::::new(); + + world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + let mut result = world + .query::, filter::Has>>() + .map(|result!(a)| a.0) + .collect::>(); + result.sort(); + assert_eq!(result, vec![1, 2]); + } + + #[test] + fn system_refs() { + struct TestSystem; + + impl<'a> System<'a> for TestSystem { + type Views = views!(&'a A); + type Filter = filter::None; + + fn run(&mut self, query_results: result::Iter<'a, R, Self::Filter, Self::Views>) + where + R: crate::registry::Registry + 'a, + { + let mut result = query_results.map(|result!(a)| a.0).collect::>(); + result.sort(); + assert_eq!(result, vec![1, 2]); + } + } + + let mut world = World::::new(); + + world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + world.run_system(&mut TestSystem); + } + + #[test] + fn system_mut_refs() { + struct TestSystem; + + impl<'a> System<'a> for TestSystem { + type Views = views!(&'a mut B); + type Filter = filter::None; + + fn run(&mut self, query_results: result::Iter<'a, R, Self::Filter, Self::Views>) + where + R: crate::registry::Registry + 'a, + { + let mut result = query_results.map(|result!(b)| b.0).collect::>(); + result.sort(); + assert_eq!(result, vec!['a', 'b']); + } + } + + let mut world = World::::new(); + + world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + world.run_system(&mut TestSystem); + } + + #[test] + fn system_option_refs() { + struct TestSystem; + + impl<'a> System<'a> for TestSystem { + type Views = views!(Option<&'a A>); + type Filter = filter::None; + + fn run(&mut self, query_results: result::Iter<'a, R, Self::Filter, Self::Views>) + where + R: crate::registry::Registry + 'a, + { + let mut result = query_results + .map(|result!(a)| a.map(|a| a.0)) + .collect::>(); + result.sort(); + assert_eq!(result, vec![None, None, Some(1), Some(2)]); + } + } + + let mut world = World::::new(); + + world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + world.run_system(&mut TestSystem); + } + + #[test] + fn system_option_mut_refs() { + struct TestSystem; + + impl<'a> System<'a> for TestSystem { + type Views = views!(Option<&'a mut B>); + type Filter = filter::None; + + fn run(&mut self, query_results: result::Iter<'a, R, Self::Filter, Self::Views>) + where + R: crate::registry::Registry + 'a, + { + let mut result = query_results + .map(|result!(b)| b.map(|b| b.0)) + .collect::>(); + result.sort(); + assert_eq!(result, vec![None, None, Some('a'), Some('b')]); + } + } + + let mut world = World::::new(); + + world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + world.run_system(&mut TestSystem); + } + + #[test] + fn system_entity_identifier() { + struct TestSystem { + entity_identifier: entity::Identifier, + } + + impl<'a> System<'a> for TestSystem { + type Views = views!(entity::Identifier); + type Filter = filter::And, filter::Has>; + + fn run(&mut self, query_results: result::Iter<'a, R, Self::Filter, Self::Views>) + where + R: crate::registry::Registry + 'a, + { + let result = query_results + .map(|result!(entity_identifier)| entity_identifier) + .collect::>(); + assert_eq!(result, vec![self.entity_identifier]); + } + } + + let mut world = World::::new(); + + let entity_identifier = world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + world.run_system(&mut TestSystem { entity_identifier }); + } + + #[test] + fn system_has_filter() { + struct TestSystem; + + impl<'a> System<'a> for TestSystem { + type Views = views!(&'a A); + type Filter = filter::Has; + + fn run(&mut self, query_results: result::Iter<'a, R, Self::Filter, Self::Views>) + where + R: crate::registry::Registry + 'a, + { + let result = query_results.map(|result!(a)| a.0).collect::>(); + assert_eq!(result, vec![1]); + } + } + + let mut world = World::::new(); + + world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + world.run_system(&mut TestSystem); + } + + #[test] + fn system_not_filter() { + struct TestSystem; + + impl<'a> System<'a> for TestSystem { + type Views = views!(&'a A); + type Filter = filter::Not>; + + fn run(&mut self, query_results: result::Iter<'a, R, Self::Filter, Self::Views>) + where + R: crate::registry::Registry + 'a, + { + let result = query_results.map(|result!(a)| a.0).collect::>(); + assert_eq!(result, vec![2]); + } + } + + let mut world = World::::new(); + + world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + world.run_system(&mut TestSystem); + } + + #[test] + fn system_and_filter() { + struct TestSystem; + + impl<'a> System<'a> for TestSystem { + type Views = views!(&'a A); + type Filter = filter::And, filter::Has>; + + fn run(&mut self, query_results: result::Iter<'a, R, Self::Filter, Self::Views>) + where + R: crate::registry::Registry + 'a, + { + let result = query_results.map(|result!(a)| a.0).collect::>(); + assert_eq!(result, vec![1]); + } + } + + let mut world = World::::new(); + + world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + world.run_system(&mut TestSystem); + } + + #[test] + fn system_or_filter() { + struct TestSystem; + + impl<'a> System<'a> for TestSystem { + type Views = views!(&'a A); + type Filter = filter::Or, filter::Has>; + + fn run(&mut self, query_results: result::Iter<'a, R, Self::Filter, Self::Views>) + where + R: crate::registry::Registry + 'a, + { + let mut result = query_results.map(|result!(a)| a.0).collect::>(); + result.sort(); + assert_eq!(result, vec![1, 2]); + } + } + + let mut world = World::::new(); + + world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + world.run_system(&mut TestSystem); + } + + #[cfg(feature = "rayon")] + #[test] + fn par_system_refs() { + struct TestSystem; + + impl<'a> ParSystem<'a> for TestSystem { + type Views = views!(&'a A); + type Filter = filter::None; + + fn run(&mut self, query_results: result::ParIter<'a, R, Self::Filter, Self::Views>) + where + R: crate::registry::Registry + 'a, + { + let mut result = query_results.map(|result!(a)| a.0).collect::>(); + result.sort(); + assert_eq!(result, vec![1, 2]); + } + } + + let mut world = World::::new(); + + world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + world.run_par_system(&mut TestSystem); + } + + #[cfg(feature = "rayon")] + #[test] + fn par_system_mut_refs() { + struct TestSystem; + + impl<'a> ParSystem<'a> for TestSystem { + type Views = views!(&'a mut B); + type Filter = filter::None; + + fn run(&mut self, query_results: result::ParIter<'a, R, Self::Filter, Self::Views>) + where + R: crate::registry::Registry + 'a, + { + let mut result = query_results.map(|result!(b)| b.0).collect::>(); + result.sort(); + assert_eq!(result, vec!['a', 'b']); + } + } + + let mut world = World::::new(); + + world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + world.run_par_system(&mut TestSystem); + } + + #[cfg(feature = "rayon")] + #[test] + fn par_system_option_refs() { + struct TestSystem; + + impl<'a> ParSystem<'a> for TestSystem { + type Views = views!(Option<&'a A>); + type Filter = filter::None; + + fn run(&mut self, query_results: result::ParIter<'a, R, Self::Filter, Self::Views>) + where + R: crate::registry::Registry + 'a, + { + let mut result = query_results + .map(|result!(a)| a.map(|a| a.0)) + .collect::>(); + result.sort(); + assert_eq!(result, vec![None, None, Some(1), Some(2)]); + } + } + + let mut world = World::::new(); + + world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + world.run_par_system(&mut TestSystem); + } + + #[cfg(feature = "rayon")] + #[test] + fn par_system_option_mut_refs() { + struct TestSystem; + + impl<'a> ParSystem<'a> for TestSystem { + type Views = views!(Option<&'a mut B>); + type Filter = filter::None; + + fn run(&mut self, query_results: result::ParIter<'a, R, Self::Filter, Self::Views>) + where + R: crate::registry::Registry + 'a, + { + let mut result = query_results + .map(|result!(b)| b.map(|b| b.0)) + .collect::>(); + result.sort(); + assert_eq!(result, vec![None, None, Some('a'), Some('b')]); + } + } + + let mut world = World::::new(); + + world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + world.run_par_system(&mut TestSystem); + } + + #[cfg(feature = "rayon")] + #[test] + fn par_system_entity_identifier() { + struct TestSystem { + entity_identifier: entity::Identifier, + } + + impl<'a> ParSystem<'a> for TestSystem { + type Views = views!(entity::Identifier); + type Filter = filter::And, filter::Has>; + + fn run(&mut self, query_results: result::ParIter<'a, R, Self::Filter, Self::Views>) + where + R: crate::registry::Registry + 'a, + { + let result = query_results + .map(|result!(entity_identifier)| entity_identifier) + .collect::>(); + assert_eq!(result, vec![self.entity_identifier]); + } + } + + let mut world = World::::new(); + + let entity_identifier = world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + world.run_par_system(&mut TestSystem { entity_identifier }); + } + + #[cfg(feature = "rayon")] + #[test] + fn par_system_has_filter() { + struct TestSystem; + + impl<'a> ParSystem<'a> for TestSystem { + type Views = views!(&'a A); + type Filter = filter::Has; + + fn run(&mut self, query_results: result::ParIter<'a, R, Self::Filter, Self::Views>) + where + R: crate::registry::Registry + 'a, + { + let result = query_results.map(|result!(a)| a.0).collect::>(); + assert_eq!(result, vec![1]); + } + } + + let mut world = World::::new(); + + world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + world.run_par_system(&mut TestSystem); + } + + #[cfg(feature = "rayon")] + #[test] + fn par_system_not_filter() { + struct TestSystem; + + impl<'a> ParSystem<'a> for TestSystem { + type Views = views!(&'a A); + type Filter = filter::Not>; + + fn run(&mut self, query_results: result::ParIter<'a, R, Self::Filter, Self::Views>) + where + R: crate::registry::Registry + 'a, + { + let result = query_results.map(|result!(a)| a.0).collect::>(); + assert_eq!(result, vec![2]); + } + } + + let mut world = World::::new(); + + world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + world.run_par_system(&mut TestSystem); + } + + #[cfg(feature = "rayon")] + #[test] + fn par_system_and_filter() { + struct TestSystem; + + impl<'a> ParSystem<'a> for TestSystem { + type Views = views!(&'a A); + type Filter = filter::And, filter::Has>; + + fn run(&mut self, query_results: result::ParIter<'a, R, Self::Filter, Self::Views>) + where + R: crate::registry::Registry + 'a, + { + let result = query_results.map(|result!(a)| a.0).collect::>(); + assert_eq!(result, vec![1]); + } + } + + let mut world = World::::new(); + + world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + world.run_par_system(&mut TestSystem); + } + + #[cfg(feature = "rayon")] + #[test] + fn par_system_or_filter() { + struct TestSystem; + + impl<'a> ParSystem<'a> for TestSystem { + type Views = views!(&'a A); + type Filter = filter::Or, filter::Has>; + + fn run(&mut self, query_results: result::ParIter<'a, R, Self::Filter, Self::Views>) + where + R: crate::registry::Registry + 'a, + { + let mut result = query_results.map(|result!(a)| a.0).collect::>(); + result.sort(); + assert_eq!(result, vec![1, 2]); + } + } + + let mut world = World::::new(); + + world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + world.run_par_system(&mut TestSystem); + } + + #[cfg(feature = "rayon")] + #[test] + fn schedule() { + struct TestSystem; + + impl<'a> System<'a> for TestSystem { + type Views = views!(&'a A); + type Filter = filter::None; + + fn run(&mut self, query_results: result::Iter<'a, R, Self::Filter, Self::Views>) + where + R: crate::registry::Registry + 'a, + { + let mut result = query_results.map(|result!(a)| a.0).collect::>(); + result.sort(); + assert_eq!(result, vec![1, 2]); + } + } + + struct TestParSystem; + + impl<'a> ParSystem<'a> for TestParSystem { + type Views = views!(&'a mut B); + type Filter = filter::None; + + fn run(&mut self, query_results: result::ParIter<'a, R, Self::Filter, Self::Views>) + where + R: crate::registry::Registry + 'a, + { + let mut result = query_results.map(|result!(b)| b.0).collect::>(); + result.sort(); + assert_eq!(result, vec!['a', 'b']); + } + } + + let mut world = World::::new(); + + world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + let mut schedule = Schedule::builder() + .system(TestSystem) + .par_system(TestParSystem) + .build(); + + world.run_schedule(&mut schedule); + } + + #[test] + fn contains() { + let mut world = World::::new(); + + let entity_identifier = world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + assert!(world.contains(entity_identifier)); + } + + #[test] + fn not_contains() { + let mut world = World::::new(); + + let entity_identifier = world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + world.remove(entity_identifier); + + assert!(!world.contains(entity_identifier)); + } + + #[test] + fn entry_add_component() { + let mut world = World::::new(); + + world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + let entity_identifier = world.insert(entity!()); + + let mut entry = assert_some!(world.entry(entity_identifier)); + entry.add(A(3)); + + let mut result = world + .query::() + .map(|result!(a)| a.0) + .collect::>(); + result.sort(); + assert_eq!(result, vec![1, 2, 3]); + } + + #[test] + fn entry_set_existing_component() { + let mut world = World::::new(); + + let entity_identifier = world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + let mut entry = assert_some!(world.entry(entity_identifier)); + entry.add(A(3)); + + let mut result = world + .query::() + .map(|result!(a)| a.0) + .collect::>(); + result.sort(); + assert_eq!(result, vec![2, 3]); + } + + #[test] + fn entry_remove_component() { + let mut world = World::::new(); + + let entity_identifier = world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + let mut entry = assert_some!(world.entry(entity_identifier)); + entry.remove::(); + + let mut result = world + .query::() + .map(|result!(a)| a.0) + .collect::>(); + result.sort(); + assert_eq!(result, vec![2]); + } + + #[test] + fn no_entry_found() { + let mut world = World::::new(); + + let entity_identifier = world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + world.remove(entity_identifier); + + assert_none!(world.entry(entity_identifier)); + } + + #[test] + fn remove() { + let mut world = World::::new(); + + let entity_identifier = world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + world.remove(entity_identifier); + + let mut result = world + .query::() + .map(|result!(a)| a.0) + .collect::>(); + result.sort(); + assert_eq!(result, vec![2]); + assert_eq!(world.len(), 3); + } + + #[test] + fn remove_already_removed() { + let mut world = World::::new(); + + let entity_identifier = world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + world.remove(entity_identifier); + assert_eq!(world.len(), 3); + world.remove(entity_identifier); + + assert_eq!(world.len(), 3); + } + + #[test] + fn clear() { + let mut world = World::::new(); + + world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + world.clear(); + + let mut result = world + .query::() + .map(|result!(a)| a.0) + .collect::>(); + result.sort(); + assert_eq!(result, Vec::new()); + assert_eq!(world.len(), 0); + } + + #[test] + fn len() { + let mut world = World::::new(); + + world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + assert_eq!(world.len(), 4); + } + + #[test] + fn is_empty() { + let mut world = World::::new(); + + assert!(world.is_empty()); + + world.insert(entity!(A(1), B('a'))); + world.insert(entity!(A(2))); + world.insert(entity!(B('b'))); + world.insert(entity!()); + + assert!(!world.is_empty()); + } +}