1pub use paste;
461
462#[cfg(feature = "dynamic")]
463pub mod dynamic;
464
465#[cfg(feature = "dynamic")]
466pub mod system_param;
467
468#[cfg(feature = "state")]
469pub mod state;
470
471#[cfg(feature = "dynamic")]
505#[macro_export]
506macro_rules! dynamic_schema {
507 (@consts $bit:expr;) => {};
508 (@consts $bit:expr; $const:ident $(, $rest:ident)*) => {
509 pub const $const: u64 = $bit;
510 const _: () = assert!(
511 $const != 0,
512 "dynamic_schema! supports at most 64 components per world"
513 );
514 $crate::dynamic_schema!(@consts $bit << 1; $($rest),*);
515 };
516 (
517 $vis:vis fn $register_fn:ident {
518 $($field:ident: $ty:ty => $const:ident,)+
519 }
520 ) => {
521 $crate::dynamic_schema!(@consts 1u64; $($const),+);
522
523 $vis fn $register_fn() -> $crate::dynamic::ComponentRegistry {
524 let mut registry = $crate::dynamic::ComponentRegistry::new();
525 $(
526 let key = registry.register::<$ty>();
527 assert_eq!(
528 key.mask,
529 $const,
530 "schema declaration order must match registration order"
531 );
532 )+
533 registry
534 }
535 };
536 (
537 serde $vis:vis fn $register_fn:ident {
538 $($field:ident: $ty:ty => $const:ident,)+
539 }
540 ) => {
541 $crate::dynamic_schema!(@consts 1u64; $($const),+);
542
543 $vis fn $register_fn() -> $crate::dynamic::ComponentRegistry {
544 let mut registry = $crate::dynamic::ComponentRegistry::new();
545 $(
546 let key = registry.register_serde::<$ty>();
547 assert_eq!(
548 key.mask,
549 $const,
550 "schema declaration order must match registration order"
551 );
552 )+
553 registry
554 }
555 };
556}
557
558#[cfg(feature = "dynamic")]
583#[macro_export]
584macro_rules! dynamic_worlds {
585 (@consts $index:expr;) => {};
586 (@consts $index:expr; $const:ident $(, $rest:ident)*) => {
587 pub const $const: usize = $index;
588 $crate::dynamic_worlds!(@consts $index + 1; $($rest),*);
589 };
590 (
591 $vis:vis fn $build_fn:ident {
592 $($const:ident => $register_fn:ident,)+
593 }
594 ) => {
595 $crate::dynamic_worlds!(@consts 0usize; $($const),+);
596
597 $vis fn $build_fn() -> $crate::dynamic::DynEcs {
598 let mut ecs = $crate::dynamic::DynEcs::new();
599 $(
600 ecs.add_world_at($const, $register_fn());
601 )+
602 ecs
603 }
604 };
605}
606
607#[cfg(feature = "dynamic")]
655#[macro_export]
656macro_rules! dynamic_accessors {
657 (
658 $vis:vis struct $keys:ident for $wrapper:ident { $world_field:ident, $keys_field:ident }
659 components {
660 $($component:ident: $component_type:ty,)*
661 }
662 tags {
663 $($tag:ident: $tag_type:ty,)*
664 }
665 ) => {
666 $vis struct $keys {
667 $(pub $component: $crate::dynamic::ComponentKey<$component_type>,)*
668 $(pub $tag: $crate::dynamic::TagKey,)*
669 }
670
671 impl $keys {
672 $vis fn resolve(world: &mut $crate::dynamic::DynWorld) -> Self {
673 Self {
674 $($component: world.register::<$component_type>(),)*
675 $($tag: world.tag_key::<$tag_type>(),)*
676 }
677 }
678 }
679
680 $crate::paste::paste! {
681 impl $wrapper {
682 $(
683 $vis fn [<get_ $component>](&self, entity: $crate::Entity) -> Option<&$component_type> {
684 self.$world_field.get_keyed(self.$keys_field.$component, entity)
685 }
686
687 $vis fn [<get_ $component _mut>](&mut self, entity: $crate::Entity) -> Option<&mut $component_type> {
688 self.$world_field.get_mut_keyed(self.$keys_field.$component, entity)
689 }
690
691 $vis fn [<set_ $component>](&mut self, entity: $crate::Entity, value: $component_type) {
692 self.$world_field.set_keyed(self.$keys_field.$component, entity, value);
693 }
694
695 $vis fn [<remove_ $component>](&mut self, entity: $crate::Entity) -> bool {
696 self.$world_field.remove_components(entity, self.$keys_field.$component.mask)
697 }
698
699 $vis fn [<has_ $component>](&self, entity: $crate::Entity) -> bool {
700 self.$world_field.entity_has_components(entity, self.$keys_field.$component.mask)
701 }
702 )*
703
704 $(
705 $vis fn [<add_ $tag>](&mut self, entity: $crate::Entity) {
706 self.$world_field.add_tag(self.$keys_field.$tag, entity);
707 }
708
709 $vis fn [<remove_ $tag>](&mut self, entity: $crate::Entity) -> bool {
710 self.$world_field.remove_tag(self.$keys_field.$tag, entity)
711 }
712
713 $vis fn [<has_ $tag>](&self, entity: $crate::Entity) -> bool {
714 self.$world_field.has_tag(self.$keys_field.$tag, entity)
715 }
716
717 $vis fn [<query_ $tag>](&self) -> impl Iterator<Item = $crate::Entity> + '_ {
718 self.$world_field.query_tag(self.$keys_field.$tag)
719 }
720 )*
721 }
722 }
723 };
724}
725
726#[cfg(not(target_family = "wasm"))]
727pub use rayon;
728
729#[derive(Default, Clone, Copy, Debug, Eq, PartialEq, Hash)]
730#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
731pub struct Entity {
732 pub id: u32,
733 pub generation: u32,
734}
735
736impl std::fmt::Display for Entity {
737 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
738 let Self { id, generation } = self;
739 write!(f, "Id: {id} - Generation: {generation}")
740 }
741}
742
743#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
746#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
747pub struct EntitySlot {
748 pub generation: u32,
749 pub alive: bool,
750}
751
752#[derive(Default)]
758#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
759pub struct EntityAllocator {
760 pub next_id: u32,
761 pub free_ids: Vec<(u32, u32)>,
762 pub slots: Vec<EntitySlot>,
763}
764
765impl EntityAllocator {
766 pub fn revive(&mut self, entity: Entity) {
771 let index = entity.id as usize;
772 if self.slots.len() <= index {
773 self.slots.resize(
774 index + 1,
775 EntitySlot {
776 generation: 0,
777 alive: false,
778 },
779 );
780 }
781 self.slots[index] = EntitySlot {
782 generation: entity.generation,
783 alive: true,
784 };
785 self.free_ids.retain(|&(id, _)| id != entity.id);
786 if self.next_id <= entity.id {
787 self.next_id = entity.id + 1;
788 }
789 }
790
791 #[inline]
792 pub fn allocate(&mut self) -> Entity {
793 let entity = if let Some((id, next_generation)) = self.free_ids.pop() {
794 Entity {
795 id,
796 generation: next_generation,
797 }
798 } else {
799 let id = self.next_id;
800 self.next_id += 1;
801 Entity { id, generation: 0 }
802 };
803 let index = entity.id as usize;
804 if index >= self.slots.len() {
805 self.slots.resize(index + 1, EntitySlot::default());
806 }
807 self.slots[index] = EntitySlot {
808 generation: entity.generation,
809 alive: true,
810 };
811 entity
812 }
813
814 pub fn allocate_batch(&mut self, count: usize, entities: &mut Vec<Entity>) {
818 entities.reserve(count);
819 let recycled = count.min(self.free_ids.len());
820 for _ in 0..recycled {
821 let Some((id, generation)) = self.free_ids.pop() else {
822 break;
823 };
824 let index = id as usize;
825 if index >= self.slots.len() {
826 self.slots.resize(index + 1, EntitySlot::default());
827 }
828 self.slots[index] = EntitySlot {
829 generation,
830 alive: true,
831 };
832 entities.push(Entity { id, generation });
833 }
834
835 let fresh = count - recycled;
836 if fresh > 0 {
837 let start = self.next_id;
838 self.next_id += fresh as u32;
839 let start_index = start as usize;
840 let end_index = start_index + fresh;
841 if end_index > self.slots.len() {
842 self.slots.resize(end_index, EntitySlot::default());
843 }
844 self.slots[start_index..end_index].fill(EntitySlot {
845 generation: 0,
846 alive: true,
847 });
848 for id in start..self.next_id {
849 entities.push(Entity { id, generation: 0 });
850 }
851 }
852 }
853
854 #[inline]
857 pub fn deallocate(&mut self, entity: Entity) -> bool {
858 if !self.is_alive(entity) {
859 return false;
860 }
861 self.slots[entity.id as usize].alive = false;
862 self.free_ids
863 .push((entity.id, entity.generation.wrapping_add(1)));
864 true
865 }
866
867 #[inline]
868 pub fn is_alive(&self, entity: Entity) -> bool {
869 self.slots
870 .get(entity.id as usize)
871 .is_some_and(|slot| slot.alive && slot.generation == entity.generation)
872 }
873}
874
875#[derive(Copy, Clone, Default)]
876pub struct EntityLocation {
877 pub generation: u32,
878 pub table_index: u32,
879 pub array_index: u32,
880 pub allocated: bool,
881}
882
883#[derive(Default)]
884pub struct EntityLocations {
885 pub locations: Vec<EntityLocation>,
886}
887
888impl EntityLocations {
889 pub fn get(&self, id: u32) -> Option<&EntityLocation> {
890 self.locations.get(id as usize)
891 }
892
893 pub fn get_mut(&mut self, id: u32) -> Option<&mut EntityLocation> {
894 self.locations.get_mut(id as usize)
895 }
896
897 pub fn ensure_slot(&mut self, id: u32, generation: u32) {
898 let id_usize = id as usize;
899 if id_usize >= self.locations.len() {
900 self.locations.resize(
901 (self.locations.len() * 2).max(64).max(id_usize + 1),
902 EntityLocation::default(),
903 );
904 }
905 self.locations[id_usize].generation = generation;
906 }
907
908 pub fn insert(&mut self, id: u32, location: EntityLocation) {
909 let id_usize = id as usize;
910 if id_usize >= self.locations.len() {
911 self.locations.resize(
912 (self.locations.len() * 2).max(64).max(id_usize + 1),
913 EntityLocation::default(),
914 );
915 }
916 self.locations[id_usize] = location;
917 }
918
919 pub fn mark_deallocated(&mut self, id: u32) {
920 if let Some(loc) = self.locations.get_mut(id as usize) {
921 loc.allocated = false;
922 }
923 }
924}
925
926#[inline]
929pub const fn tick_is_newer(tick: u32, since_tick: u32) -> bool {
930 tick.wrapping_sub(since_tick) as i32 > 0
931}
932
933const SPARSE_TAG_ABSENT: u32 = u32::MAX;
934
935#[derive(Default, Clone, Debug)]
960pub struct SparseTagSet {
961 pub dense: Vec<Entity>,
962 pub sparse: Vec<u32>,
963}
964
965impl SparseTagSet {
966 #[inline]
969 pub fn insert(&mut self, entity: Entity) -> bool {
970 let index = entity.id as usize;
971 if index >= self.sparse.len() {
972 self.sparse.resize(index + 1, SPARSE_TAG_ABSENT);
973 }
974 let slot = self.sparse[index];
975 if slot != SPARSE_TAG_ABSENT {
976 let existing = &mut self.dense[slot as usize];
977 if *existing == entity {
978 return false;
979 }
980 *existing = entity;
981 return true;
982 }
983 self.sparse[index] = self.dense.len() as u32;
984 self.dense.push(entity);
985 true
986 }
987
988 #[inline]
989 pub fn remove(&mut self, entity: Entity) -> bool {
990 let index = entity.id as usize;
991 let Some(&slot) = self.sparse.get(index) else {
992 return false;
993 };
994 if slot == SPARSE_TAG_ABSENT || self.dense[slot as usize] != entity {
995 return false;
996 }
997 self.dense.swap_remove(slot as usize);
998 self.sparse[index] = SPARSE_TAG_ABSENT;
999 if (slot as usize) < self.dense.len() {
1000 let moved = self.dense[slot as usize];
1001 self.sparse[moved.id as usize] = slot;
1002 }
1003 true
1004 }
1005
1006 #[inline]
1007 pub fn contains(&self, entity: Entity) -> bool {
1008 self.sparse
1009 .get(entity.id as usize)
1010 .is_some_and(|&slot| slot != SPARSE_TAG_ABSENT && self.dense[slot as usize] == entity)
1011 }
1012
1013 pub fn iter(&self) -> impl Iterator<Item = Entity> + '_ {
1014 self.dense.iter().copied()
1015 }
1016
1017 pub fn len(&self) -> usize {
1018 self.dense.len()
1019 }
1020
1021 pub fn is_empty(&self) -> bool {
1022 self.dense.is_empty()
1023 }
1024
1025 pub fn clear(&mut self) {
1026 self.dense.clear();
1027 self.sparse.fill(SPARSE_TAG_ABSENT);
1028 }
1029}
1030
1031#[derive(Clone, Default)]
1036pub struct ArchetypeEdges {
1037 pub add_edges: Vec<Option<usize>>,
1038 pub remove_edges: Vec<Option<usize>>,
1039 pub multi_add_cache: std::collections::HashMap<u64, usize>,
1040 pub multi_remove_cache: std::collections::HashMap<u64, usize>,
1041}
1042
1043impl ArchetypeEdges {
1044 pub fn new(component_count: usize) -> Self {
1045 Self {
1046 add_edges: vec![None; component_count],
1047 remove_edges: vec![None; component_count],
1048 multi_add_cache: std::collections::HashMap::default(),
1049 multi_remove_cache: std::collections::HashMap::default(),
1050 }
1051 }
1052}
1053
1054pub struct ArchetypeRouting<'world> {
1060 pub table_lookup: &'world mut std::collections::HashMap<u64, usize>,
1061 pub table_edges: &'world mut Vec<ArchetypeEdges>,
1062 pub query_cache: &'world mut std::collections::HashMap<u64, Vec<usize>>,
1063}
1064
1065pub fn archetype_register_table<M>(
1066 routing: ArchetypeRouting<'_>,
1067 component_count: usize,
1068 mask: u64,
1069 table_index: usize,
1070 table_masks: M,
1071 component_bits: impl Iterator<Item = (u64, usize)>,
1072) where
1073 M: Iterator<Item = u64> + Clone,
1074{
1075 routing
1076 .table_edges
1077 .push(ArchetypeEdges::new(component_count));
1078 routing.table_lookup.insert(mask, table_index);
1079
1080 for (query_mask, cached_tables) in routing.query_cache.iter_mut() {
1081 if mask & *query_mask == *query_mask {
1082 cached_tables.push(table_index);
1083 }
1084 }
1085
1086 for (component_mask, component_index) in component_bits {
1087 for (index, existing_mask) in table_masks.clone().enumerate() {
1088 if existing_mask | component_mask == mask {
1089 routing.table_edges[index].add_edges[component_index] = Some(table_index);
1090 }
1091 if existing_mask & !component_mask == mask {
1092 routing.table_edges[index].remove_edges[component_index] = Some(table_index);
1093 }
1094 }
1095 }
1096}
1097
1098pub fn archetype_cached_tables<M>(
1103 query_cache: &mut std::collections::HashMap<u64, Vec<usize>>,
1104 table_masks: M,
1105 mask: u64,
1106) -> &[usize]
1107where
1108 M: Iterator<Item = u64>,
1109{
1110 query_cache.entry(mask).or_insert_with(|| {
1111 table_masks
1112 .enumerate()
1113 .filter(|(_, table_mask)| table_mask & mask == mask)
1114 .map(|(table_index, _)| table_index)
1115 .collect()
1116 })
1117}
1118
1119pub const STRUCTURAL_LOG_CAPACITY: usize = 262_144;
1124
1125#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1126#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1127pub enum StructuralChangeKind {
1128 Spawned,
1129 Despawned,
1130 ComponentsAdded,
1131 ComponentsRemoved,
1132 TagsAdded,
1133 TagsRemoved,
1134}
1135
1136#[derive(Debug, Clone, Copy)]
1142#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1143pub struct StructuralChange {
1144 pub sequence: u64,
1145 pub entity: Entity,
1146 pub kind: StructuralChangeKind,
1147 pub mask: u64,
1148}
1149
1150pub const EVENT_CHANNEL_CAPACITY: usize = 262_144;
1154
1155#[derive(Clone)]
1198pub struct EventChannel<T> {
1199 pub events: Vec<T>,
1200 pub base_sequence: u64,
1201 pub previous_update_sequence: u64,
1202}
1203
1204impl<T> Default for EventChannel<T> {
1205 fn default() -> Self {
1206 Self::new()
1207 }
1208}
1209
1210impl<T> EventChannel<T> {
1211 pub fn new() -> Self {
1212 Self {
1213 events: Vec::new(),
1214 base_sequence: 0,
1215 previous_update_sequence: 0,
1216 }
1217 }
1218
1219 #[inline]
1222 pub fn send(&mut self, event: T) {
1223 if self.events.len() >= EVENT_CHANNEL_CAPACITY {
1224 self.drop_oldest_half();
1225 }
1226 self.events.push(event);
1227 }
1228
1229 #[cold]
1230 fn drop_oldest_half(&mut self) {
1231 let half = self.events.len() / 2;
1232 self.events.drain(..half);
1233 self.base_sequence += half as u64;
1234 }
1235
1236 #[inline]
1239 pub fn sequence(&self) -> u64 {
1240 self.base_sequence + self.events.len() as u64
1241 }
1242
1243 #[inline]
1246 pub fn events_since(&self, cursor: u64) -> &[T] {
1247 let start = cursor
1248 .saturating_sub(self.base_sequence)
1249 .min(self.events.len() as u64) as usize;
1250 &self.events[start..]
1251 }
1252
1253 #[inline]
1260 pub fn consume(&self, cursor: &mut u64) -> &[T] {
1261 let events = self.events_since(*cursor);
1262 *cursor = self.sequence();
1263 events
1264 }
1265
1266 pub fn read(&self) -> impl Iterator<Item = &T> {
1268 self.events.iter()
1269 }
1270
1271 #[inline]
1282 pub fn read_frame(&self) -> &[T] {
1283 let end = self
1284 .previous_update_sequence
1285 .saturating_sub(self.base_sequence)
1286 .min(self.events.len() as u64) as usize;
1287 &self.events[..end]
1288 }
1289
1290 pub fn peek(&self) -> Option<&T> {
1292 self.events.first()
1293 }
1294
1295 pub fn trim(&mut self, up_to_sequence: u64) {
1298 let drop_count = up_to_sequence
1299 .saturating_sub(self.base_sequence)
1300 .min(self.events.len() as u64) as usize;
1301 self.events.drain(..drop_count);
1302 self.base_sequence += drop_count as u64;
1303 }
1304
1305 pub fn update(&mut self) {
1308 let expire = self.previous_update_sequence;
1309 self.trim(expire);
1310 self.previous_update_sequence = self.sequence();
1311 }
1312
1313 pub fn clear(&mut self) {
1315 let sequence = self.sequence();
1316 self.trim(sequence);
1317 }
1318
1319 pub fn len(&self) -> usize {
1321 self.events.len()
1322 }
1323
1324 pub fn is_empty(&self) -> bool {
1326 self.events.is_empty()
1327 }
1328}
1329
1330struct ScheduleEntry<W> {
1331 name: &'static str,
1332 system: Box<dyn FnMut(&mut W) + Send>,
1333}
1334
1335pub struct Schedule<W> {
1336 entries: Vec<ScheduleEntry<W>>,
1337}
1338
1339impl<W> Schedule<W> {
1340 pub fn new() -> Self {
1341 Self {
1342 entries: Vec::new(),
1343 }
1344 }
1345
1346 pub fn push<F>(&mut self, name: &'static str, system: F) -> &mut Self
1347 where
1348 F: FnMut(&mut W) + Send + 'static,
1349 {
1350 self.assert_unique(name);
1351 self.entries.push(ScheduleEntry {
1352 name,
1353 system: Box::new(system),
1354 });
1355 self
1356 }
1357
1358 pub fn push_readonly<F>(&mut self, name: &'static str, mut system: F) -> &mut Self
1359 where
1360 F: FnMut(&W) + Send + 'static,
1361 {
1362 self.assert_unique(name);
1363 self.entries.push(ScheduleEntry {
1364 name,
1365 system: Box::new(move |world: &mut W| {
1366 system(&*world);
1367 }),
1368 });
1369 self
1370 }
1371
1372 pub fn push_if<C, F>(&mut self, name: &'static str, condition: C, mut system: F) -> &mut Self
1376 where
1377 C: Fn(&W) -> bool + Send + 'static,
1378 F: FnMut(&mut W) + Send + 'static,
1379 {
1380 self.push(name, move |world: &mut W| {
1381 if condition(world) {
1382 system(world);
1383 }
1384 })
1385 }
1386
1387 pub fn insert_before<F>(&mut self, target: &str, name: &'static str, system: F) -> &mut Self
1388 where
1389 F: FnMut(&mut W) + Send + 'static,
1390 {
1391 self.assert_unique(name);
1392 let index = self.index_of_or_panic(target, "insert_before");
1393 self.entries.insert(
1394 index,
1395 ScheduleEntry {
1396 name,
1397 system: Box::new(system),
1398 },
1399 );
1400 self
1401 }
1402
1403 pub fn insert_after<F>(&mut self, target: &str, name: &'static str, system: F) -> &mut Self
1404 where
1405 F: FnMut(&mut W) + Send + 'static,
1406 {
1407 self.assert_unique(name);
1408 let index = self.index_of_or_panic(target, "insert_after");
1409 self.entries.insert(
1410 index + 1,
1411 ScheduleEntry {
1412 name,
1413 system: Box::new(system),
1414 },
1415 );
1416 self
1417 }
1418
1419 pub fn replace<F>(&mut self, name: &str, system: F) -> &mut Self
1420 where
1421 F: FnMut(&mut W) + Send + 'static,
1422 {
1423 let index = self
1424 .index_of(name)
1425 .unwrap_or_else(|| panic!("Schedule::replace: system \"{name}\" not found"));
1426 self.entries[index].system = Box::new(system);
1427 self
1428 }
1429
1430 pub fn remove(&mut self, name: &str) -> bool {
1431 let len_before = self.entries.len();
1432 self.entries.retain(|entry| entry.name != name);
1433 self.entries.len() != len_before
1434 }
1435
1436 pub fn contains(&self, name: &str) -> bool {
1437 self.entries.iter().any(|entry| entry.name == name)
1438 }
1439
1440 pub fn names(&self) -> impl Iterator<Item = &'static str> + '_ {
1441 self.entries.iter().map(|entry| entry.name)
1442 }
1443
1444 pub fn len(&self) -> usize {
1445 self.entries.len()
1446 }
1447
1448 pub fn is_empty(&self) -> bool {
1449 self.entries.is_empty()
1450 }
1451
1452 pub fn run(&mut self, world: &mut W) {
1453 for entry in &mut self.entries {
1454 (entry.system)(world);
1455 }
1456 }
1457
1458 fn index_of(&self, name: &str) -> Option<usize> {
1459 self.entries.iter().position(|entry| entry.name == name)
1460 }
1461
1462 fn assert_unique(&self, name: &str) {
1463 assert!(
1464 !self.contains(name),
1465 "Schedule: system \"{name}\" already exists"
1466 );
1467 }
1468
1469 fn index_of_or_panic(&self, target: &str, method: &str) -> usize {
1470 self.index_of(target)
1471 .unwrap_or_else(|| panic!("Schedule::{method}: system \"{target}\" not found"))
1472 }
1473}
1474
1475impl<W> Default for Schedule<W> {
1476 fn default() -> Self {
1477 Self::new()
1478 }
1479}
1480
1481pub struct Stages<W> {
1488 pub stages: Vec<(&'static str, Schedule<W>)>,
1489}
1490
1491impl<W> Stages<W> {
1492 pub fn new() -> Self {
1493 Self { stages: Vec::new() }
1494 }
1495
1496 pub fn add_stage(&mut self, name: &'static str) -> &mut Self {
1499 assert!(
1500 !self.stages.iter().any(|(existing, _)| *existing == name),
1501 "stage {name:?} is already declared"
1502 );
1503 self.stages.push((name, Schedule::new()));
1504 self
1505 }
1506
1507 pub fn stage_mut(&mut self, name: &str) -> &mut Schedule<W> {
1512 match self
1513 .stages
1514 .iter()
1515 .position(|(stage_name, _)| *stage_name == name)
1516 {
1517 Some(index) => &mut self.stages[index].1,
1518 None => {
1519 let declared: Vec<&'static str> = self
1520 .stages
1521 .iter()
1522 .map(|(stage_name, _)| *stage_name)
1523 .collect();
1524 panic!("no stage named {name:?}; declared stages are {declared:?}")
1525 }
1526 }
1527 }
1528
1529 pub fn run(&mut self, world: &mut W) {
1531 for (_name, schedule) in &mut self.stages {
1532 schedule.run(world);
1533 }
1534 }
1535
1536 pub fn run_stage(&mut self, name: &str, world: &mut W) {
1539 self.stage_mut(name).run(world);
1540 }
1541}
1542
1543impl<W> Default for Stages<W> {
1544 fn default() -> Self {
1545 Self::new()
1546 }
1547}
1548
1549#[macro_export]
1550macro_rules! ecs {
1551 (
1552 $world:ident {
1553 $($(#[$comp_attr:meta])* $name:ident: $type:ty => $mask:ident),* $(,)?
1554 }
1555 Tags {
1556 $($tag_name:ident => $tag_mask:ident),* $(,)?
1557 }
1558 Events {
1559 $($event_name:ident: $event_type:ty),* $(,)?
1560 }
1561 $resources:ident {
1562 $($(#[$attr:meta])* $resource_name:ident: $resource_type:ty),* $(,)?
1563 }
1564 ) => {
1565 $crate::ecs_impl! {
1566 $world {
1567 $($(#[$comp_attr])* $name: $type => $mask),*
1568 }
1569 Tags {
1570 $($tag_name => $tag_mask),*
1571 }
1572 Events {
1573 $($event_name: $event_type),*
1574 }
1575 $resources {
1576 $($(#[$attr])* $resource_name: $resource_type),*
1577 }
1578 }
1579 };
1580
1581 (
1582 $world:ident {
1583 $($(#[$comp_attr:meta])* $name:ident: $type:ty => $mask:ident),* $(,)?
1584 }
1585 Tags {
1586 $($tag_name:ident => $tag_mask:ident),* $(,)?
1587 }
1588 $resources:ident {
1589 $($(#[$attr:meta])* $resource_name:ident: $resource_type:ty),* $(,)?
1590 }
1591 ) => {
1592 $crate::ecs_impl! {
1593 $world {
1594 $($(#[$comp_attr])* $name: $type => $mask),*
1595 }
1596 Tags {
1597 $($tag_name => $tag_mask),*
1598 }
1599 Events {}
1600 $resources {
1601 $($(#[$attr])* $resource_name: $resource_type),*
1602 }
1603 }
1604 };
1605
1606 (
1607 $world:ident {
1608 $($(#[$comp_attr:meta])* $name:ident: $type:ty => $mask:ident),* $(,)?
1609 }
1610 Events {
1611 $($event_name:ident: $event_type:ty),* $(,)?
1612 }
1613 $resources:ident {
1614 $($(#[$attr:meta])* $resource_name:ident: $resource_type:ty),* $(,)?
1615 }
1616 ) => {
1617 $crate::ecs_impl! {
1618 $world {
1619 $($(#[$comp_attr])* $name: $type => $mask),*
1620 }
1621 Tags {}
1622 Events {
1623 $($event_name: $event_type),*
1624 }
1625 $resources {
1626 $($(#[$attr])* $resource_name: $resource_type),*
1627 }
1628 }
1629 };
1630
1631 (
1632 $world:ident {
1633 $($(#[$comp_attr:meta])* $name:ident: $type:ty => $mask:ident),* $(,)?
1634 }
1635 $resources:ident {
1636 $($(#[$attr:meta])* $resource_name:ident: $resource_type:ty),* $(,)?
1637 }
1638 ) => {
1639 $crate::ecs_impl! {
1640 $world {
1641 $($(#[$comp_attr])* $name: $type => $mask),*
1642 }
1643 Tags {}
1644 Events {}
1645 $resources {
1646 $($(#[$attr])* $resource_name: $resource_type),*
1647 }
1648 }
1649 };
1650
1651 (
1652 $ecs:ident {
1653 $($world_name:ident {
1654 $($(#[$comp_attr:meta])* $name:ident: $type:ty => $mask:ident),* $(,)?
1655 })+
1656 }
1657 Tags {
1658 $($tag_name:ident => $tag_mask:ident),* $(,)?
1659 }
1660 Events {
1661 $($event_name:ident: $event_type:ty),* $(,)?
1662 }
1663 $resources:ident {
1664 $($(#[$attr:meta])* $resource_name:ident: $resource_type:ty),* $(,)?
1665 }
1666 ) => {
1667 $crate::ecs_multi_impl! {
1668 $ecs {
1669 $($world_name {
1670 $($(#[$comp_attr])* $name: $type => $mask),*
1671 })+
1672 }
1673 Tags {
1674 $($tag_name => $tag_mask),*
1675 }
1676 Events {
1677 $($event_name: $event_type),*
1678 }
1679 $resources {
1680 $($(#[$attr])* $resource_name: $resource_type),*
1681 }
1682 }
1683 };
1684
1685 (
1686 $ecs:ident {
1687 $($world_name:ident {
1688 $($(#[$comp_attr:meta])* $name:ident: $type:ty => $mask:ident),* $(,)?
1689 })+
1690 }
1691 Tags {
1692 $($tag_name:ident => $tag_mask:ident),* $(,)?
1693 }
1694 $resources:ident {
1695 $($(#[$attr:meta])* $resource_name:ident: $resource_type:ty),* $(,)?
1696 }
1697 ) => {
1698 $crate::ecs_multi_impl! {
1699 $ecs {
1700 $($world_name {
1701 $($(#[$comp_attr])* $name: $type => $mask),*
1702 })+
1703 }
1704 Tags {
1705 $($tag_name => $tag_mask),*
1706 }
1707 Events {}
1708 $resources {
1709 $($(#[$attr])* $resource_name: $resource_type),*
1710 }
1711 }
1712 };
1713
1714 (
1715 $ecs:ident {
1716 $($world_name:ident {
1717 $($(#[$comp_attr:meta])* $name:ident: $type:ty => $mask:ident),* $(,)?
1718 })+
1719 }
1720 Events {
1721 $($event_name:ident: $event_type:ty),* $(,)?
1722 }
1723 $resources:ident {
1724 $($(#[$attr:meta])* $resource_name:ident: $resource_type:ty),* $(,)?
1725 }
1726 ) => {
1727 $crate::ecs_multi_impl! {
1728 $ecs {
1729 $($world_name {
1730 $($(#[$comp_attr])* $name: $type => $mask),*
1731 })+
1732 }
1733 Tags {}
1734 Events {
1735 $($event_name: $event_type),*
1736 }
1737 $resources {
1738 $($(#[$attr])* $resource_name: $resource_type),*
1739 }
1740 }
1741 };
1742
1743 (
1744 $ecs:ident {
1745 $($world_name:ident {
1746 $($(#[$comp_attr:meta])* $name:ident: $type:ty => $mask:ident),* $(,)?
1747 })+
1748 }
1749 $resources:ident {
1750 $($(#[$attr:meta])* $resource_name:ident: $resource_type:ty),* $(,)?
1751 }
1752 ) => {
1753 $crate::ecs_multi_impl! {
1754 $ecs {
1755 $($world_name {
1756 $($(#[$comp_attr])* $name: $type => $mask),*
1757 })+
1758 }
1759 Tags {}
1760 Events {}
1761 $resources {
1762 $($(#[$attr])* $resource_name: $resource_type),*
1763 }
1764 }
1765 };
1766}
1767
1768#[doc(hidden)]
1804#[macro_export]
1805macro_rules! ecs_kernel_impl {
1806 (
1807 $world:ident,
1808 $allow_insert:literal,
1809 { $($(#[$comp_attr:meta])* $name:ident: $type:ty => $mask:ident),* $(,)? }
1810 ) => {
1811 $crate::paste::paste! {
1812 #[repr(u64)]
1813 #[allow(clippy::upper_case_acronyms)]
1814 #[allow(non_camel_case_types)]
1815 #[allow(unused)]
1816 pub enum [<$world Component>] {
1817 $($(#[$comp_attr])* $mask,)*
1818 }
1819
1820 $($(#[$comp_attr])* pub const $mask: u64 = 1 << ([<$world Component>]::$mask as u64);)*
1821
1822 #[allow(unused)]
1823 pub const [<$world:snake:upper _COMPONENT_COUNT>]: usize = {
1824 let mut count = 0;
1825 $($(#[$comp_attr])* { count += 1; let _ = [<$world Component>]::$mask; })*
1826 count
1827 };
1828
1829 #[allow(unused)]
1830 pub const [<$world:snake:upper _ALL_COMPONENTS>]: u64 = {
1831 let mut mask = 0;
1832 $($(#[$comp_attr])* { mask |= $mask; })*
1833 mask
1834 };
1835
1836 #[derive(Default)]
1837 pub struct [<$world ComponentArrays>] {
1838 $($(#[$comp_attr])* pub $name: Vec<$type>,)*
1839 $($(#[$comp_attr])* pub [<$name _changed>]: Vec<u32>,)*
1840 $($(#[$comp_attr])* pub [<$name _peak_changed>]: u32,)*
1841 pub entity_indices: Vec<$crate::Entity>,
1842 pub mask: u64,
1843 }
1844
1845 #[allow(unused)]
1846 impl [<$world ComponentArrays>] {
1847 pub fn mark_columns_changed(&mut self, mask: u64, tick: u32) {
1854 $(
1855 $(#[$comp_attr])*
1856 {
1857 if self.mask & mask & $mask != 0 {
1858 self.[<$name _changed>].fill(tick);
1859 self.[<$name _peak_changed>] = tick;
1860 }
1861 }
1862 )*
1863 }
1864 }
1865
1866 #[allow(unused)]
1867 fn [<get_component_index_ $world:snake>](mask: u64) -> Option<usize> {
1868 match mask {
1869 $($(#[$comp_attr])* $mask => Some([<$world Component>]::$mask as _),)*
1870 _ => None,
1871 }
1872 }
1873
1874 #[allow(unused)]
1875 fn [<get_location_ $world:snake>](
1876 locations: &$crate::EntityLocations,
1877 entity: $crate::Entity,
1878 ) -> Option<(usize, usize)> {
1879 let location = locations.get(entity.id)?;
1880 if !location.allocated || location.generation != entity.generation {
1881 return None;
1882 }
1883 Some((location.table_index as usize, location.array_index as usize))
1884 }
1885
1886 #[allow(unused)]
1887 fn [<insert_location_ $world:snake>](
1888 locations: &mut $crate::EntityLocations,
1889 entity: $crate::Entity,
1890 location: (usize, usize),
1891 ) {
1892 locations.insert(entity.id, $crate::EntityLocation {
1893 generation: entity.generation,
1894 table_index: location.0 as u32,
1895 array_index: location.1 as u32,
1896 allocated: true,
1897 });
1898 }
1899
1900 #[allow(unused)]
1901 fn [<remove_from_table_ $world:snake>](
1902 arrays: &mut [<$world ComponentArrays>],
1903 index: usize,
1904 ) -> Option<$crate::Entity> {
1905 let last_index = arrays.entity_indices.len() - 1;
1906 let mut swapped_entity = None;
1907 if index < last_index {
1908 swapped_entity = Some(arrays.entity_indices[last_index]);
1909 }
1910 $(
1911 $(#[$comp_attr])*
1912 {
1913 if arrays.mask & $mask != 0 {
1914 arrays.$name.swap_remove(index);
1915 arrays.[<$name _changed>].swap_remove(index);
1916 }
1917 }
1918 )*
1919 arrays.entity_indices.swap_remove(index);
1920 swapped_entity
1921 }
1922
1923 #[allow(unused)]
1924 fn [<move_entity_ $world:snake>](
1925 world: &mut $world,
1926 entity: $crate::Entity,
1927 from_table: usize,
1928 from_index: usize,
1929 to_table: usize,
1930 ) {
1931 let tick = world.current_tick;
1932 $(
1933 $(#[$comp_attr])*
1934 {
1935 let component = if world.tables[from_table].mask & $mask != 0 {
1936 Some(std::mem::take(&mut world.tables[from_table].$name[from_index]))
1937 } else {
1938 None
1939 };
1940 if world.tables[to_table].mask & $mask != 0 {
1941 let arrays = &mut world.tables[to_table];
1942 arrays.$name.push(component.unwrap_or_default());
1943 arrays.[<$name _changed>].push(tick);
1944 arrays.[<$name _peak_changed>] = tick;
1945 }
1946 }
1947 )*
1948 world.tables[to_table].entity_indices.push(entity);
1949 let new_index = world.tables[to_table].entity_indices.len() - 1;
1950 [<insert_location_ $world:snake>](&mut world.entity_locations, entity, (to_table, new_index));
1951
1952 if let Some(swapped) = [<remove_from_table_ $world:snake>](&mut world.tables[from_table], from_index) {
1953 [<insert_location_ $world:snake>](
1954 &mut world.entity_locations,
1955 swapped,
1956 (from_table, from_index),
1957 );
1958 }
1959 }
1960
1961 #[allow(unused)]
1962 fn [<get_or_create_table_ $world:snake>](world: &mut $world, mask: u64) -> usize {
1963 debug_assert_eq!(
1964 mask & ![<$world:snake:upper _ALL_COMPONENTS>],
1965 0,
1966 "archetype masks must not contain tag bits or unknown component bits"
1967 );
1968 if let Some(&index) = world.table_lookup.get(&mask) {
1969 return index;
1970 }
1971
1972 let table_index = world.tables.len();
1973 world.tables.push([<$world ComponentArrays>] {
1974 mask,
1975 ..Default::default()
1976 });
1977 $crate::archetype_register_table(
1978 $crate::ArchetypeRouting {
1979 table_lookup: &mut world.table_lookup,
1980 table_edges: &mut world.table_edges,
1981 query_cache: &mut world.query_cache,
1982 },
1983 [<$world:snake:upper _COMPONENT_COUNT>],
1984 mask,
1985 table_index,
1986 world.tables.iter().map(|table| table.mask),
1987 [$($(#[$comp_attr])* $mask,)*].into_iter().filter_map(|component_mask| {
1988 [<get_component_index_ $world:snake>](component_mask)
1989 .map(|component_index| (component_mask, component_index))
1990 }),
1991 );
1992
1993 table_index
1994 }
1995
1996 #[allow(unused)]
1997 fn [<cached_tables_ $world:snake>]<'cache>(
1998 query_cache: &'cache mut std::collections::HashMap<u64, Vec<usize>>,
1999 tables: &[[<$world ComponentArrays>]],
2000 mask: u64,
2001 ) -> &'cache [usize] {
2002 $crate::archetype_cached_tables(
2003 query_cache,
2004 tables.iter().map(|table| table.mask),
2005 mask,
2006 )
2007 }
2008
2009 #[allow(unused)]
2010 fn [<tables_for_each_ $world:snake>]<F, P>(
2011 tables: &[[<$world ComponentArrays>]],
2012 query_cache: &std::collections::HashMap<u64, Vec<usize>>,
2013 include: u64,
2014 exclude: u64,
2015 filter: P,
2016 mut f: F,
2017 ) where
2018 F: FnMut($crate::Entity, &[<$world ComponentArrays>], usize),
2019 P: Fn($crate::Entity) -> bool,
2020 {
2021 if let Some(cached) = query_cache.get(&include) {
2022 for &table_index in cached {
2023 let table = &tables[table_index];
2024 if table.mask & exclude != 0 {
2025 continue;
2026 }
2027 for (index, &entity) in table.entity_indices.iter().enumerate() {
2028 if filter(entity) {
2029 f(entity, table, index);
2030 }
2031 }
2032 }
2033 return;
2034 }
2035 for table in tables {
2036 if table.mask & include != include || table.mask & exclude != 0 {
2037 continue;
2038 }
2039 for (index, &entity) in table.entity_indices.iter().enumerate() {
2040 if filter(entity) {
2041 f(entity, table, index);
2042 }
2043 }
2044 }
2045 }
2046
2047 #[allow(unused)]
2048 fn [<tables_for_each_mut_ $world:snake>]<F, P>(
2049 tables: &mut [[<$world ComponentArrays>]],
2050 query_cache: &mut std::collections::HashMap<u64, Vec<usize>>,
2051 include: u64,
2052 exclude: u64,
2053 filter: P,
2054 mut f: F,
2055 ) where
2056 F: FnMut($crate::Entity, &mut [<$world ComponentArrays>], usize),
2057 P: Fn($crate::Entity) -> bool,
2058 {
2059 let table_indices = [<cached_tables_ $world:snake>](query_cache, tables, include);
2060 for position in 0..table_indices.len() {
2061 let table_index = table_indices[position];
2062 let table = &mut tables[table_index];
2063 if table.mask & exclude != 0 {
2064 continue;
2065 }
2066 for index in 0..table.entity_indices.len() {
2067 let entity = table.entity_indices[index];
2068 if filter(entity) {
2069 f(entity, table, index);
2070 }
2071 }
2072 }
2073 }
2074
2075 #[cfg(not(target_family = "wasm"))]
2076 #[allow(unused)]
2077 fn [<tables_par_for_each_mut_ $world:snake>]<F, P>(
2078 tables: &mut [[<$world ComponentArrays>]],
2079 include: u64,
2080 exclude: u64,
2081 filter: P,
2082 f: F,
2083 ) where
2084 F: Fn($crate::Entity, &mut [<$world ComponentArrays>], usize) + Send + Sync,
2085 P: Fn($crate::Entity) -> bool + Send + Sync,
2086 {
2087 use $crate::rayon::prelude::*;
2088 tables
2089 .par_iter_mut()
2090 .filter(|table| table.mask & include == include && table.mask & exclude == 0)
2091 .for_each(|table| {
2092 for index in 0..table.entity_indices.len() {
2093 let entity = table.entity_indices[index];
2094 if filter(entity) {
2095 f(entity, table, index);
2096 }
2097 }
2098 });
2099 }
2100
2101 #[allow(unused)]
2102 fn [<tables_for_each_mut_changed_ $world:snake>]<F, P>(
2103 tables: &mut [[<$world ComponentArrays>]],
2104 query_cache: &mut std::collections::HashMap<u64, Vec<usize>>,
2105 include: u64,
2106 exclude: u64,
2107 since_tick: u32,
2108 filter: P,
2109 mut f: F,
2110 ) where
2111 F: FnMut($crate::Entity, &mut [<$world ComponentArrays>], usize),
2112 P: Fn($crate::Entity) -> bool,
2113 {
2114 let table_indices = [<cached_tables_ $world:snake>](query_cache, tables, include);
2115 for position in 0..table_indices.len() {
2116 let table_index = table_indices[position];
2117 let table = &mut tables[table_index];
2118 if table.mask & exclude != 0 {
2119 continue;
2120 }
2121
2122 let mut table_changed = false;
2123 $(
2124 $(#[$comp_attr])*
2125 {
2126 if include & $mask != 0
2127 && table.mask & $mask != 0
2128 && $crate::tick_is_newer(table.[<$name _peak_changed>], since_tick)
2129 {
2130 table_changed = true;
2131 }
2132 }
2133 )*
2134 if !table_changed {
2135 continue;
2136 }
2137
2138 for index in 0..table.entity_indices.len() {
2139 let entity = table.entity_indices[index];
2140 if !filter(entity) {
2141 continue;
2142 }
2143
2144 let mut changed = false;
2145 $(
2146 $(#[$comp_attr])*
2147 {
2148 if include & $mask != 0
2149 && table.mask & $mask != 0
2150 && $crate::tick_is_newer(table.[<$name _changed>][index], since_tick)
2151 {
2152 changed = true;
2153 }
2154 }
2155 )*
2156 if changed {
2157 f(entity, table, index);
2158 }
2159 }
2160 }
2161 }
2162
2163 pub struct [<$world EntityQueryIter>]<'world> {
2164 tables: &'world [[<$world ComponentArrays>]],
2165 mask: u64,
2166 table_index: usize,
2167 array_index: usize,
2168 }
2169
2170 impl<'world> Iterator for [<$world EntityQueryIter>]<'world> {
2171 type Item = $crate::Entity;
2172
2173 fn next(&mut self) -> Option<Self::Item> {
2174 loop {
2175 if self.table_index >= self.tables.len() {
2176 return None;
2177 }
2178 let table = &self.tables[self.table_index];
2179 if table.mask & self.mask != self.mask {
2180 self.table_index += 1;
2181 self.array_index = 0;
2182 continue;
2183 }
2184 if self.array_index >= table.entity_indices.len() {
2185 self.table_index += 1;
2186 self.array_index = 0;
2187 continue;
2188 }
2189 let entity = table.entity_indices[self.array_index];
2190 self.array_index += 1;
2191 return Some(entity);
2192 }
2193 }
2194
2195 fn size_hint(&self) -> (usize, Option<usize>) {
2196 let mut remaining = 0;
2197 for table_index in self.table_index..self.tables.len() {
2198 let table = &self.tables[table_index];
2199 if table.mask & self.mask != self.mask {
2200 continue;
2201 }
2202 if table_index == self.table_index {
2203 remaining += table.entity_indices.len().saturating_sub(self.array_index);
2204 } else {
2205 remaining += table.entity_indices.len();
2206 }
2207 }
2208 (remaining, Some(remaining))
2209 }
2210 }
2211
2212 pub struct [<$world ChangedEntityQueryIter>]<'world> {
2213 tables: &'world [[<$world ComponentArrays>]],
2214 mask: u64,
2215 since_tick: u32,
2216 table_index: usize,
2217 array_index: usize,
2218 }
2219
2220 impl<'world> Iterator for [<$world ChangedEntityQueryIter>]<'world> {
2221 type Item = $crate::Entity;
2222
2223 fn next(&mut self) -> Option<Self::Item> {
2224 loop {
2225 if self.table_index >= self.tables.len() {
2226 return None;
2227 }
2228 let table = &self.tables[self.table_index];
2229 if table.mask & self.mask != self.mask {
2230 self.table_index += 1;
2231 self.array_index = 0;
2232 continue;
2233 }
2234 if self.array_index == 0 {
2235 let mut table_changed = false;
2236 $(
2237 $(#[$comp_attr])*
2238 {
2239 if self.mask & $mask != 0
2240 && table.mask & $mask != 0
2241 && $crate::tick_is_newer(table.[<$name _peak_changed>], self.since_tick)
2242 {
2243 table_changed = true;
2244 }
2245 }
2246 )*
2247 if !table_changed {
2248 self.table_index += 1;
2249 continue;
2250 }
2251 }
2252 if self.array_index >= table.entity_indices.len() {
2253 self.table_index += 1;
2254 self.array_index = 0;
2255 continue;
2256 }
2257 let index = self.array_index;
2258 self.array_index += 1;
2259
2260 let mut changed = false;
2261 $(
2262 $(#[$comp_attr])*
2263 {
2264 if self.mask & $mask != 0
2265 && table.mask & $mask != 0
2266 && $crate::tick_is_newer(table.[<$name _changed>][index], self.since_tick)
2267 {
2268 changed = true;
2269 }
2270 }
2271 )*
2272 if changed {
2273 return Some(table.entity_indices[index]);
2274 }
2275 }
2276 }
2277 }
2278
2279 $(
2280 $(#[$comp_attr])*
2281 pub struct [<$mask:camel QueryIter>]<'world> {
2282 tables: &'world [[<$world ComponentArrays>]],
2283 table_index: usize,
2284 array_index: usize,
2285 }
2286
2287 $(#[$comp_attr])*
2288 impl<'world> Iterator for [<$mask:camel QueryIter>]<'world> {
2289 type Item = &'world $type;
2290
2291 fn next(&mut self) -> Option<Self::Item> {
2292 loop {
2293 if self.table_index >= self.tables.len() {
2294 return None;
2295 }
2296 let table = &self.tables[self.table_index];
2297 if table.mask & $mask == 0 {
2298 self.table_index += 1;
2299 self.array_index = 0;
2300 continue;
2301 }
2302 if self.array_index >= table.$name.len() {
2303 self.table_index += 1;
2304 self.array_index = 0;
2305 continue;
2306 }
2307 let component = &table.$name[self.array_index];
2308 self.array_index += 1;
2309 return Some(component);
2310 }
2311 }
2312
2313 fn size_hint(&self) -> (usize, Option<usize>) {
2314 let mut remaining = 0;
2315 for table_index in self.table_index..self.tables.len() {
2316 let table = &self.tables[table_index];
2317 if table.mask & $mask == 0 {
2318 continue;
2319 }
2320 if table_index == self.table_index {
2321 remaining += table.$name.len().saturating_sub(self.array_index);
2322 } else {
2323 remaining += table.$name.len();
2324 }
2325 }
2326 (remaining, Some(remaining))
2327 }
2328 }
2329 )*
2330
2331 #[allow(unused)]
2332 pub struct [<$world QueryBuilder>]<'world> {
2333 world: &'world $world,
2334 include: u64,
2335 exclude: u64,
2336 }
2337
2338 #[allow(unused)]
2339 impl<'world> [<$world QueryBuilder>]<'world> {
2340 pub fn new(world: &'world $world) -> Self {
2341 Self {
2342 world,
2343 include: 0,
2344 exclude: 0,
2345 }
2346 }
2347
2348 pub fn with(mut self, mask: u64) -> Self {
2349 self.include |= mask;
2350 self
2351 }
2352
2353 pub fn without(mut self, mask: u64) -> Self {
2354 self.exclude |= mask;
2355 self
2356 }
2357
2358 pub fn iter<F>(self, f: F)
2359 where
2360 F: FnMut($crate::Entity, &[<$world ComponentArrays>], usize),
2361 {
2362 self.world.for_each(self.include, self.exclude, f);
2363 }
2364 }
2365
2366 #[allow(unused)]
2367 pub struct [<$world QueryBuilderMut>]<'world> {
2368 world: &'world mut $world,
2369 include: u64,
2370 exclude: u64,
2371 }
2372
2373 #[allow(unused)]
2374 impl<'world> [<$world QueryBuilderMut>]<'world> {
2375 pub fn new(world: &'world mut $world) -> Self {
2376 Self {
2377 world,
2378 include: 0,
2379 exclude: 0,
2380 }
2381 }
2382
2383 pub fn with(mut self, mask: u64) -> Self {
2384 self.include |= mask;
2385 self
2386 }
2387
2388 pub fn without(mut self, mask: u64) -> Self {
2389 self.exclude |= mask;
2390 self
2391 }
2392
2393 pub fn iter<F>(self, f: F)
2394 where
2395 F: FnMut($crate::Entity, &mut [<$world ComponentArrays>], usize),
2396 {
2397 self.world.for_each_mut(self.include, self.exclude, f);
2398 }
2399 }
2400
2401 #[allow(unused)]
2402 impl $world {
2403 const KERNEL_ALLOW_INSERT: bool = $allow_insert;
2404
2405 pub fn query(&self) -> [<$world QueryBuilder>]<'_> {
2406 [<$world QueryBuilder>]::new(self)
2407 }
2408
2409 pub fn query_mut(&mut self) -> [<$world QueryBuilderMut>]<'_> {
2410 [<$world QueryBuilderMut>]::new(self)
2411 }
2412
2413 pub fn contains_entity(&self, entity: $crate::Entity) -> bool {
2414 [<get_location_ $world:snake>](&self.entity_locations, entity).is_some()
2415 }
2416
2417 $(
2418 $(#[$comp_attr])*
2419 #[inline]
2420 pub fn [<get_ $name>](&self, entity: $crate::Entity) -> Option<&$type> {
2421 let (table_index, array_index) = [<get_location_ $world:snake>](&self.entity_locations, entity)?;
2422 let table = &self.tables[table_index];
2423 if table.mask & $mask == 0 {
2424 return None;
2425 }
2426 Some(&table.$name[array_index])
2427 }
2428
2429 $(#[$comp_attr])*
2430 #[inline]
2431 pub fn [<get_ $name _mut>](&mut self, entity: $crate::Entity) -> Option<&mut $type> {
2432 let (table_index, array_index) = [<get_location_ $world:snake>](&self.entity_locations, entity)?;
2433 let current_tick = self.current_tick;
2434 let table = &mut self.tables[table_index];
2435 if table.mask & $mask == 0 {
2436 return None;
2437 }
2438 table.[<$name _changed>][array_index] = current_tick;
2439 table.[<$name _peak_changed>] = current_tick;
2440 Some(&mut table.$name[array_index])
2441 }
2442
2443 $(#[$comp_attr])*
2444 #[inline]
2445 pub fn [<modify_ $name>]<R>(&mut self, entity: $crate::Entity, f: impl FnOnce(&mut $type) -> R) -> Option<R> {
2446 let (table_index, array_index) = [<get_location_ $world:snake>](&self.entity_locations, entity)?;
2447 let current_tick = self.current_tick;
2448 let table = &mut self.tables[table_index];
2449 if table.mask & $mask == 0 {
2450 return None;
2451 }
2452 table.[<$name _changed>][array_index] = current_tick;
2453 table.[<$name _peak_changed>] = current_tick;
2454 Some(f(&mut table.$name[array_index]))
2455 }
2456
2457 $(#[$comp_attr])*
2458 #[inline]
2459 pub fn [<entity_has_ $name>](&self, entity: $crate::Entity) -> bool {
2460 self.entity_has_components(entity, $mask)
2461 }
2462
2463 $(#[$comp_attr])*
2464 #[inline]
2465 pub fn [<set_ $name>](&mut self, entity: $crate::Entity, value: $type) {
2466 let current_tick = self.current_tick;
2467 if let Some((table_index, array_index)) = [<get_location_ $world:snake>](&self.entity_locations, entity) {
2468 let table = &mut self.tables[table_index];
2469 if table.mask & $mask != 0 {
2470 table.$name[array_index] = value;
2471 table.[<$name _changed>][array_index] = current_tick;
2472 table.[<$name _peak_changed>] = current_tick;
2473 return;
2474 }
2475 }
2476 if self.add_components(entity, $mask) {
2477 if let Some((table_index, array_index)) = [<get_location_ $world:snake>](&self.entity_locations, entity) {
2478 let table = &mut self.tables[table_index];
2479 table.$name[array_index] = value;
2480 table.[<$name _changed>][array_index] = current_tick;
2481 table.[<$name _peak_changed>] = current_tick;
2482 }
2483 }
2484 }
2485
2486 $(#[$comp_attr])*
2487 #[inline]
2488 pub fn [<add_ $name>](&mut self, entity: $crate::Entity) {
2489 self.add_components(entity, $mask);
2490 }
2491
2492 $(#[$comp_attr])*
2493 #[inline]
2494 pub fn [<remove_ $name>](&mut self, entity: $crate::Entity) -> bool {
2495 self.remove_components(entity, $mask)
2496 }
2497
2498 $(#[$comp_attr])*
2499 #[inline]
2500 pub fn [<query_ $name>](&self) -> [<$mask:camel QueryIter>]<'_> {
2501 [<$mask:camel QueryIter>] {
2502 tables: &self.tables,
2503 table_index: 0,
2504 array_index: 0,
2505 }
2506 }
2507
2508 $(#[$comp_attr])*
2509 pub fn [<iter_ $name>]<F>(&self, mut f: F)
2510 where
2511 F: FnMut($crate::Entity, &$type),
2512 {
2513 [<tables_for_each_ $world:snake>](
2514 &self.tables,
2515 &self.query_cache,
2516 $mask,
2517 0,
2518 |_| true,
2519 |entity, table, index| f(entity, &table.$name[index]),
2520 );
2521 }
2522
2523 $(#[$comp_attr])*
2524 pub fn [<for_each_ $name _mut>]<F>(&mut self, mut f: F)
2525 where
2526 F: FnMut(&mut $type),
2527 {
2528 let table_indices =
2529 [<cached_tables_ $world:snake>](&mut self.query_cache, &self.tables, $mask);
2530 for position in 0..table_indices.len() {
2531 let table_index = table_indices[position];
2532 for component in &mut self.tables[table_index].$name {
2533 f(component);
2534 }
2535 }
2536 }
2537
2538 $(#[$comp_attr])*
2539 #[cfg(not(target_family = "wasm"))]
2540 pub fn [<par_for_each_ $name _mut>]<F>(&mut self, f: F)
2541 where
2542 F: Fn(&mut $type) + Send + Sync,
2543 {
2544 use $crate::rayon::prelude::*;
2545 self.tables
2546 .par_iter_mut()
2547 .filter(|table| table.mask & $mask != 0)
2548 .for_each(|table| {
2549 table.$name.par_iter_mut().for_each(|component| f(component));
2550 });
2551 }
2552
2553 $(#[$comp_attr])*
2554 pub fn [<iter_ $name _slices>](&self) -> impl Iterator<Item = &[$type]> {
2555 self.tables
2556 .iter()
2557 .filter(|table| table.mask & $mask != 0)
2558 .map(|table| table.$name.as_slice())
2559 }
2560
2561 $(#[$comp_attr])*
2562 pub fn [<iter_ $name _slices_mut>](&mut self) -> impl Iterator<Item = &mut [$type]> {
2563 self.tables
2564 .iter_mut()
2565 .filter(|table| table.mask & $mask != 0)
2566 .map(|table| table.$name.as_mut_slice())
2567 }
2568 )*
2569
2570 pub fn spawn_entities_with(
2571 &mut self,
2572 allocator: &mut $crate::EntityAllocator,
2573 mask: u64,
2574 count: usize,
2575 ) -> Vec<$crate::Entity> {
2576 debug_assert_eq!(
2577 mask & ![<$world:snake:upper _ALL_COMPONENTS>],
2578 0,
2579 "spawn masks must not contain tag bits or unknown component bits"
2580 );
2581 let table_index = [<get_or_create_table_ $world:snake>](self, mask);
2582 let start_index = self.tables[table_index].entity_indices.len();
2583
2584 self.tables[table_index].entity_indices.reserve(count);
2585 $(
2586 $(#[$comp_attr])*
2587 {
2588 if mask & $mask != 0 {
2589 self.tables[table_index].$name.reserve(count);
2590 self.tables[table_index].[<$name _changed>].reserve(count);
2591 self.tables[table_index].[<$name _peak_changed>] = self.current_tick;
2592 }
2593 }
2594 )*
2595
2596 let mut entities = Vec::new();
2597 allocator.allocate_batch(count, &mut entities);
2598 for (offset, &entity) in entities.iter().enumerate() {
2599 self.tables[table_index].entity_indices.push(entity);
2600 $(
2601 $(#[$comp_attr])*
2602 {
2603 if mask & $mask != 0 {
2604 self.tables[table_index].$name.push(<$type>::default());
2605 self.tables[table_index].[<$name _changed>].push(self.current_tick);
2606 }
2607 }
2608 )*
2609
2610 [<insert_location_ $world:snake>](
2611 &mut self.entity_locations,
2612 entity,
2613 (table_index, start_index + offset),
2614 );
2615 self.record_structural(entity, $crate::StructuralChangeKind::Spawned, mask);
2616 }
2617
2618 entities
2619 }
2620
2621 pub fn spawn_batch_with<F>(
2622 &mut self,
2623 allocator: &mut $crate::EntityAllocator,
2624 mask: u64,
2625 count: usize,
2626 mut init: F,
2627 ) -> Vec<$crate::Entity>
2628 where
2629 F: FnMut(&mut [<$world ComponentArrays>], usize),
2630 {
2631 debug_assert_eq!(
2632 mask & ![<$world:snake:upper _ALL_COMPONENTS>],
2633 0,
2634 "spawn masks must not contain tag bits or unknown component bits"
2635 );
2636 let table_index = [<get_or_create_table_ $world:snake>](self, mask);
2637 let start_index = self.tables[table_index].entity_indices.len();
2638
2639 self.tables[table_index].entity_indices.reserve(count);
2640 $(
2641 $(#[$comp_attr])*
2642 {
2643 if mask & $mask != 0 {
2644 self.tables[table_index].$name.reserve(count);
2645 self.tables[table_index].[<$name _changed>].reserve(count);
2646 self.tables[table_index].[<$name _peak_changed>] = self.current_tick;
2647 }
2648 }
2649 )*
2650
2651 let mut entities = Vec::new();
2652 allocator.allocate_batch(count, &mut entities);
2653 for (offset, &entity) in entities.iter().enumerate() {
2654 self.tables[table_index].entity_indices.push(entity);
2655 $(
2656 $(#[$comp_attr])*
2657 {
2658 if mask & $mask != 0 {
2659 self.tables[table_index].$name.push(<$type>::default());
2660 self.tables[table_index].[<$name _changed>].push(self.current_tick);
2661 }
2662 }
2663 )*
2664
2665 [<insert_location_ $world:snake>](
2666 &mut self.entity_locations,
2667 entity,
2668 (table_index, start_index + offset),
2669 );
2670 self.record_structural(entity, $crate::StructuralChangeKind::Spawned, mask);
2671
2672 init(&mut self.tables[table_index], start_index + offset);
2673 }
2674
2675 entities
2676 }
2677
2678 pub fn despawn_entities_with(
2681 &mut self,
2682 allocator: &mut $crate::EntityAllocator,
2683 entities: &[$crate::Entity],
2684 ) -> Vec<$crate::Entity> {
2685 let mut despawned = Vec::with_capacity(entities.len());
2686 for &entity in entities {
2687 if allocator.deallocate(entity) {
2688 self.retire_entity(entity);
2689 despawned.push(entity);
2690 }
2691 }
2692 despawned
2693 }
2694
2695 pub fn retire_entity(&mut self, entity: $crate::Entity) -> bool {
2705 let mut removed = false;
2706 if let Some(loc) = self.entity_locations.get_mut(entity.id) {
2707 if loc.allocated && loc.generation == entity.generation {
2708 let table_index = loc.table_index as usize;
2709 let array_index = loc.array_index as usize;
2710
2711 self.entity_locations.mark_deallocated(entity.id);
2712
2713 let despawned_mask = if table_index < self.tables.len() {
2714 self.tables[table_index].mask
2715 } else {
2716 0
2717 };
2718 self.record_structural(entity, $crate::StructuralChangeKind::Despawned, despawned_mask);
2719
2720 if table_index < self.tables.len() {
2721 if let Some(swapped) = [<remove_from_table_ $world:snake>](&mut self.tables[table_index], array_index) {
2722 if let Some(swapped_location) = self.entity_locations.get_mut(swapped.id) {
2723 if swapped_location.allocated {
2724 swapped_location.array_index = array_index as u32;
2725 }
2726 }
2727 }
2728 }
2729 removed = true;
2730 }
2731 }
2732
2733 let next_generation = entity.generation.wrapping_add(1);
2734 let should_retire = match self.entity_locations.get(entity.id) {
2735 None => true,
2736 Some(loc) => {
2737 !loc.allocated && $crate::tick_is_newer(next_generation, loc.generation)
2738 }
2739 };
2740 if should_retire {
2741 self.entity_locations.ensure_slot(entity.id, next_generation);
2742 }
2743
2744 removed
2745 }
2746
2747 pub fn add_components(&mut self, entity: $crate::Entity, mask: u64) -> bool {
2755 debug_assert_eq!(
2756 mask & ![<$world:snake:upper _ALL_COMPONENTS>],
2757 0,
2758 "component masks must not contain tag bits or unknown component bits"
2759 );
2760 if let Some((table_index, array_index)) = [<get_location_ $world:snake>](&self.entity_locations, entity) {
2761 let current_mask = self.tables[table_index].mask;
2762 if current_mask & mask == mask {
2763 return true;
2764 }
2765
2766 let target_table = if mask.count_ones() == 1 {
2767 [<get_component_index_ $world:snake>](mask)
2768 .and_then(|component_index| self.table_edges[table_index].add_edges[component_index])
2769 } else {
2770 self.table_edges[table_index].multi_add_cache.get(&mask).copied()
2771 };
2772
2773 let new_table_index = target_table.unwrap_or_else(|| {
2774 let new_index = [<get_or_create_table_ $world:snake>](self, current_mask | mask);
2775 self.table_edges[table_index].multi_add_cache.insert(mask, new_index);
2776 new_index
2777 });
2778
2779 [<move_entity_ $world:snake>](self, entity, table_index, array_index, new_table_index);
2780 self.record_structural(entity, $crate::StructuralChangeKind::ComponentsAdded, mask & !current_mask);
2781 true
2782 } else if !Self::KERNEL_ALLOW_INSERT {
2783 false
2784 } else {
2785 if let Some(loc) = self.entity_locations.get(entity.id) {
2786 if loc.allocated || loc.generation != entity.generation {
2787 return false;
2788 }
2789 }
2790
2791 let table_index = [<get_or_create_table_ $world:snake>](self, mask);
2792 let start_index = self.tables[table_index].entity_indices.len();
2793
2794 self.tables[table_index].entity_indices.push(entity);
2795 $(
2796 $(#[$comp_attr])*
2797 {
2798 if mask & $mask != 0 {
2799 self.tables[table_index].$name.push(<$type>::default());
2800 self.tables[table_index].[<$name _changed>].push(self.current_tick);
2801 self.tables[table_index].[<$name _peak_changed>] = self.current_tick;
2802 }
2803 }
2804 )*
2805
2806 [<insert_location_ $world:snake>](
2807 &mut self.entity_locations,
2808 entity,
2809 (table_index, start_index),
2810 );
2811 self.record_structural(entity, $crate::StructuralChangeKind::Spawned, mask);
2812 true
2813 }
2814 }
2815
2816 pub fn remove_components(&mut self, entity: $crate::Entity, mask: u64) -> bool {
2817 debug_assert_eq!(
2818 mask & ![<$world:snake:upper _ALL_COMPONENTS>],
2819 0,
2820 "component masks must not contain tag bits or unknown component bits"
2821 );
2822 if let Some((table_index, array_index)) = [<get_location_ $world:snake>](&self.entity_locations, entity) {
2823 let current_mask = self.tables[table_index].mask;
2824 if current_mask & mask == 0 {
2825 return true;
2826 }
2827
2828 let target_table = if mask.count_ones() == 1 {
2829 [<get_component_index_ $world:snake>](mask)
2830 .and_then(|component_index| self.table_edges[table_index].remove_edges[component_index])
2831 } else {
2832 self.table_edges[table_index].multi_remove_cache.get(&mask).copied()
2833 };
2834
2835 let new_table_index = target_table.unwrap_or_else(|| {
2836 let new_index = [<get_or_create_table_ $world:snake>](self, current_mask & !mask);
2837 self.table_edges[table_index].multi_remove_cache.insert(mask, new_index);
2838 new_index
2839 });
2840
2841 [<move_entity_ $world:snake>](self, entity, table_index, array_index, new_table_index);
2842 self.record_structural(entity, $crate::StructuralChangeKind::ComponentsRemoved, current_mask & mask);
2843 true
2844 } else {
2845 false
2846 }
2847 }
2848
2849 pub fn component_mask(&self, entity: $crate::Entity) -> Option<u64> {
2850 [<get_location_ $world:snake>](&self.entity_locations, entity)
2851 .map(|(table_index, _)| self.tables[table_index].mask)
2852 }
2853
2854 pub fn get_all_entities(&self) -> Vec<$crate::Entity> {
2855 let mut result = Vec::with_capacity(self.entity_count());
2856 for table in &self.tables {
2857 result.extend(table.entity_indices.iter().copied());
2858 }
2859 result
2860 }
2861
2862 pub fn entity_count(&self) -> usize {
2863 self.tables.iter().map(|table| table.entity_indices.len()).sum()
2864 }
2865
2866 pub fn entity_has_components(&self, entity: $crate::Entity, components: u64) -> bool {
2867 self.component_mask(entity).unwrap_or(0) & components == components
2868 }
2869
2870 pub fn increment_tick(&mut self) {
2871 self.last_tick = self.current_tick;
2872 self.current_tick = self.current_tick.wrapping_add(1);
2873 }
2874
2875 pub fn current_tick(&self) -> u32 {
2876 self.current_tick
2877 }
2878
2879 pub fn last_tick(&self) -> u32 {
2880 self.last_tick
2881 }
2882
2883 fn record_structural(&mut self, entity: $crate::Entity, kind: $crate::StructuralChangeKind, mask: u64) {
2884 if self.structural_log.len() >= $crate::STRUCTURAL_LOG_CAPACITY {
2885 self.structural_log.clear();
2886 }
2887 self.structural_sequence += 1;
2888 self.structural_log.push($crate::StructuralChange {
2889 sequence: self.structural_sequence,
2890 entity,
2891 kind,
2892 mask,
2893 });
2894 }
2895
2896 pub fn structural_sequence(&self) -> u64 {
2897 self.structural_sequence
2898 }
2899
2900 pub fn structural_changes_since(&self, cursor: u64) -> &[$crate::StructuralChange] {
2901 let start = self.structural_log.partition_point(|change| change.sequence <= cursor);
2902 &self.structural_log[start..]
2903 }
2904
2905 pub fn trim_structural_log(&mut self, up_to_sequence: u64) {
2906 let end = self.structural_log.partition_point(|change| change.sequence <= up_to_sequence);
2907 self.structural_log.drain(..end);
2908 }
2909
2910 pub fn clear_structural_log(&mut self) {
2911 self.structural_log.clear();
2912 }
2913
2914 pub fn query_entities(&self, mask: u64) -> [<$world EntityQueryIter>]<'_> {
2915 debug_assert_eq!(
2916 mask & ![<$world:snake:upper _ALL_COMPONENTS>],
2917 0,
2918 "query_entities takes component masks only; use for_each or query_<tag> for tag filtering"
2919 );
2920 [<$world EntityQueryIter>] {
2921 tables: &self.tables,
2922 mask,
2923 table_index: 0,
2924 array_index: 0,
2925 }
2926 }
2927
2928 pub fn query_entities_changed(&self, mask: u64) -> [<$world ChangedEntityQueryIter>]<'_> {
2929 self.query_entities_changed_since(mask, self.last_tick)
2930 }
2931
2932 pub fn query_entities_changed_since(&self, mask: u64, since_tick: u32) -> [<$world ChangedEntityQueryIter>]<'_> {
2933 debug_assert_eq!(
2934 mask & ![<$world:snake:upper _ALL_COMPONENTS>],
2935 0,
2936 "changed queries take component masks only; use for_each_mut_changed for tag filtering"
2937 );
2938 [<$world ChangedEntityQueryIter>] {
2939 tables: &self.tables,
2940 mask,
2941 since_tick,
2942 table_index: 0,
2943 array_index: 0,
2944 }
2945 }
2946
2947 pub fn mark_changed(&mut self, entity: $crate::Entity, mask: u64) -> bool {
2955 let Some((table_index, array_index)) = [<get_location_ $world:snake>](&self.entity_locations, entity) else {
2956 return false;
2957 };
2958 let current_tick = self.current_tick;
2959 let table = &mut self.tables[table_index];
2960 let present = table.mask & mask & [<$world:snake:upper _ALL_COMPONENTS>];
2961 if present == 0 {
2962 return false;
2963 }
2964 $(
2965 $(#[$comp_attr])*
2966 {
2967 if present & $mask != 0 {
2968 table.[<$name _changed>][array_index] = current_tick;
2969 table.[<$name _peak_changed>] = current_tick;
2970 }
2971 }
2972 )*
2973 true
2974 }
2975
2976 pub fn query_first_entity(&self, mask: u64) -> Option<$crate::Entity> {
2977 debug_assert_eq!(
2978 mask & ![<$world:snake:upper _ALL_COMPONENTS>],
2979 0,
2980 "query_first_entity takes component masks only"
2981 );
2982 for table in &self.tables {
2983 if table.mask & mask != mask {
2984 continue;
2985 }
2986 if let Some(&entity) = table.entity_indices.first() {
2987 return Some(entity);
2988 }
2989 }
2990 None
2991 }
2992
2993 #[inline]
2994 pub fn for_each_with_tags<F>(
2995 &self,
2996 include: u64,
2997 exclude: u64,
2998 include_tags: &[&$crate::SparseTagSet],
2999 exclude_tags: &[&$crate::SparseTagSet],
3000 f: F,
3001 ) where
3002 F: FnMut($crate::Entity, &[<$world ComponentArrays>], usize),
3003 {
3004 [<tables_for_each_ $world:snake>](
3005 &self.tables,
3006 &self.query_cache,
3007 include,
3008 exclude,
3009 |entity| {
3010 include_tags.iter().all(|tag_set| tag_set.contains(entity))
3011 && !exclude_tags.iter().any(|tag_set| tag_set.contains(entity))
3012 },
3013 f,
3014 );
3015 }
3016
3017 #[inline]
3018 pub fn for_each_mut_with_tags<F>(
3019 &mut self,
3020 include: u64,
3021 exclude: u64,
3022 include_tags: &[&$crate::SparseTagSet],
3023 exclude_tags: &[&$crate::SparseTagSet],
3024 f: F,
3025 ) where
3026 F: FnMut($crate::Entity, &mut [<$world ComponentArrays>], usize),
3027 {
3028 [<tables_for_each_mut_ $world:snake>](
3029 &mut self.tables,
3030 &mut self.query_cache,
3031 include,
3032 exclude,
3033 |entity| {
3034 include_tags.iter().all(|tag_set| tag_set.contains(entity))
3035 && !exclude_tags.iter().any(|tag_set| tag_set.contains(entity))
3036 },
3037 f,
3038 );
3039 }
3040
3041 #[cfg(not(target_family = "wasm"))]
3042 #[inline]
3043 pub fn par_for_each_mut_with_tags<F>(
3044 &mut self,
3045 include: u64,
3046 exclude: u64,
3047 include_tags: &[&$crate::SparseTagSet],
3048 exclude_tags: &[&$crate::SparseTagSet],
3049 f: F,
3050 ) where
3051 F: Fn($crate::Entity, &mut [<$world ComponentArrays>], usize) + Send + Sync,
3052 {
3053 [<tables_par_for_each_mut_ $world:snake>](
3054 &mut self.tables,
3055 include,
3056 exclude,
3057 |entity| {
3058 include_tags.iter().all(|tag_set| tag_set.contains(entity))
3059 && !exclude_tags.iter().any(|tag_set| tag_set.contains(entity))
3060 },
3061 f,
3062 );
3063 }
3064 }
3065 }
3066 };
3067}
3068
3069#[macro_export]
3070macro_rules! ecs_impl {
3071 (
3072 $world:ident {
3073 $($(#[$comp_attr:meta])* $name:ident: $type:ty => $mask:ident),* $(,)?
3074 }
3075 Tags {
3076 $($tag_name:ident => $tag_mask:ident),* $(,)?
3077 }
3078 Events {
3079 $($event_name:ident: $event_type:ty),* $(,)?
3080 }
3081 $resources:ident {
3082 $($(#[$attr:meta])* $resource_name:ident: $resource_type:ty),* $(,)?
3083 }
3084 ) => {
3085 $crate::ecs_kernel_impl! {
3086 $world,
3087 false,
3088 { $($(#[$comp_attr])* $name: $type => $mask),* }
3089 }
3090
3091 $crate::paste::paste! {
3092 #[allow(non_camel_case_types)]
3093 #[allow(unused)]
3094 pub enum [<$world Tag>] {
3095 $($tag_name,)*
3096 }
3097
3098 $(pub const $tag_mask: u64 = 1 << (63 - ([<$world Tag>]::$tag_name as u64));)*
3099
3100 #[allow(unused)]
3101 pub const ALL_TAGS_MASK: u64 = 0 $(| $tag_mask)*;
3102
3103 #[allow(unused)]
3104 pub const [<$world:snake:upper _TAG_COUNT>]: usize = {
3105 let mut count = 0;
3106 $(count += 1; let _ = [<$world Tag>]::$tag_name;)*
3107 count
3108 };
3109
3110 const _: () = assert!(
3111 [<$world:snake:upper _COMPONENT_COUNT>] + [<$world:snake:upper _TAG_COUNT>] <= 64,
3112 "components plus tags must fit in a u64 mask"
3113 );
3114
3115 #[allow(unused)]
3116 pub type ComponentArrays = [<$world ComponentArrays>];
3117 }
3118
3119 #[allow(unused)]
3120 #[derive(Default, Clone)]
3121 pub struct EntityBuilder {
3122 $($(#[$comp_attr])* $name: Option<$type>,)*
3123 }
3124
3125 #[allow(unused)]
3126 impl EntityBuilder {
3127 pub fn new() -> Self {
3128 Self::default()
3129 }
3130
3131 $(
3132 $(#[$comp_attr])*
3133 $crate::paste::paste! {
3134 pub fn [<with_$name>](mut self, value: $type) -> Self {
3135 self.$name = Some(value);
3136 self
3137 }
3138 }
3139 )*
3140
3141 pub fn spawn(self, world: &mut $world, instances: usize) -> Vec<$crate::Entity> {
3142 let mut mask = 0;
3143 $(
3144 $(#[$comp_attr])*
3145 if self.$name.is_some() {
3146 mask |= $mask;
3147 }
3148 )*
3149 let entities = world.spawn_entities(mask, instances);
3150 let last_entity_index = entities.len().saturating_sub(1);
3151 for (entity_index, entity) in entities.iter().enumerate() {
3152 if entity_index == last_entity_index {
3153 $(
3154 $(#[$comp_attr])*
3155 $crate::paste::paste! {
3156 if let Some(component) = self.$name {
3157 world.[<set_$name>](*entity, component);
3158 }
3159 }
3160 )*
3161 break;
3162 } else {
3163 $(
3164 $(#[$comp_attr])*
3165 $crate::paste::paste! {
3166 if let Some(ref component) = self.$name {
3167 world.[<set_$name>](*entity, component.clone());
3168 }
3169 }
3170 )*
3171 }
3172 }
3173 entities
3174 }
3175 }
3176
3177 $crate::paste::paste! {
3178 pub enum Command {
3179 SpawnEntities { mask: u64, count: usize },
3180 DespawnEntity { entity: $crate::Entity },
3181 DespawnEntities { entities: Vec<$crate::Entity> },
3182 AddComponents { entity: $crate::Entity, mask: u64 },
3183 RemoveComponents { entity: $crate::Entity, mask: u64 },
3184 $(
3185 $(#[$comp_attr])*
3186 [<Set $mask:camel>] { entity: $crate::Entity, value: $type },
3187 )*
3188 $(
3189 [<Add $tag_mask:camel>] { entity: $crate::Entity },
3190 [<Remove $tag_mask:camel>] { entity: $crate::Entity },
3191 )*
3192 }
3193 }
3194
3195 $crate::paste::paste! {
3196 #[allow(unused)]
3197 #[derive(Default)]
3198 pub struct $world {
3199 pub entity_locations: $crate::EntityLocations,
3200 pub tables: Vec<ComponentArrays>,
3201 pub allocator: $crate::EntityAllocator,
3202 pub resources: $resources,
3203 pub table_edges: Vec<$crate::ArchetypeEdges>,
3204 pub table_lookup: std::collections::HashMap<u64, usize>,
3205 pub query_cache: std::collections::HashMap<u64, Vec<usize>>,
3206 pub current_tick: u32,
3207 pub last_tick: u32,
3208 pub structural_log: Vec<$crate::StructuralChange>,
3209 pub structural_sequence: u64,
3210 $(pub $tag_name: $crate::SparseTagSet,)*
3211 pub command_buffer: Vec<Command>,
3212 $(pub $event_name: $crate::EventChannel<$event_type>,)*
3213 }
3214 }
3215
3216 #[allow(unused)]
3217 impl $world {
3218 pub fn spawn_entities(&mut self, mask: u64, count: usize) -> Vec<$crate::Entity> {
3219 let mut allocator = std::mem::take(&mut self.allocator);
3220 let entities = self.spawn_entities_with(&mut allocator, mask, count);
3221 self.allocator = allocator;
3222 entities
3223 }
3224
3225 pub fn spawn_batch<F>(&mut self, mask: u64, count: usize, init: F) -> Vec<$crate::Entity>
3226 where
3227 F: FnMut(&mut ComponentArrays, usize),
3228 {
3229 let mut allocator = std::mem::take(&mut self.allocator);
3230 let entities = self.spawn_batch_with(&mut allocator, mask, count, init);
3231 self.allocator = allocator;
3232 entities
3233 }
3234
3235 pub fn despawn_entities(&mut self, entities: &[$crate::Entity]) -> Vec<$crate::Entity> {
3236 let mut allocator = std::mem::take(&mut self.allocator);
3237 let despawned = self.despawn_entities_with(&mut allocator, entities);
3238 self.allocator = allocator;
3239 $(
3240 for &entity in &despawned {
3241 self.$tag_name.remove(entity);
3242 }
3243 )*
3244 despawned
3245 }
3246
3247 pub fn is_alive(&self, entity: $crate::Entity) -> bool {
3248 self.allocator.is_alive(entity)
3249 }
3250
3251 fn entity_matches_tags(&self, entity: $crate::Entity, include_tags: u64, exclude_tags: u64) -> bool {
3252 $(
3253 if include_tags & $tag_mask != 0 && !self.$tag_name.contains(entity) {
3254 return false;
3255 }
3256 if exclude_tags & $tag_mask != 0 && self.$tag_name.contains(entity) {
3257 return false;
3258 }
3259 )*
3260 let _ = (entity, include_tags, exclude_tags);
3261 true
3262 }
3263
3264 fn reduce_tag_masks(&self, tag_include: u64, tag_exclude: u64) -> Option<(u64, u64)> {
3269 let mut reduced_exclude = tag_exclude;
3270 $(
3271 if tag_include & $tag_mask != 0 && self.$tag_name.is_empty() {
3272 return None;
3273 }
3274 if reduced_exclude & $tag_mask != 0 && self.$tag_name.is_empty() {
3275 reduced_exclude &= !$tag_mask;
3276 }
3277 )*
3278 Some((tag_include, reduced_exclude))
3279 }
3280 }
3281
3282 $crate::paste::paste! {
3283 #[allow(unused)]
3284 impl $world {
3285 #[inline]
3286 pub fn for_each<F>(&self, include: u64, exclude: u64, f: F)
3287 where
3288 F: FnMut($crate::Entity, &ComponentArrays, usize),
3289 {
3290 let component_include = include & !ALL_TAGS_MASK;
3291 let component_exclude = exclude & !ALL_TAGS_MASK;
3292 let Some((tag_include, tag_exclude)) =
3293 self.reduce_tag_masks(include & ALL_TAGS_MASK, exclude & ALL_TAGS_MASK)
3294 else {
3295 return;
3296 };
3297
3298 if tag_include == 0 && tag_exclude == 0 {
3299 [<tables_for_each_ $world:snake>](
3300 &self.tables,
3301 &self.query_cache,
3302 component_include,
3303 component_exclude,
3304 |_| true,
3305 f,
3306 );
3307 } else {
3308 [<tables_for_each_ $world:snake>](
3309 &self.tables,
3310 &self.query_cache,
3311 component_include,
3312 component_exclude,
3313 |entity| self.entity_matches_tags(entity, tag_include, tag_exclude),
3314 f,
3315 );
3316 }
3317 }
3318
3319 #[inline]
3320 pub fn for_each_mut<F>(&mut self, include: u64, exclude: u64, f: F)
3321 where
3322 F: FnMut($crate::Entity, &mut ComponentArrays, usize),
3323 {
3324 let component_include = include & !ALL_TAGS_MASK;
3325 let component_exclude = exclude & !ALL_TAGS_MASK;
3326 let Some((tag_include, tag_exclude)) =
3327 self.reduce_tag_masks(include & ALL_TAGS_MASK, exclude & ALL_TAGS_MASK)
3328 else {
3329 return;
3330 };
3331
3332 if tag_include == 0 && tag_exclude == 0 {
3333 [<tables_for_each_mut_ $world:snake>](
3334 &mut self.tables,
3335 &mut self.query_cache,
3336 component_include,
3337 component_exclude,
3338 |_| true,
3339 f,
3340 );
3341 } else {
3342 let Self { tables, query_cache, $($tag_name,)* .. } = self;
3343 $(let $tag_name = &*$tag_name;)*
3344 [<tables_for_each_mut_ $world:snake>](
3345 tables,
3346 query_cache,
3347 component_include,
3348 component_exclude,
3349 |entity| {
3350 let _ = entity;
3351 $(
3352 if tag_include & $tag_mask != 0 && !$tag_name.contains(entity) {
3353 return false;
3354 }
3355 if tag_exclude & $tag_mask != 0 && $tag_name.contains(entity) {
3356 return false;
3357 }
3358 )*
3359 true
3360 },
3361 f,
3362 );
3363 }
3364 }
3365
3366 #[cfg(not(target_family = "wasm"))]
3367 #[inline]
3368 pub fn par_for_each_mut<F>(&mut self, include: u64, exclude: u64, f: F)
3369 where
3370 F: Fn($crate::Entity, &mut ComponentArrays, usize) + Send + Sync,
3371 {
3372 let component_include = include & !ALL_TAGS_MASK;
3373 let component_exclude = exclude & !ALL_TAGS_MASK;
3374 let Some((tag_include, tag_exclude)) =
3375 self.reduce_tag_masks(include & ALL_TAGS_MASK, exclude & ALL_TAGS_MASK)
3376 else {
3377 return;
3378 };
3379
3380 if tag_include == 0 && tag_exclude == 0 {
3381 [<tables_par_for_each_mut_ $world:snake>](
3382 &mut self.tables,
3383 component_include,
3384 component_exclude,
3385 |_| true,
3386 f,
3387 );
3388 } else {
3389 let Self { tables, $($tag_name,)* .. } = self;
3390 $(let $tag_name = &*$tag_name;)*
3391 [<tables_par_for_each_mut_ $world:snake>](
3392 tables,
3393 component_include,
3394 component_exclude,
3395 |entity| {
3396 let _ = entity;
3397 $(
3398 if tag_include & $tag_mask != 0 && !$tag_name.contains(entity) {
3399 return false;
3400 }
3401 if tag_exclude & $tag_mask != 0 && $tag_name.contains(entity) {
3402 return false;
3403 }
3404 )*
3405 true
3406 },
3407 f,
3408 );
3409 }
3410 }
3411
3412 #[inline]
3413 pub fn for_each_mut_changed<F>(&mut self, include: u64, exclude: u64, f: F)
3414 where
3415 F: FnMut($crate::Entity, &mut ComponentArrays, usize),
3416 {
3417 let since_tick = self.last_tick;
3418 self.for_each_mut_changed_since(include, exclude, since_tick, f);
3419 }
3420
3421 pub fn for_each_mut_changed_since<F>(&mut self, include: u64, exclude: u64, since_tick: u32, f: F)
3422 where
3423 F: FnMut($crate::Entity, &mut ComponentArrays, usize),
3424 {
3425 let component_include = include & !ALL_TAGS_MASK;
3426 let component_exclude = exclude & !ALL_TAGS_MASK;
3427 let Some((tag_include, tag_exclude)) =
3428 self.reduce_tag_masks(include & ALL_TAGS_MASK, exclude & ALL_TAGS_MASK)
3429 else {
3430 return;
3431 };
3432
3433 if tag_include == 0 && tag_exclude == 0 {
3434 [<tables_for_each_mut_changed_ $world:snake>](
3435 &mut self.tables,
3436 &mut self.query_cache,
3437 component_include,
3438 component_exclude,
3439 since_tick,
3440 |_| true,
3441 f,
3442 );
3443 } else {
3444 let Self { tables, query_cache, $($tag_name,)* .. } = self;
3445 $(let $tag_name = &*$tag_name;)*
3446 [<tables_for_each_mut_changed_ $world:snake>](
3447 tables,
3448 query_cache,
3449 component_include,
3450 component_exclude,
3451 since_tick,
3452 |entity| {
3453 let _ = entity;
3454 $(
3455 if tag_include & $tag_mask != 0 && !$tag_name.contains(entity) {
3456 return false;
3457 }
3458 if tag_exclude & $tag_mask != 0 && $tag_name.contains(entity) {
3459 return false;
3460 }
3461 )*
3462 true
3463 },
3464 f,
3465 );
3466 }
3467 }
3468
3469 $(
3470 $(#[$comp_attr])*
3471 pub fn [<query_ $name _mut>]<F>(&mut self, mask: u64, mut f: F)
3478 where
3479 F: FnMut($crate::Entity, &mut $type),
3480 {
3481 let component_include = (mask & !ALL_TAGS_MASK) | $mask;
3482 let tag_include = mask & ALL_TAGS_MASK;
3483 let current_tick = self.current_tick;
3484
3485 for table_index in 0..self.tables.len() {
3486 if self.tables[table_index].mask & component_include != component_include {
3487 continue;
3488 }
3489 if tag_include == 0 {
3490 let table = &mut self.tables[table_index];
3491 for index in 0..table.entity_indices.len() {
3492 let entity = table.entity_indices[index];
3493 table.[<$name _changed>][index] = current_tick;
3494 table.[<$name _peak_changed>] = current_tick;
3495 f(entity, &mut table.$name[index]);
3496 }
3497 } else {
3498 for index in 0..self.tables[table_index].entity_indices.len() {
3499 let entity = self.tables[table_index].entity_indices[index];
3500 if !self.entity_matches_tags(entity, tag_include, 0) {
3501 continue;
3502 }
3503 let table = &mut self.tables[table_index];
3504 table.[<$name _changed>][index] = current_tick;
3505 table.[<$name _peak_changed>] = current_tick;
3506 f(entity, &mut table.$name[index]);
3507 }
3508 }
3509 }
3510 }
3511
3512 $(#[$comp_attr])*
3513 pub fn [<iter_ $name _mut>]<F>(&mut self, mut f: F)
3514 where
3515 F: FnMut($crate::Entity, &mut $type),
3516 {
3517 self.[<query_ $name _mut>](0, |entity, component| f(entity, component));
3518 }
3519 )*
3520
3521 $(
3522 pub fn [<add_ $tag_name>](&mut self, entity: $crate::Entity) {
3523 if self.contains_entity(entity) && self.$tag_name.insert(entity) {
3524 self.record_structural(entity, $crate::StructuralChangeKind::TagsAdded, $tag_mask);
3525 }
3526 }
3527
3528 pub fn [<remove_ $tag_name>](&mut self, entity: $crate::Entity) -> bool {
3529 let removed = self.$tag_name.remove(entity);
3530 if removed {
3531 self.record_structural(entity, $crate::StructuralChangeKind::TagsRemoved, $tag_mask);
3532 }
3533 removed
3534 }
3535
3536 pub fn [<has_ $tag_name>](&self, entity: $crate::Entity) -> bool {
3537 self.$tag_name.contains(entity)
3538 }
3539
3540 pub fn [<query_ $tag_name>](&self) -> impl Iterator<Item = $crate::Entity> + '_ {
3541 self.$tag_name.iter()
3542 }
3543 )*
3544
3545 $(
3546 pub fn [<send_ $event_name>](&mut self, event: $event_type) {
3547 self.$event_name.send(event);
3548 }
3549
3550 pub fn [<read_ $event_name>](&self) -> impl Iterator<Item = &$event_type> {
3551 self.$event_name.read()
3552 }
3553
3554 pub fn [<read_ $event_name _since>](&self, cursor: u64) -> &[$event_type] {
3555 self.$event_name.events_since(cursor)
3556 }
3557
3558 pub fn [<consume_ $event_name>](&self, cursor: &mut u64) -> &[$event_type] {
3565 self.$event_name.consume(cursor)
3566 }
3567
3568 pub fn [<sequence_ $event_name>](&self) -> u64 {
3569 self.$event_name.sequence()
3570 }
3571
3572 pub fn [<trim_ $event_name>](&mut self, up_to_sequence: u64) {
3573 self.$event_name.trim(up_to_sequence);
3574 }
3575
3576 pub fn [<clear_ $event_name>](&mut self) {
3577 self.$event_name.clear();
3578 }
3579
3580 pub fn [<update_ $event_name>](&mut self) {
3586 self.$event_name.update();
3587 }
3588
3589 pub fn [<len_ $event_name>](&self) -> usize {
3590 self.$event_name.len()
3591 }
3592
3593 pub fn [<is_empty_ $event_name>](&self) -> bool {
3594 self.$event_name.is_empty()
3595 }
3596
3597 pub fn [<peek_ $event_name>](&self) -> Option<&$event_type> {
3598 self.$event_name.peek()
3599 }
3600
3601 pub fn [<collect_ $event_name>](&self) -> Vec<$event_type>
3602 where
3603 $event_type: Clone,
3604 {
3605 self.$event_name.read().cloned().collect()
3606 }
3607 )*
3608
3609 fn update_events(&mut self) {
3610 $(
3611 self.$event_name.update();
3612 )*
3613 }
3614
3615 pub fn step(&mut self) {
3616 self.update_events();
3617 self.last_tick = self.current_tick;
3618 self.current_tick = self.current_tick.wrapping_add(1);
3619 }
3620
3621 pub fn queue_spawn_entities(&mut self, mask: u64, count: usize) {
3622 debug_assert_eq!(
3623 mask & ALL_TAGS_MASK,
3624 0,
3625 "spawn masks must not contain tag bits; use queue_add_<tag> for tags"
3626 );
3627 self.command_buffer.push(Command::SpawnEntities { mask, count });
3628 }
3629
3630 pub fn queue_despawn_entities(&mut self, entities: Vec<$crate::Entity>) {
3631 self.command_buffer.push(Command::DespawnEntities { entities });
3632 }
3633
3634 pub fn queue_despawn_entity(&mut self, entity: $crate::Entity) {
3635 self.command_buffer.push(Command::DespawnEntity { entity });
3636 }
3637
3638 pub fn queue_add_components(&mut self, entity: $crate::Entity, mask: u64) {
3639 debug_assert_eq!(
3640 mask & ALL_TAGS_MASK,
3641 0,
3642 "component masks must not contain tag bits; use queue_add_<tag> for tags"
3643 );
3644 self.command_buffer.push(Command::AddComponents { entity, mask });
3645 }
3646
3647 pub fn queue_remove_components(&mut self, entity: $crate::Entity, mask: u64) {
3648 debug_assert_eq!(
3649 mask & ALL_TAGS_MASK,
3650 0,
3651 "component masks must not contain tag bits; use queue_remove_<tag> for tags"
3652 );
3653 self.command_buffer.push(Command::RemoveComponents { entity, mask });
3654 }
3655
3656 $(
3657 $(#[$comp_attr])*
3658 pub fn [<queue_set_ $name>](&mut self, entity: $crate::Entity, value: $type) {
3659 self.command_buffer.push(Command::[<Set $mask:camel>] { entity, value });
3660 }
3661 )*
3662
3663 $(
3664 pub fn [<queue_add_ $tag_name>](&mut self, entity: $crate::Entity) {
3665 self.command_buffer.push(Command::[<Add $tag_mask:camel>] { entity });
3666 }
3667
3668 pub fn [<queue_remove_ $tag_name>](&mut self, entity: $crate::Entity) {
3669 self.command_buffer.push(Command::[<Remove $tag_mask:camel>] { entity });
3670 }
3671 )*
3672
3673 pub fn apply_commands(&mut self) {
3674 let commands = std::mem::take(&mut self.command_buffer);
3675
3676 for command in commands {
3677 match command {
3678 Command::SpawnEntities { mask, count } => {
3679 self.spawn_entities(mask, count);
3680 }
3681 Command::DespawnEntity { entity } => {
3682 self.despawn_entities(&[entity]);
3683 }
3684 Command::DespawnEntities { entities } => {
3685 self.despawn_entities(&entities);
3686 }
3687 Command::AddComponents { entity, mask } => {
3688 self.add_components(entity, mask);
3689 }
3690 Command::RemoveComponents { entity, mask } => {
3691 self.remove_components(entity, mask);
3692 }
3693 $(
3694 $(#[$comp_attr])*
3695 Command::[<Set $mask:camel>] { entity, value } => {
3696 self.[<set_ $name>](entity, value);
3697 }
3698 )*
3699 $(
3700 Command::[<Add $tag_mask:camel>] { entity } => {
3701 self.[<add_ $tag_name>](entity);
3702 }
3703 Command::[<Remove $tag_mask:camel>] { entity } => {
3704 self.[<remove_ $tag_name>](entity);
3705 }
3706 )*
3707 }
3708 }
3709 }
3710
3711 pub fn command_count(&self) -> usize {
3712 self.command_buffer.len()
3713 }
3714
3715 pub fn clear_commands(&mut self) {
3716 self.command_buffer.clear();
3717 }
3718 }
3719 }
3720
3721 #[derive(Default)]
3722 pub struct $resources {
3723 $($(#[$attr])* pub $resource_name: $resource_type,)*
3724 }
3725 };
3726}
3727
3728#[macro_export]
3729macro_rules! ecs_multi_impl {
3730 (
3731 $ecs:ident {
3732 $($world_name:ident {
3733 $($(#[$comp_attr:meta])* $name:ident: $type:ty => $mask:ident),* $(,)?
3734 })+
3735 }
3736 Tags {
3737 $($tag_name:ident => $tag_mask:ident),* $(,)?
3738 }
3739 Events {
3740 $($event_name:ident: $event_type:ty),* $(,)?
3741 }
3742 $resources:ident {
3743 $($(#[$attr:meta])* $resource_name:ident: $resource_type:ty),* $(,)?
3744 }
3745 ) => {
3746 $crate::paste::paste! {
3747 #[allow(non_camel_case_types)]
3748 #[allow(unused)]
3749 pub enum [<$ecs Tag>] {
3750 $($tag_name,)*
3751 }
3752
3753 $(pub const $tag_mask: u64 = 1 << (63 - ([<$ecs Tag>]::$tag_name as u64));)*
3754
3755 #[allow(unused)]
3756 pub const ALL_TAGS_MASK: u64 = 0 $(| $tag_mask)*;
3757
3758 #[allow(unused)]
3759 pub const [<$ecs:snake:upper _TAG_COUNT>]: usize = {
3760 let mut count = 0;
3761 $(count += 1; let _ = [<$ecs Tag>]::$tag_name;)*
3762 count
3763 };
3764 }
3765
3766 $(
3767 $crate::ecs_kernel_impl! {
3768 $world_name,
3769 true,
3770 { $($(#[$comp_attr])* $name: $type => $mask),* }
3771 }
3772
3773 $crate::paste::paste! {
3774 const _: () = assert!(
3775 [<$world_name:snake:upper _COMPONENT_COUNT>] + [<$ecs:snake:upper _TAG_COUNT>] <= 64,
3776 "components plus tags must fit in a u64 mask"
3777 );
3778
3779 #[allow(unused)]
3780 #[derive(Default)]
3781 pub struct $world_name {
3782 pub entity_locations: $crate::EntityLocations,
3783 pub tables: Vec<[<$world_name ComponentArrays>]>,
3784 pub table_edges: Vec<$crate::ArchetypeEdges>,
3785 pub table_lookup: std::collections::HashMap<u64, usize>,
3786 pub query_cache: std::collections::HashMap<u64, Vec<usize>>,
3787 pub current_tick: u32,
3788 pub last_tick: u32,
3789 pub structural_log: Vec<$crate::StructuralChange>,
3790 pub structural_sequence: u64,
3791 }
3792
3793 #[allow(unused)]
3794 impl $world_name {
3795 pub fn spawn_entities(
3796 &mut self,
3797 allocator: &mut $crate::EntityAllocator,
3798 mask: u64,
3799 count: usize,
3800 ) -> Vec<$crate::Entity> {
3801 self.spawn_entities_with(allocator, mask, count)
3802 }
3803
3804 pub fn spawn_batch<F>(
3805 &mut self,
3806 allocator: &mut $crate::EntityAllocator,
3807 mask: u64,
3808 count: usize,
3809 init: F,
3810 ) -> Vec<$crate::Entity>
3811 where
3812 F: FnMut(&mut [<$world_name ComponentArrays>], usize),
3813 {
3814 self.spawn_batch_with(allocator, mask, count, init)
3815 }
3816
3817 #[inline]
3818 pub fn for_each<F>(&self, include: u64, exclude: u64, f: F)
3819 where
3820 F: FnMut($crate::Entity, &[<$world_name ComponentArrays>], usize),
3821 {
3822 debug_assert_eq!(
3823 include & ![<$world_name:snake:upper _ALL_COMPONENTS>],
3824 0,
3825 "per-world queries take component masks only; use for_each_with_tags for tag filtering"
3826 );
3827 [<tables_for_each_ $world_name:snake>](
3828 &self.tables,
3829 &self.query_cache,
3830 include,
3831 exclude,
3832 |_| true,
3833 f,
3834 );
3835 }
3836
3837 #[inline]
3838 pub fn for_each_mut<F>(&mut self, include: u64, exclude: u64, f: F)
3839 where
3840 F: FnMut($crate::Entity, &mut [<$world_name ComponentArrays>], usize),
3841 {
3842 debug_assert_eq!(
3843 include & ![<$world_name:snake:upper _ALL_COMPONENTS>],
3844 0,
3845 "per-world queries take component masks only; use for_each_mut_with_tags for tag filtering"
3846 );
3847 [<tables_for_each_mut_ $world_name:snake>](
3848 &mut self.tables,
3849 &mut self.query_cache,
3850 include,
3851 exclude,
3852 |_| true,
3853 f,
3854 );
3855 }
3856
3857 #[cfg(not(target_family = "wasm"))]
3858 #[inline]
3859 pub fn par_for_each_mut<F>(&mut self, include: u64, exclude: u64, f: F)
3860 where
3861 F: Fn($crate::Entity, &mut [<$world_name ComponentArrays>], usize) + Send + Sync,
3862 {
3863 debug_assert_eq!(
3864 include & ![<$world_name:snake:upper _ALL_COMPONENTS>],
3865 0,
3866 "per-world queries take component masks only; use par_for_each_mut_with_tags for tag filtering"
3867 );
3868 [<tables_par_for_each_mut_ $world_name:snake>](
3869 &mut self.tables,
3870 include,
3871 exclude,
3872 |_| true,
3873 f,
3874 );
3875 }
3876
3877 #[inline]
3878 pub fn for_each_mut_changed<F>(&mut self, include: u64, exclude: u64, f: F)
3879 where
3880 F: FnMut($crate::Entity, &mut [<$world_name ComponentArrays>], usize),
3881 {
3882 let since_tick = self.last_tick;
3883 self.for_each_mut_changed_since(include, exclude, since_tick, f);
3884 }
3885
3886 pub fn for_each_mut_changed_since<F>(&mut self, include: u64, exclude: u64, since_tick: u32, f: F)
3887 where
3888 F: FnMut($crate::Entity, &mut [<$world_name ComponentArrays>], usize),
3889 {
3890 debug_assert_eq!(
3891 include & ![<$world_name:snake:upper _ALL_COMPONENTS>],
3892 0,
3893 "per-world changed queries take component masks only"
3894 );
3895 [<tables_for_each_mut_changed_ $world_name:snake>](
3896 &mut self.tables,
3897 &mut self.query_cache,
3898 include,
3899 exclude,
3900 since_tick,
3901 |_| true,
3902 f,
3903 );
3904 }
3905
3906 $(
3907 $(#[$comp_attr])*
3908 pub fn [<query_ $name _mut>]<F>(&mut self, mask: u64, mut f: F)
3909 where
3910 F: FnMut($crate::Entity, &mut $type),
3911 {
3912 debug_assert_eq!(
3913 mask & ![<$world_name:snake:upper _ALL_COMPONENTS>],
3914 0,
3915 "per-world queries take component masks only"
3916 );
3917 let current_tick = self.current_tick;
3918 [<tables_for_each_mut_ $world_name:snake>](
3919 &mut self.tables,
3920 &mut self.query_cache,
3921 mask | $mask,
3922 0,
3923 |_| true,
3924 |entity, table, index| {
3925 table.[<$name _changed>][index] = current_tick;
3926 table.[<$name _peak_changed>] = current_tick;
3927 f(entity, &mut table.$name[index]);
3928 },
3929 );
3930 }
3931
3932 $(#[$comp_attr])*
3933 pub fn [<iter_ $name _mut>]<F>(&mut self, mut f: F)
3934 where
3935 F: FnMut($crate::Entity, &mut $type),
3936 {
3937 self.[<query_ $name _mut>](0, |entity, component| f(entity, component));
3938 }
3939 )*
3940 }
3941 }
3942 )+
3943
3944 #[derive(Default)]
3945 pub struct $resources {
3946 $($(#[$attr])* pub $resource_name: $resource_type,)*
3947 }
3948
3949 $crate::paste::paste! {
3950 #[allow(unused)]
3951 #[derive(Default, Clone)]
3952 pub struct EntityBuilder {
3953 $($(
3954 $(#[$comp_attr])* $name: Option<$type>,
3955 )*)+
3956 }
3957
3958 #[allow(unused)]
3959 impl EntityBuilder {
3960 pub fn new() -> Self {
3961 Self::default()
3962 }
3963
3964 $($(
3965 $(#[$comp_attr])*
3966 pub fn [<with_ $name>](mut self, value: $type) -> Self {
3967 self.$name = Some(value);
3968 self
3969 }
3970 )*)+
3971
3972 pub fn spawn(self, ecs: &mut $ecs, instances: usize) -> Vec<$crate::Entity> {
3973 let mut entities = Vec::with_capacity(instances);
3974 for _ in 0..instances {
3975 entities.push(ecs.spawn());
3976 }
3977
3978 $(
3979 {
3980 let mut mask: u64 = 0;
3981 $(
3982 $(#[$comp_attr])*
3983 if self.$name.is_some() {
3984 mask |= $mask;
3985 }
3986 )*
3987 if mask != 0 {
3988 let last_entity_index = entities.len().saturating_sub(1);
3989 for (entity_index, entity) in entities.iter().enumerate() {
3990 ecs.[<$world_name:snake>].add_components(*entity, mask);
3991 if entity_index == last_entity_index {
3992 $(
3993 $(#[$comp_attr])*
3994 if let Some(component) = self.$name {
3995 ecs.[<$world_name:snake>].[<set_ $name>](*entity, component);
3996 }
3997 )*
3998 break;
3999 } else {
4000 $(
4001 $(#[$comp_attr])*
4002 if let Some(ref component) = self.$name {
4003 ecs.[<$world_name:snake>].[<set_ $name>](*entity, component.clone());
4004 }
4005 )*
4006 }
4007 }
4008 }
4009 }
4010 )+
4011
4012 entities
4013 }
4014 }
4015
4016 pub enum Command {
4017 Spawn { count: usize },
4018 DespawnEntity { entity: $crate::Entity },
4019 Despawn { entities: Vec<$crate::Entity> },
4020 $($(
4021 $(#[$comp_attr])*
4022 [<$world_name Set $mask:camel>] { entity: $crate::Entity, value: $type },
4023 $(#[$comp_attr])*
4024 [<$world_name AddComponents $mask:camel>] { entity: $crate::Entity },
4025 $(#[$comp_attr])*
4026 [<$world_name RemoveComponents $mask:camel>] { entity: $crate::Entity },
4027 )*)+
4028 $(
4029 [<Add $tag_mask:camel>] { entity: $crate::Entity },
4030 [<Remove $tag_mask:camel>] { entity: $crate::Entity },
4031 )*
4032 }
4033
4034 #[allow(unused)]
4035 #[derive(Default)]
4036 pub struct $ecs {
4037 $(pub [<$world_name:snake>]: $world_name,)+
4038 pub allocator: $crate::EntityAllocator,
4039 pub resources: $resources,
4040 $(pub $tag_name: $crate::SparseTagSet,)*
4041 pub command_buffer: Vec<Command>,
4042 $(pub $event_name: $crate::EventChannel<$event_type>,)*
4043 pub structural_log: Vec<$crate::StructuralChange>,
4044 pub structural_sequence: u64,
4045 }
4046
4047 #[allow(unused)]
4048 impl $ecs {
4049 pub fn spawn(&mut self) -> $crate::Entity {
4050 let entity = self.allocator.allocate();
4051 self.record_structural(entity, $crate::StructuralChangeKind::Spawned, 0);
4052 entity
4053 }
4054
4055 pub fn spawn_count(&mut self, count: usize) -> Vec<$crate::Entity> {
4056 let mut entities = Vec::new();
4057 self.allocator.allocate_batch(count, &mut entities);
4058 for index in 0..entities.len() {
4059 let entity = entities[index];
4060 self.record_structural(entity, $crate::StructuralChangeKind::Spawned, 0);
4061 }
4062 entities
4063 }
4064
4065 pub fn is_alive(&self, entity: $crate::Entity) -> bool {
4066 self.allocator.is_alive(entity)
4067 }
4068
4069 pub fn despawn(&mut self, entity: $crate::Entity) -> bool {
4077 if !self.allocator.deallocate(entity) {
4078 return false;
4079 }
4080 $(self.[<$world_name:snake>].retire_entity(entity);)+
4081 $(self.$tag_name.remove(entity);)*
4082 self.record_structural(entity, $crate::StructuralChangeKind::Despawned, 0);
4083 true
4084 }
4085
4086 pub fn despawn_entities(&mut self, entities: &[$crate::Entity]) {
4087 for &entity in entities {
4088 self.despawn(entity);
4089 }
4090 }
4091
4092 fn record_structural(&mut self, entity: $crate::Entity, kind: $crate::StructuralChangeKind, mask: u64) {
4093 if self.structural_log.len() >= $crate::STRUCTURAL_LOG_CAPACITY {
4094 self.structural_log.clear();
4095 }
4096 self.structural_sequence += 1;
4097 self.structural_log.push($crate::StructuralChange {
4098 sequence: self.structural_sequence,
4099 entity,
4100 kind,
4101 mask,
4102 });
4103 }
4104
4105 pub fn structural_sequence(&self) -> u64 {
4106 self.structural_sequence
4107 }
4108
4109 pub fn structural_changes_since(&self, cursor: u64) -> &[$crate::StructuralChange] {
4117 let start = self.structural_log.partition_point(|change| change.sequence <= cursor);
4118 &self.structural_log[start..]
4119 }
4120
4121 pub fn trim_structural_log(&mut self, up_to_sequence: u64) {
4122 let end = self.structural_log.partition_point(|change| change.sequence <= up_to_sequence);
4123 self.structural_log.drain(..end);
4124 }
4125
4126 pub fn clear_structural_log(&mut self) {
4127 self.structural_log.clear();
4128 }
4129
4130 $(
4131 pub fn [<add_ $tag_name>](&mut self, entity: $crate::Entity) {
4132 if self.allocator.is_alive(entity) && self.$tag_name.insert(entity) {
4133 self.record_structural(entity, $crate::StructuralChangeKind::TagsAdded, $tag_mask);
4134 }
4135 }
4136
4137 pub fn [<remove_ $tag_name>](&mut self, entity: $crate::Entity) -> bool {
4138 let removed = self.$tag_name.remove(entity);
4139 if removed {
4140 self.record_structural(entity, $crate::StructuralChangeKind::TagsRemoved, $tag_mask);
4141 }
4142 removed
4143 }
4144
4145 pub fn [<has_ $tag_name>](&self, entity: $crate::Entity) -> bool {
4146 self.$tag_name.contains(entity)
4147 }
4148
4149 pub fn [<query_ $tag_name>](&self) -> impl Iterator<Item = $crate::Entity> + '_ {
4150 self.$tag_name.iter()
4151 }
4152 )*
4153
4154 $(
4155 pub fn [<send_ $event_name>](&mut self, event: $event_type) {
4156 self.$event_name.send(event);
4157 }
4158
4159 pub fn [<read_ $event_name>](&self) -> impl Iterator<Item = &$event_type> {
4160 self.$event_name.read()
4161 }
4162
4163 pub fn [<read_ $event_name _since>](&self, cursor: u64) -> &[$event_type] {
4164 self.$event_name.events_since(cursor)
4165 }
4166
4167 pub fn [<consume_ $event_name>](&self, cursor: &mut u64) -> &[$event_type] {
4174 self.$event_name.consume(cursor)
4175 }
4176
4177 pub fn [<sequence_ $event_name>](&self) -> u64 {
4178 self.$event_name.sequence()
4179 }
4180
4181 pub fn [<trim_ $event_name>](&mut self, up_to_sequence: u64) {
4182 self.$event_name.trim(up_to_sequence);
4183 }
4184
4185 pub fn [<clear_ $event_name>](&mut self) {
4186 self.$event_name.clear();
4187 }
4188
4189 pub fn [<update_ $event_name>](&mut self) {
4195 self.$event_name.update();
4196 }
4197
4198 pub fn [<len_ $event_name>](&self) -> usize {
4199 self.$event_name.len()
4200 }
4201
4202 pub fn [<is_empty_ $event_name>](&self) -> bool {
4203 self.$event_name.is_empty()
4204 }
4205
4206 pub fn [<peek_ $event_name>](&self) -> Option<&$event_type> {
4207 self.$event_name.peek()
4208 }
4209
4210 pub fn [<collect_ $event_name>](&self) -> Vec<$event_type>
4211 where
4212 $event_type: Clone,
4213 {
4214 self.$event_name.read().cloned().collect()
4215 }
4216 )*
4217
4218 fn update_events(&mut self) {
4219 $(
4220 self.$event_name.update();
4221 )*
4222 }
4223
4224 pub fn step(&mut self) {
4225 self.update_events();
4226 $(
4227 self.[<$world_name:snake>].increment_tick();
4228 )+
4229 }
4230
4231 pub fn queue_spawn(&mut self, count: usize) {
4232 self.command_buffer.push(Command::Spawn { count });
4233 }
4234
4235 pub fn queue_despawn_entity(&mut self, entity: $crate::Entity) {
4236 self.command_buffer.push(Command::DespawnEntity { entity });
4237 }
4238
4239 pub fn queue_despawn_entities(&mut self, entities: Vec<$crate::Entity>) {
4240 self.command_buffer.push(Command::Despawn { entities });
4241 }
4242
4243 $($(
4244 $(#[$comp_attr])*
4245 pub fn [<queue_set_ $name>](&mut self, entity: $crate::Entity, value: $type) {
4246 self.command_buffer.push(Command::[<$world_name Set $mask:camel>] { entity, value });
4247 }
4248
4249 $(#[$comp_attr])*
4250 pub fn [<queue_add_ $name>](&mut self, entity: $crate::Entity) {
4251 self.command_buffer.push(Command::[<$world_name AddComponents $mask:camel>] { entity });
4252 }
4253
4254 $(#[$comp_attr])*
4255 pub fn [<queue_remove_ $name>](&mut self, entity: $crate::Entity) {
4256 self.command_buffer.push(Command::[<$world_name RemoveComponents $mask:camel>] { entity });
4257 }
4258 )*)+
4259
4260 $(
4261 pub fn [<queue_add_ $tag_name>](&mut self, entity: $crate::Entity) {
4262 self.command_buffer.push(Command::[<Add $tag_mask:camel>] { entity });
4263 }
4264
4265 pub fn [<queue_remove_ $tag_name>](&mut self, entity: $crate::Entity) {
4266 self.command_buffer.push(Command::[<Remove $tag_mask:camel>] { entity });
4267 }
4268 )*
4269
4270 pub fn apply_commands(&mut self) {
4271 let commands = std::mem::take(&mut self.command_buffer);
4272 for command in commands {
4273 match command {
4274 Command::Spawn { count } => {
4275 self.spawn_count(count);
4276 }
4277 Command::DespawnEntity { entity } => {
4278 self.despawn(entity);
4279 }
4280 Command::Despawn { entities } => {
4281 self.despawn_entities(&entities);
4282 }
4283 $($(
4284 $(#[$comp_attr])*
4285 Command::[<$world_name Set $mask:camel>] { entity, value } => {
4286 self.[<$world_name:snake>].[<set_ $name>](entity, value);
4287 }
4288 $(#[$comp_attr])*
4289 Command::[<$world_name AddComponents $mask:camel>] { entity } => {
4290 self.[<$world_name:snake>].add_components(entity, $mask);
4291 }
4292 $(#[$comp_attr])*
4293 Command::[<$world_name RemoveComponents $mask:camel>] { entity } => {
4294 self.[<$world_name:snake>].remove_components(entity, $mask);
4295 }
4296 )*)+
4297 $(
4298 Command::[<Add $tag_mask:camel>] { entity } => {
4299 self.[<add_ $tag_name>](entity);
4300 }
4301 Command::[<Remove $tag_mask:camel>] { entity } => {
4302 self.[<remove_ $tag_name>](entity);
4303 }
4304 )*
4305 }
4306 }
4307 }
4308
4309 pub fn command_count(&self) -> usize {
4310 self.command_buffer.len()
4311 }
4312
4313 pub fn clear_commands(&mut self) {
4314 self.command_buffer.clear();
4315 }
4316 }
4317 }
4318 };
4319}
4320#[macro_export]
4321macro_rules! table_has_components {
4322 ($table:expr, $mask:expr) => {
4323 $table.mask & $mask == $mask
4324 };
4325}
4326
4327#[cfg(test)]
4328mod tests {
4329 use super::*;
4330 use std::collections::HashSet;
4331
4332 ecs! {
4333 World {
4334 position: Position => POSITION,
4335 velocity: Velocity => VELOCITY,
4336 health: Health => HEALTH,
4337 parent: Parent => PARENT,
4338 node: Node => NODE,
4339 }
4340 Tags {
4341 player => PLAYER,
4342 enemy => ENEMY,
4343 active => ACTIVE,
4344 }
4345 Events {
4346 ping: PingEvent,
4347 }
4348 Resources {
4349 _delta_time: f32,
4350 }
4351 }
4352
4353 #[derive(Debug, Clone)]
4354 pub struct PingEvent {
4355 pub value: u32,
4356 }
4357
4358 use components::*;
4359 mod components {
4360 use super::*;
4361
4362 #[derive(Default, Debug, Copy, Clone, PartialEq)]
4363 pub struct Parent(pub Entity);
4364
4365 #[derive(Default, Debug, Clone, PartialEq)]
4366 pub struct Node {
4367 pub id: Entity,
4368 pub parent: Option<Entity>,
4369 pub children: Vec<Entity>,
4370 }
4371
4372 #[derive(Default, Debug, Clone)]
4373 pub struct Position {
4374 pub x: f32,
4375 pub y: f32,
4376 }
4377
4378 #[derive(Default, Debug, Clone)]
4379 pub struct Velocity {
4380 pub x: f32,
4381 pub y: f32,
4382 }
4383
4384 #[derive(Default, Debug, Clone)]
4385 pub struct Health {
4386 pub value: f32,
4387 }
4388 }
4389
4390 mod systems {
4391 use super::*;
4392
4393 pub fn run_systems(world: &mut World, dt: f32) {
4394 world.tables.iter_mut().for_each(|table| {
4395 if super::table_has_components!(table, POSITION | VELOCITY | HEALTH) {
4396 update_positions_system(&mut table.position, &table.velocity, dt);
4397 }
4398 if super::table_has_components!(table, HEALTH) {
4399 health_system(&mut table.health);
4400 }
4401 });
4402 }
4403
4404 #[inline]
4405 pub fn update_positions_system(
4406 positions: &mut [Position],
4407 velocities: &[Velocity],
4408 dt: f32,
4409 ) {
4410 positions
4411 .iter_mut()
4412 .zip(velocities.iter())
4413 .for_each(|(pos, vel)| {
4414 pos.x += vel.x * dt;
4415 pos.y += vel.y * dt;
4416 });
4417 }
4418
4419 #[inline]
4420 pub fn health_system(health: &mut [Health]) {
4421 health.iter_mut().for_each(|health| {
4422 health.value *= 0.98;
4423 });
4424 }
4425 }
4426
4427 fn setup_test_world() -> (World, Entity) {
4428 let mut world = World::default();
4429 let entity = world.spawn_entities(POSITION | VELOCITY, 1)[0];
4430
4431 if let Some(pos) = world.get_position_mut(entity) {
4432 pos.x = 1.0;
4433 pos.y = 2.0;
4434 }
4435 if let Some(vel) = world.get_velocity_mut(entity) {
4436 vel.x = 3.0;
4437 vel.y = 4.0;
4438 }
4439
4440 (world, entity)
4441 }
4442
4443 #[test]
4444 fn test_spawn_entities() {
4445 let mut world = World::default();
4446 let entities = world.spawn_entities(POSITION | VELOCITY, 3);
4447
4448 assert_eq!(entities.len(), 3);
4449 assert_eq!(world.get_all_entities().len(), 3);
4450
4451 for entity in entities {
4452 assert!(world.get_position(entity).is_some());
4453 assert!(world.get_velocity(entity).is_some());
4454 assert!(world.get_health(entity).is_none());
4455 }
4456 }
4457
4458 #[test]
4459 fn test_component_access() {
4460 let (mut world, entity) = setup_test_world();
4461
4462 let pos = world.get_position(entity).unwrap();
4463 assert_eq!(pos.x, 1.0);
4464 assert_eq!(pos.y, 2.0);
4465
4466 if let Some(pos) = world.get_position_mut(entity) {
4467 pos.x = 5.0;
4468 }
4469
4470 let pos = world.get_position(entity).unwrap();
4471 assert_eq!(pos.x, 5.0);
4472 }
4473
4474 #[test]
4475 fn test_modify_component() {
4476 let (mut world, entity) = setup_test_world();
4477
4478 let pos = world.get_position(entity).unwrap();
4479 assert_eq!(pos.x, 1.0);
4480 assert_eq!(pos.y, 2.0);
4481
4482 let old_x = world.modify_position(entity, |pos| {
4483 let old = pos.x;
4484 pos.x = 10.0;
4485 pos.y = 20.0;
4486 old
4487 });
4488 assert_eq!(old_x, Some(1.0));
4489
4490 let pos = world.get_position(entity).unwrap();
4491 assert_eq!(pos.x, 10.0);
4492 assert_eq!(pos.y, 20.0);
4493
4494 let invalid_entity = Entity {
4495 id: 9999,
4496 generation: 0,
4497 };
4498 let result = world.modify_position(invalid_entity, |pos| pos.x = 100.0);
4499 assert!(result.is_none());
4500
4501 let entity_no_health = world.spawn_entities(POSITION, 1)[0];
4502 let result = world.modify_health(entity_no_health, |h| h.value = 50.0);
4503 assert!(result.is_none());
4504 }
4505
4506 #[test]
4507 fn test_add_remove_components() {
4508 let (mut world, entity) = setup_test_world();
4509
4510 assert!(world.get_health(entity).is_none());
4511
4512 world.add_components(entity, HEALTH);
4513 assert!(world.get_health(entity).is_some());
4514
4515 world.remove_components(entity, HEALTH);
4516 assert!(world.get_health(entity).is_none());
4517 }
4518
4519 #[test]
4520 fn test_component_mask() {
4521 let (mut world, entity) = setup_test_world();
4522
4523 let mask = world.component_mask(entity).unwrap();
4524 assert_eq!(mask, POSITION | VELOCITY);
4525
4526 world.add_components(entity, HEALTH);
4527 let mask = world.component_mask(entity).unwrap();
4528 assert_eq!(mask, POSITION | VELOCITY | HEALTH);
4529 }
4530
4531 #[test]
4532 fn test_query_entities() {
4533 let mut world = World::default();
4534
4535 let e1 = world.spawn_entities(POSITION | VELOCITY, 1)[0];
4536 let _e2 = world.spawn_entities(POSITION | HEALTH, 1)[0];
4537 let e3 = world.spawn_entities(POSITION | VELOCITY | HEALTH, 1)[0];
4538
4539 let pos_vel: Vec<_> = world.query_entities(POSITION | VELOCITY).collect();
4540 let pos_health: Vec<_> = world.query_entities(POSITION | HEALTH).collect();
4541 let all: Vec<_> = world.query_entities(POSITION | VELOCITY | HEALTH).collect();
4542
4543 assert_eq!(pos_vel.len(), 2);
4544 assert_eq!(pos_health.len(), 2);
4545 assert_eq!(all.len(), 1);
4546
4547 let pos_vel: HashSet<_> = pos_vel.into_iter().collect();
4548 assert!(pos_vel.contains(&e1));
4549 assert!(pos_vel.contains(&e3));
4550
4551 assert_eq!(all[0], e3);
4552 }
4553
4554 #[test]
4555 fn test_query_first_entity() {
4556 let mut world = World::default();
4557
4558 let e1 = world.spawn_entities(POSITION | VELOCITY, 1)[0];
4559 let e2 = world.spawn_entities(POSITION | VELOCITY | HEALTH, 1)[0];
4560
4561 let first = world.query_first_entity(POSITION | VELOCITY).unwrap();
4562 assert!(first == e1 || first == e2);
4563
4564 assert!(world.query_first_entity(HEALTH).is_some());
4565 assert!(
4566 world
4567 .query_first_entity(POSITION | VELOCITY | HEALTH)
4568 .is_some()
4569 );
4570 }
4571
4572 #[test]
4573 fn test_despawn_entities() {
4574 let mut world = World::default();
4575
4576 let entities = world.spawn_entities(POSITION | VELOCITY, 3);
4577 assert_eq!(world.get_all_entities().len(), 3);
4578
4579 let despawned = world.despawn_entities(&[entities[1]]);
4580 assert_eq!(despawned.len(), 1);
4581 assert_eq!(world.get_all_entities().len(), 2);
4582
4583 assert!(world.get_position(entities[1]).is_none());
4584
4585 assert!(world.get_position(entities[0]).is_some());
4586 assert!(world.get_position(entities[2]).is_some());
4587 }
4588
4589 #[test]
4590 fn test_parallel_systems() {
4591 let mut world = World::default();
4592
4593 let entity = world.spawn_entities(POSITION | VELOCITY | HEALTH, 1)[0];
4594
4595 if let Some(pos) = world.get_position_mut(entity) {
4596 pos.x = 0.0;
4597 pos.y = 0.0;
4598 }
4599 if let Some(vel) = world.get_velocity_mut(entity) {
4600 vel.x = 1.0;
4601 vel.y = 1.0;
4602 }
4603 if let Some(health) = world.get_health_mut(entity) {
4604 health.value = 100.0;
4605 }
4606
4607 systems::run_systems(&mut world, 1.0);
4608
4609 let pos = world.get_position(entity).unwrap();
4610 let health = world.get_health(entity).unwrap();
4611
4612 assert_eq!(pos.x, 1.0);
4613 assert_eq!(pos.y, 1.0);
4614 assert!(health.value < 100.0);
4615 }
4616
4617 #[test]
4618 fn test_add_components() {
4619 let (mut world, entity) = setup_test_world();
4620
4621 assert!(world.get_health(entity).is_none());
4622
4623 world.add_components(entity, HEALTH);
4624 assert!(world.get_health(entity).is_some());
4625
4626 world.remove_components(entity, HEALTH);
4627 assert!(world.get_health(entity).is_none());
4628 }
4629
4630 #[test]
4631 fn test_multiple_component_addition() {
4632 let mut world = World::default();
4633 let entity = world.spawn_entities(POSITION, 1)[0];
4634
4635 world.add_components(entity, VELOCITY | HEALTH);
4636
4637 assert!(world.get_position(entity).is_some());
4638 assert!(world.get_velocity(entity).is_some());
4639 assert!(world.get_health(entity).is_some());
4640
4641 if let Some(pos) = world.get_position_mut(entity) {
4642 pos.x = 1.0;
4643 }
4644 world.add_components(entity, VELOCITY);
4645 assert_eq!(world.get_position(entity).unwrap().x, 1.0);
4646 }
4647
4648 #[test]
4649 fn test_component_chain_addition() {
4650 let mut world = World::default();
4651 let entity = world.spawn_entities(POSITION, 1)[0];
4652
4653 if let Some(pos) = world.get_position_mut(entity) {
4654 pos.x = 1.0;
4655 }
4656
4657 world.add_components(entity, VELOCITY);
4658 world.add_components(entity, HEALTH);
4659
4660 assert_eq!(world.get_position(entity).unwrap().x, 1.0);
4661 }
4662
4663 #[test]
4664 fn test_component_removal_order() {
4665 let mut world = World::default();
4666 let entity = world.spawn_entities(POSITION | VELOCITY | HEALTH, 1)[0];
4667
4668 world.remove_components(entity, VELOCITY);
4669 world.remove_components(entity, HEALTH);
4670 assert!(world.get_position(entity).is_some());
4671 assert!(world.get_velocity(entity).is_none());
4672 assert!(world.get_health(entity).is_none());
4673 }
4674
4675 #[test]
4676 fn test_edge_cases() {
4677 let mut world = World::default();
4678
4679 let empty = world.spawn_entities(0, 1)[0];
4680
4681 world.add_components(empty, POSITION);
4682 assert!(world.get_position(empty).is_some());
4683
4684 world.add_components(empty, POSITION);
4685 world.add_components(empty, POSITION);
4686
4687 world.remove_components(empty, VELOCITY);
4688
4689 world.remove_components(empty, POSITION);
4690 assert_eq!(world.component_mask(empty).unwrap(), 0);
4691
4692 let invalid = Entity {
4693 id: 9999,
4694 generation: 0,
4695 };
4696 assert!(!world.add_components(invalid, POSITION));
4697 }
4698
4699 #[test]
4700 fn test_component_data_integrity() {
4701 let mut world = World::default();
4702 let entity = world.spawn_entities(POSITION | VELOCITY, 1)[0];
4703
4704 {
4705 let pos = world.get_position_mut(entity).unwrap();
4706 pos.x = 1.0;
4707 pos.y = 2.0;
4708 let vel = world.get_velocity_mut(entity).unwrap();
4709 vel.x = 3.0;
4710 vel.y = 4.0;
4711 }
4712
4713 world.add_components(entity, HEALTH);
4714 world.remove_components(entity, HEALTH);
4715 world.add_components(entity, HEALTH);
4716
4717 let pos = world.get_position(entity).unwrap();
4718 let vel = world.get_velocity(entity).unwrap();
4719 assert_eq!(pos.x, 1.0);
4720 assert_eq!(pos.y, 2.0);
4721 assert_eq!(vel.x, 3.0);
4722 assert_eq!(vel.y, 4.0);
4723 }
4724
4725 #[test]
4726 fn test_entity_references_through_moves() {
4727 let mut world = World::default();
4728
4729 let entity1 = world.spawn_entities(POSITION, 1)[0];
4730 let entity2 = world.spawn_entities(POSITION, 1)[0];
4731
4732 world.add_components(entity1, VELOCITY);
4733 if let Some(vel) = world.get_velocity_mut(entity1) {
4734 vel.x = entity2.id as f32;
4735 }
4736
4737 world.add_components(entity2, VELOCITY | HEALTH);
4738
4739 let stored_id = world.get_velocity(entity1).unwrap().x as u32;
4740 let entity2_loc = get_location_world(&world.entity_locations, entity2);
4741 assert!(entity2_loc.is_some());
4742 assert_eq!(stored_id, entity2.id);
4743 }
4744
4745 #[test]
4746 fn test_table_cleanup_after_despawn() {
4747 let mut world = World::default();
4748
4749 let e1 = world.spawn_entities(POSITION, 1)[0];
4750 let e2 = world.spawn_entities(POSITION | VELOCITY, 1)[0];
4751
4752 let initial_tables = world.tables.len();
4753 assert_eq!(initial_tables, 2, "Should have two tables initially");
4754
4755 world.despawn_entities(&[e2]);
4756
4757 assert!(world.get_position(e2).is_none());
4758 assert!(world.get_velocity(e2).is_none());
4759
4760 assert!(world.get_position(e1).is_some());
4761
4762 let remaining: Vec<_> = world.query_entities(POSITION).collect();
4763 assert_eq!(remaining.len(), 1);
4764 assert!(remaining.contains(&e1));
4765
4766 assert!(
4767 world.tables.len() <= initial_tables,
4768 "Should not have more tables than initial state"
4769 );
4770
4771 for table in &world.tables {
4772 for &entity in &table.entity_indices {
4773 assert!(
4774 get_location_world(&world.entity_locations, entity).is_some(),
4775 "Entity location should be valid for remaining entities"
4776 );
4777 }
4778 }
4779 }
4780
4781 #[test]
4782 fn test_concurrent_entity_references() {
4783 let mut world = World::default();
4784
4785 let entity1 = world.spawn_entities(POSITION | HEALTH, 1)[0];
4786 let entity2 = world.spawn_entities(POSITION | HEALTH, 1)[0];
4787
4788 if let Some(pos) = world.get_position_mut(entity1) {
4789 pos.x = 1.0;
4790 }
4791 if let Some(health) = world.get_health_mut(entity1) {
4792 health.value = 100.0;
4793 }
4794
4795 let id1 = entity1.id;
4796
4797 world.despawn_entities(&[entity1]);
4798
4799 let entity3 = world.spawn_entities(POSITION | HEALTH, 1)[0];
4800 assert_eq!(entity3.id, id1, "Should reuse entity1's ID");
4801 assert_eq!(
4802 entity3.generation,
4803 entity1.generation + 1,
4804 "Should have incremented generation"
4805 );
4806
4807 if let Some(pos) = world.get_position_mut(entity3) {
4808 pos.x = 3.0;
4809 }
4810 if let Some(health) = world.get_health_mut(entity3) {
4811 health.value = 50.0;
4812 }
4813
4814 if let Some(pos) = world.get_position(entity2) {
4815 assert_eq!(pos.x, 0.0, "Entity2's data should be unchanged");
4816 }
4817
4818 if let Some(pos) = world.get_position(entity3) {
4819 assert_eq!(pos.x, 3.0, "Should get entity3's data, not entity1's");
4820 }
4821 assert!(
4822 world.get_position(entity1).is_none(),
4823 "Should not be able to access entity1's old data"
4824 );
4825 }
4826
4827 #[test]
4828 fn test_generational_indices_aba() {
4829 let mut world = World::default();
4830
4831 let entity_a1 = world.spawn_entities(POSITION, 1)[0];
4832 assert_eq!(
4833 entity_a1.generation, 0,
4834 "First use of ID should have generation 0"
4835 );
4836
4837 if let Some(pos) = world.get_position_mut(entity_a1) {
4838 pos.x = 1.0;
4839 pos.y = 1.0;
4840 }
4841
4842 let id = entity_a1.id;
4843
4844 world.despawn_entities(&[entity_a1]);
4845
4846 let entity_a2 = world.spawn_entities(POSITION, 1)[0];
4847 assert_eq!(entity_a2.id, id, "Should reuse the same ID");
4848 assert_eq!(
4849 entity_a2.generation, 1,
4850 "Second use of ID should have generation 1"
4851 );
4852
4853 if let Some(pos) = world.get_position_mut(entity_a2) {
4854 pos.x = 2.0;
4855 pos.y = 2.0;
4856 }
4857
4858 assert!(
4859 world.get_position(entity_a1).is_none(),
4860 "Old reference to entity should be invalid"
4861 );
4862
4863 world.despawn_entities(&[entity_a2]);
4864
4865 let entity_a3 = world.spawn_entities(POSITION, 1)[0];
4866 assert_eq!(entity_a3.id, id, "Should reuse the same ID again");
4867 assert_eq!(
4868 entity_a3.generation, 2,
4869 "Third use of ID should have generation 2"
4870 );
4871
4872 if let Some(pos) = world.get_position_mut(entity_a3) {
4873 pos.x = 3.0;
4874 pos.y = 3.0;
4875 }
4876
4877 assert!(
4878 world.get_position(entity_a1).is_none(),
4879 "First generation reference should be invalid"
4880 );
4881 assert!(
4882 world.get_position(entity_a2).is_none(),
4883 "Second generation reference should be invalid"
4884 );
4885
4886 let pos = world.get_position(entity_a3);
4887 assert!(
4888 pos.is_some(),
4889 "Current generation reference should be valid"
4890 );
4891 let pos = pos.unwrap();
4892 assert_eq!(pos.x, 3.0, "Should have the current generation's data");
4893 assert_eq!(pos.y, 3.0, "Should have the current generation's data");
4894 }
4895
4896 #[test]
4897 fn test_all_entities() {
4898 let mut world = World::default();
4899
4900 let e1 = world.spawn_entities(POSITION, 1)[0];
4901 let e2 = world.spawn_entities(POSITION | VELOCITY, 1)[0];
4902 let e3 = world.spawn_entities(POSITION | HEALTH, 1)[0];
4903 let e4 = world.spawn_entities(POSITION | VELOCITY | HEALTH, 1)[0];
4904
4905 let all = world.get_all_entities();
4906
4907 assert_eq!(all.len(), 4, "Should have 4 total entities");
4908
4909 assert!(all.contains(&e1), "Missing entity 1");
4910 assert!(all.contains(&e2), "Missing entity 2");
4911 assert!(all.contains(&e3), "Missing entity 3");
4912 assert!(all.contains(&e4), "Missing entity 4");
4913
4914 world.despawn_entities(&[e2, e3]);
4915 let remaining = world.get_all_entities();
4916
4917 assert_eq!(remaining.len(), 2, "Should have 2 entities after despawn");
4918
4919 assert!(remaining.contains(&e1), "Missing entity 1 after despawn");
4920 assert!(remaining.contains(&e4), "Missing entity 4 after despawn");
4921 assert!(!remaining.contains(&e2), "Entity 2 should be despawned");
4922 assert!(!remaining.contains(&e3), "Entity 3 should be despawned");
4923 }
4924
4925 #[test]
4926 fn test_all_entities_empty_world() {
4927 assert!(
4928 World::default().get_all_entities().is_empty(),
4929 "Empty world should return empty vector"
4930 );
4931 }
4932
4933 #[test]
4934 fn test_all_entities_after_table_merges() {
4935 let mut world = World::default();
4936
4937 let e1 = world.spawn_entities(POSITION, 1)[0];
4938 let e2 = world.spawn_entities(VELOCITY, 1)[0];
4939
4940 world.add_components(e1, VELOCITY);
4941 world.add_components(e2, POSITION);
4942
4943 let all = world.get_all_entities();
4944 assert_eq!(
4945 all.len(),
4946 2,
4947 "Should maintain all entities through table merges"
4948 );
4949 assert!(all.contains(&e1), "Should contain first entity after merge");
4950 assert!(
4951 all.contains(&e2),
4952 "Should contain second entity after merge"
4953 );
4954 }
4955
4956 #[test]
4957 fn test_table_transitions() {
4958 let mut world = World::default();
4959
4960 let entity = world.spawn_entities(POSITION | VELOCITY | HEALTH, 1)[0];
4961
4962 println!("Initial mask: {:b}", world.component_mask(entity).unwrap());
4963
4964 let (old_table_idx, _) = get_location_world(&world.entity_locations, entity).unwrap();
4965
4966 world.add_components(entity, POSITION);
4967
4968 let final_mask = world.component_mask(entity).unwrap();
4969 println!("Final mask: {:b}", final_mask);
4970 let (new_table_idx, _) = get_location_world(&world.entity_locations, entity).unwrap();
4971
4972 println!(
4973 "Old table index: {}, New table index: {}",
4974 old_table_idx, new_table_idx
4975 );
4976 println!("Tables after operation:");
4977 for (index, table) in world.tables.iter().enumerate() {
4978 println!("Table {}: mask={:b}", index, table.mask);
4979 }
4980
4981 assert_eq!(
4982 final_mask & (POSITION | VELOCITY | HEALTH),
4983 POSITION | VELOCITY | HEALTH,
4984 "Entity should still have all original components"
4985 );
4986 }
4987
4988 #[test]
4989 fn test_real_camera_scenario() {
4990 let mut world = World::default();
4991
4992 let entity = world.spawn_entities(POSITION | VELOCITY | HEALTH, 1)[0];
4993
4994 let query_results: Vec<_> = world.query_entities(POSITION | VELOCITY).collect();
4995 assert!(
4996 query_results.contains(&entity),
4997 "Initial query should match\n\
4998 Entity mask: {:b}\n\
4999 Query mask: {:b}",
5000 world.component_mask(entity).unwrap(),
5001 POSITION | VELOCITY
5002 );
5003
5004 world.add_components(entity, HEALTH);
5005
5006 let query_results: Vec<_> = world.query_entities(POSITION | VELOCITY).collect();
5007 assert!(
5008 query_results.contains(&entity),
5009 "Query should still match after adding component\n\
5010 Entity mask: {:b}\n\
5011 Query mask: {:b}",
5012 world.component_mask(entity).unwrap(),
5013 POSITION | VELOCITY
5014 );
5015 }
5016
5017 #[test]
5018 fn test_query_consistency() {
5019 let mut world = World::default();
5020
5021 let entity = world.spawn_entities(POSITION | VELOCITY | HEALTH, 1)[0];
5022
5023 let query_mask = POSITION | VELOCITY;
5024
5025 let query_results: Vec<_> = world.query_entities(query_mask).collect();
5026 assert!(
5027 query_results.contains(&entity),
5028 "query_entities should find entity with mask {:b} when querying for {:b}",
5029 world.component_mask(entity).unwrap(),
5030 query_mask
5031 );
5032
5033 let first_result = world.query_first_entity(query_mask);
5034 assert!(
5035 first_result.is_some(),
5036 "query_first_entity should find entity with mask {:b} when querying for {:b}",
5037 world.component_mask(entity).unwrap(),
5038 query_mask
5039 );
5040 assert_eq!(
5041 first_result.unwrap(),
5042 entity,
5043 "query_first_entity should find same entity as query_entities"
5044 );
5045
5046 world.add_components(entity, HEALTH);
5047
5048 let query_results: Vec<_> = world.query_entities(query_mask).collect();
5049 assert!(
5050 query_results.contains(&entity),
5051 "query_entities should still find entity after adding component\n\
5052 Entity mask: {:b}\n\
5053 Query mask: {:b}",
5054 world.component_mask(entity).unwrap(),
5055 query_mask
5056 );
5057
5058 let first_result = world.query_first_entity(query_mask);
5059 assert!(
5060 first_result.is_some(),
5061 "query_first_entity should still find entity after adding component\n\
5062 Entity mask: {:b}\n\
5063 Query mask: {:b}",
5064 world.component_mask(entity).unwrap(),
5065 query_mask
5066 );
5067 }
5068
5069 #[test]
5070 fn entity_has_components_test() {
5071 let mut world = World::default();
5072 let entity = world.spawn_entities(POSITION | VELOCITY, 1)[0];
5073 assert!(world.entity_has_components(entity, POSITION | VELOCITY));
5074 assert!(!world.entity_has_components(entity, HEALTH));
5075 }
5076
5077 #[test]
5078 fn test_set_component() {
5079 let mut world = World::default();
5080 let entity = world.spawn_entities(POSITION, 1)[0];
5081 world.set_position(entity, Position { x: 1.0, y: 2.0 });
5082 assert_eq!(world.get_position(entity).unwrap().x, 1.0);
5083 assert_eq!(world.get_position(entity).unwrap().y, 2.0);
5084
5085 world.set_position(entity, Position { x: 3.0, y: 4.0 });
5086 assert_eq!(world.get_position(entity).unwrap().x, 3.0);
5087 assert_eq!(world.get_position(entity).unwrap().y, 4.0);
5088 }
5089
5090 #[test]
5091 fn test_entity_builder() {
5092 let mut world = World::default();
5093 let entities = EntityBuilder::new()
5094 .with_position(Position { x: 1.0, y: 2.0 })
5095 .spawn(&mut world, 2);
5096 assert_eq!(world.get_position(entities[0]).unwrap().x, 1.0);
5097 assert_eq!(world.get_position(entities[1]).unwrap().y, 2.0);
5098 }
5099
5100 #[test]
5101 fn test_query_composition_exclude() {
5102 let mut world = World::default();
5103
5104 let e1 = world.spawn_entities(POSITION | VELOCITY, 1)[0];
5105 let e2 = world.spawn_entities(POSITION | VELOCITY | HEALTH, 1)[0];
5106 let e3 = world.spawn_entities(POSITION, 1)[0];
5107
5108 let mut count = 0;
5109 world.for_each_mut(POSITION | VELOCITY, HEALTH, |_entity, _table, _idx| {
5110 count += 1;
5111 });
5112
5113 assert_eq!(count, 1);
5114
5115 let mut found_entities = Vec::new();
5116 world.for_each_mut(POSITION | VELOCITY, HEALTH, |entity, _table, _idx| {
5117 found_entities.push(entity);
5118 });
5119
5120 assert!(found_entities.contains(&e1));
5121 assert!(!found_entities.contains(&e2));
5122 assert!(!found_entities.contains(&e3));
5123 }
5124
5125 #[test]
5126 fn test_query_composition_include_only() {
5127 let mut world = World::default();
5128
5129 let e1 = world.spawn_entities(POSITION, 1)[0];
5130 let e2 = world.spawn_entities(POSITION | VELOCITY, 1)[0];
5131 let e3 = world.spawn_entities(POSITION | VELOCITY | HEALTH, 1)[0];
5132
5133 let mut count = 0;
5134 world.for_each_mut(POSITION | VELOCITY, 0, |_entity, _table, _idx| {
5135 count += 1;
5136 });
5137
5138 assert_eq!(count, 2);
5139
5140 let mut found_entities = Vec::new();
5141 world.for_each_mut(POSITION | VELOCITY, 0, |entity, _table, _idx| {
5142 found_entities.push(entity);
5143 });
5144
5145 assert!(!found_entities.contains(&e1));
5146 assert!(found_entities.contains(&e2));
5147 assert!(found_entities.contains(&e3));
5148 }
5149
5150 #[test]
5151 fn test_change_detection_basic() {
5152 let mut world = World::default();
5153 let entity = world.spawn_entities(POSITION | VELOCITY, 1)[0];
5154
5155 world.step();
5156
5157 world.get_position_mut(entity).unwrap().x = 10.0;
5158
5159 let mut changed_count = 0;
5160 world.for_each_mut_changed(POSITION, 0, |_entity, _table, _idx| {
5161 changed_count += 1;
5162 });
5163
5164 assert_eq!(changed_count, 1);
5165 }
5166
5167 #[test]
5168 fn test_change_detection_unchanged() {
5169 let mut world = World::default();
5170 let e1 = world.spawn_entities(POSITION, 1)[0];
5171 let e2 = world.spawn_entities(POSITION, 1)[0];
5172
5173 world.step();
5174
5175 world.get_position_mut(e1).unwrap().x = 5.0;
5176
5177 let mut changed_entities = Vec::new();
5178 world.for_each_mut_changed(POSITION, 0, |entity, _table, _idx| {
5179 changed_entities.push(entity);
5180 });
5181
5182 assert_eq!(changed_entities.len(), 1);
5183 assert!(changed_entities.contains(&e1));
5184 assert!(!changed_entities.contains(&e2));
5185 }
5186
5187 #[test]
5188 fn test_change_detection_tick_tracking() {
5189 let mut world = World::default();
5190 let entity = world.spawn_entities(POSITION, 1)[0];
5191
5192 assert_eq!(world.current_tick(), 0);
5193 assert_eq!(world.last_tick(), 0);
5194
5195 world.step();
5196 assert_eq!(world.current_tick(), 1);
5197 assert_eq!(world.last_tick(), 0);
5198
5199 world.step();
5200 assert_eq!(world.current_tick(), 2);
5201 assert_eq!(world.last_tick(), 1);
5202
5203 world.step();
5204 world.get_position_mut(entity).unwrap().x = 10.0;
5205
5206 let mut count = 0;
5207 world.for_each_mut_changed(POSITION, 0, |_entity, _table, _idx| {
5208 count += 1;
5209 });
5210 assert_eq!(count, 1);
5211
5212 world.step();
5213 count = 0;
5214 world.for_each_mut_changed(POSITION, 0, |_entity, _table, _idx| {
5215 count += 1;
5216 });
5217 assert_eq!(count, 0);
5218 }
5219
5220 #[test]
5221 fn test_change_detection_multiple_components() {
5222 let mut world = World::default();
5223 let e1 = world.spawn_entities(POSITION | VELOCITY, 1)[0];
5224 let e2 = world.spawn_entities(POSITION | VELOCITY, 1)[0];
5225
5226 world.step();
5227
5228 world.get_position_mut(e1).unwrap().x = 5.0;
5229 world.get_velocity_mut(e2).unwrap().x = 10.0;
5230
5231 let mut changed_entities = Vec::new();
5232 world.for_each_mut_changed(POSITION | VELOCITY, 0, |entity, _table, _idx| {
5233 changed_entities.push(entity);
5234 });
5235
5236 assert_eq!(changed_entities.len(), 2);
5237 assert!(changed_entities.contains(&e1));
5238 assert!(changed_entities.contains(&e2));
5239 }
5240
5241 #[test]
5242 fn test_change_detection_with_exclude() {
5243 let mut world = World::default();
5244 let e1 = world.spawn_entities(POSITION, 1)[0];
5245 let e2 = world.spawn_entities(POSITION | HEALTH, 1)[0];
5246
5247 world.step();
5248
5249 world.get_position_mut(e1).unwrap().x = 5.0;
5250 world.get_position_mut(e2).unwrap().x = 10.0;
5251
5252 let mut changed_entities = Vec::new();
5253 world.for_each_mut_changed(POSITION, HEALTH, |entity, _table, _idx| {
5254 changed_entities.push(entity);
5255 });
5256
5257 assert_eq!(changed_entities.len(), 1);
5258 assert!(changed_entities.contains(&e1));
5259 assert!(!changed_entities.contains(&e2));
5260 }
5261
5262 #[test]
5263 fn test_change_detection_set() {
5264 let mut world = World::default();
5265 let e1 = world.spawn_entities(POSITION, 1)[0];
5266 let e2 = world.spawn_entities(POSITION, 1)[0];
5267
5268 world.step();
5269
5270 world.set_position(e1, Position { x: 5.0, y: 0.0 });
5271
5272 let mut changed_entities = Vec::new();
5273 world.for_each_mut_changed(POSITION, 0, |entity, _table, _idx| {
5274 changed_entities.push(entity);
5275 });
5276
5277 assert_eq!(changed_entities.len(), 1);
5278 assert!(changed_entities.contains(&e1));
5279 assert!(!changed_entities.contains(&e2));
5280
5281 let queried: Vec<_> = world.query_entities_changed(POSITION).collect();
5282 assert_eq!(queried, vec![e1]);
5283 }
5284
5285 #[test]
5286 fn test_change_detection_spawn() {
5287 let mut world = World::default();
5288 let e1 = world.spawn_entities(POSITION, 1)[0];
5289
5290 world.step();
5291
5292 let e2 = world.spawn_entities(POSITION, 1)[0];
5293
5294 let changed: Vec<_> = world.query_entities_changed(POSITION).collect();
5295 assert_eq!(changed, vec![e2]);
5296
5297 world.step();
5298
5299 let changed: Vec<_> = world.query_entities_changed(POSITION).collect();
5300 assert!(changed.is_empty());
5301 assert!(world.get_position(e1).is_some());
5302 }
5303
5304 #[test]
5305 fn test_change_detection_skips_untouched_tables() {
5306 let mut world = World::default();
5307 world.spawn_entities(POSITION, 3);
5308 let e2 = world.spawn_entities(POSITION | VELOCITY, 1)[0];
5309
5310 world.step();
5311
5312 world.get_position_mut(e2).unwrap().x = 1.0;
5313
5314 let changed: Vec<_> = world.query_entities_changed(POSITION).collect();
5315 assert_eq!(changed, vec![e2]);
5316
5317 for table in &world.tables {
5318 if table.mask == POSITION {
5319 assert!(!crate::tick_is_newer(
5320 table.position_peak_changed,
5321 world.last_tick
5322 ));
5323 }
5324 if table.mask == POSITION | VELOCITY {
5325 assert!(crate::tick_is_newer(
5326 table.position_peak_changed,
5327 world.last_tick
5328 ));
5329 }
5330 }
5331 }
5332
5333 #[test]
5334 fn test_mark_changed_stamps_raw_writes() {
5335 let mut world = World::default();
5336 let entities = world.spawn_entities(POSITION, 3);
5337
5338 world.step();
5339
5340 world.for_each_mut(POSITION, 0, |entity, table, index| {
5341 if entity == entities[1] {
5342 table.position[index].x = 5.0;
5343 }
5344 });
5345 assert_eq!(world.query_entities_changed(POSITION).count(), 0);
5346
5347 assert!(world.mark_changed(entities[1], POSITION));
5348 let changed: Vec<_> = world.query_entities_changed(POSITION).collect();
5349 assert_eq!(changed, vec![entities[1]]);
5350 }
5351
5352 #[test]
5353 fn test_mark_changed_rejects_missing_rows() {
5354 let mut world = World::default();
5355 let entity = world.spawn_entities(POSITION, 1)[0];
5356 let dead = world.spawn_entities(POSITION, 1)[0];
5357 world.despawn_entities(&[dead]);
5358
5359 assert!(!world.mark_changed(entity, VELOCITY));
5360 assert!(!world.mark_changed(dead, POSITION));
5361 assert!(world.mark_changed(entity, POSITION | VELOCITY));
5362 }
5363
5364 #[test]
5365 fn test_mark_columns_changed_bulk_stamps_one_table() {
5366 let mut world = World::default();
5367 world.spawn_entities(POSITION, 2);
5368 let moving = world.spawn_entities(POSITION | VELOCITY, 2);
5369
5370 world.step();
5371
5372 let current_tick = world.current_tick();
5373 for table in &mut world.tables {
5374 if table.mask & (POSITION | VELOCITY) != POSITION | VELOCITY {
5375 continue;
5376 }
5377 for value in &mut table.position {
5378 value.x += 1.0;
5379 }
5380 table.mark_columns_changed(POSITION, current_tick);
5381 }
5382
5383 let changed: Vec<_> = world.query_entities_changed(POSITION).collect();
5384 assert_eq!(changed, moving);
5385 assert_eq!(world.query_entities_changed(VELOCITY).count(), 0);
5386 }
5387
5388 #[test]
5389 fn test_change_detection_since_cursor() {
5390 let mut world = World::default();
5391 let e1 = world.spawn_entities(POSITION, 1)[0];
5392 world.step();
5393
5394 let cursor = world.last_tick();
5395 world.get_position_mut(e1).unwrap().x = 1.0;
5396 world.step();
5397 world.step();
5398
5399 let changed: Vec<_> = world
5400 .query_entities_changed_since(POSITION, cursor)
5401 .collect();
5402 assert_eq!(changed, vec![e1]);
5403
5404 let cursor = world.current_tick();
5405 let changed: Vec<_> = world
5406 .query_entities_changed_since(POSITION, cursor)
5407 .collect();
5408 assert!(changed.is_empty());
5409
5410 let mut visited = Vec::new();
5411 world.for_each_mut_changed_since(POSITION, 0, 0, |entity, _table, _idx| {
5412 visited.push(entity)
5413 });
5414 assert_eq!(visited, vec![e1]);
5415 }
5416
5417 #[test]
5418 fn test_structural_log_records_lifecycle() {
5419 let mut world = World::default();
5420 let entity = world.spawn_entities(POSITION, 1)[0];
5421 world.add_components(entity, VELOCITY);
5422 world.remove_components(entity, POSITION);
5423 world.despawn_entities(&[entity]);
5424
5425 let changes: Vec<_> = world.structural_changes_since(0).to_vec();
5426 assert_eq!(changes.len(), 4);
5427 assert!(changes.iter().all(|change| change.entity == entity));
5428 assert_eq!(changes[0].kind, StructuralChangeKind::Spawned);
5429 assert_eq!(changes[0].mask, POSITION);
5430 assert_eq!(changes[1].kind, StructuralChangeKind::ComponentsAdded);
5431 assert_eq!(changes[1].mask, VELOCITY);
5432 assert_eq!(changes[2].kind, StructuralChangeKind::ComponentsRemoved);
5433 assert_eq!(changes[2].mask, POSITION);
5434 assert_eq!(changes[3].kind, StructuralChangeKind::Despawned);
5435 assert_eq!(changes[3].mask, VELOCITY);
5436
5437 let cursor = changes[1].sequence;
5438 assert_eq!(world.structural_changes_since(cursor).len(), 2);
5439
5440 world.trim_structural_log(cursor);
5441 assert_eq!(world.structural_changes_since(0).len(), 2);
5442 assert_eq!(
5443 world.structural_changes_since(0)[0].kind,
5444 StructuralChangeKind::ComponentsRemoved
5445 );
5446
5447 world.clear_structural_log();
5448 assert!(world.structural_changes_since(0).is_empty());
5449 assert_eq!(world.structural_sequence(), 4);
5450 }
5451
5452 #[test]
5453 fn test_structural_log_set_component_records_add() {
5454 let mut world = World::default();
5455 let entity = world.spawn_entities(POSITION, 1)[0];
5456 world.set_velocity(entity, Velocity { x: 1.0, y: 0.0 });
5457
5458 let changes = world.structural_changes_since(0);
5459 assert_eq!(changes.len(), 2);
5460 assert_eq!(changes[1].kind, StructuralChangeKind::ComponentsAdded);
5461 assert_eq!(changes[1].mask, VELOCITY);
5462 }
5463
5464 #[test]
5465 fn test_tick_is_newer() {
5466 assert!(crate::tick_is_newer(1, 0));
5467 assert!(!crate::tick_is_newer(0, 0));
5468 assert!(!crate::tick_is_newer(0, 1));
5469 assert!(crate::tick_is_newer(0, u32::MAX));
5470 assert!(crate::tick_is_newer(5, u32::MAX - 3));
5471 assert!(!crate::tick_is_newer(u32::MAX, 0));
5472 }
5473
5474 #[test]
5475 fn test_allocator_liveness() {
5476 let mut allocator = EntityAllocator::default();
5477
5478 let entity = allocator.allocate();
5479 assert!(allocator.is_alive(entity));
5480
5481 assert!(allocator.deallocate(entity));
5482 assert!(!allocator.is_alive(entity));
5483 assert!(!allocator.deallocate(entity), "double free must be refused");
5484
5485 let reused = allocator.allocate();
5486 assert_eq!(reused.id, entity.id);
5487 assert_eq!(reused.generation, entity.generation + 1);
5488 assert!(allocator.is_alive(reused));
5489 assert!(!allocator.is_alive(entity));
5490
5491 assert!(
5492 !allocator.deallocate(entity),
5493 "stale free must not kill the reused id"
5494 );
5495 assert!(allocator.is_alive(reused));
5496
5497 let other = allocator.allocate();
5498 assert_ne!((other.id, other.generation), (reused.id, reused.generation));
5499 }
5500
5501 struct Lcg(u64);
5502
5503 impl Lcg {
5504 fn next(&mut self) -> u64 {
5505 self.0 = self
5506 .0
5507 .wrapping_mul(6364136223846793005)
5508 .wrapping_add(1442695040888963407);
5509 self.0 >> 16
5510 }
5511 }
5512
5513 #[test]
5514 fn test_event_channel_capacity_backstop() {
5515 let mut channel: EventChannel<u32> = EventChannel::new();
5516 let total = EVENT_CHANNEL_CAPACITY as u32 + 10;
5517 for value in 0..total {
5518 channel.send(value);
5519 }
5520
5521 assert_eq!(
5522 channel.len(),
5523 EVENT_CHANNEL_CAPACITY / 2 + 10,
5524 "crossing capacity must drop the oldest half exactly once"
5525 );
5526 assert_eq!(
5527 channel.sequence(),
5528 u64::from(total),
5529 "sequences must keep counting across the backstop"
5530 );
5531 assert_eq!(
5532 channel.peek(),
5533 Some(&(EVENT_CHANNEL_CAPACITY as u32 / 2)),
5534 "the survivor at the front is the first event after the dropped half"
5535 );
5536 assert_eq!(channel.events_since(u64::from(total) - 5).len(), 5);
5537
5538 channel.update();
5539 assert_eq!(
5540 channel.len(),
5541 EVENT_CHANNEL_CAPACITY / 2 + 10,
5542 "a stale previous-update watermark behind the base must not over-trim"
5543 );
5544
5545 channel.update();
5546 assert!(channel.is_empty());
5547 assert_eq!(channel.sequence(), u64::from(total));
5548 }
5549
5550 #[test]
5551 fn test_structural_log_capacity_backstop() {
5552 let mut world = World::default();
5553 let entity = world.spawn_entities(POSITION, 1)[0];
5554
5555 for _ in 0..STRUCTURAL_LOG_CAPACITY {
5556 world.record_structural(entity, StructuralChangeKind::ComponentsAdded, POSITION);
5557 }
5558
5559 assert_eq!(
5560 world.structural_log.len(),
5561 1,
5562 "the record that found the log full must clear it wholesale first"
5563 );
5564 assert_eq!(
5565 world.structural_sequence(),
5566 STRUCTURAL_LOG_CAPACITY as u64 + 1,
5567 "sequences must stay monotone across the wholesale clear"
5568 );
5569
5570 let tail = world.structural_changes_since(0);
5571 assert_eq!(tail.len(), 1);
5572 assert_eq!(tail[0].sequence, world.structural_sequence());
5573 assert!(
5574 world
5575 .structural_changes_since(world.structural_sequence())
5576 .is_empty()
5577 );
5578 }
5579
5580 #[derive(Default, Clone)]
5581 struct ModelEntity {
5582 mask: u64,
5583 position: Option<f32>,
5584 position_changed: bool,
5585 player: bool,
5586 enemy: bool,
5587 }
5588
5589 enum ModelCommand {
5590 Spawn(u64),
5591 Despawn(Entity),
5592 AddComponents(Entity, u64),
5593 RemoveComponents(Entity, u64),
5594 SetPosition(Entity, f32),
5595 AddPlayer(Entity),
5596 RemovePlayer(Entity),
5597 }
5598
5599 fn model_add_components(model_entity: &mut ModelEntity, mask: u64) {
5600 let migrated = mask & !model_entity.mask != 0;
5601 if mask & POSITION != 0 && model_entity.mask & POSITION == 0 {
5602 model_entity.position = Some(0.0);
5603 }
5604 model_entity.mask |= mask;
5605 if migrated && model_entity.mask & POSITION != 0 {
5606 model_entity.position_changed = true;
5607 }
5608 }
5609
5610 fn model_remove_components(model_entity: &mut ModelEntity, mask: u64) {
5611 let migrated = mask & model_entity.mask != 0;
5612 if mask & POSITION != 0 {
5613 model_entity.position = None;
5614 }
5615 model_entity.mask &= !mask;
5616 if migrated && model_entity.mask & POSITION != 0 {
5617 model_entity.position_changed = true;
5618 }
5619 }
5620
5621 fn model_set_position(model_entity: &mut ModelEntity, value: f32) {
5622 model_entity.mask |= POSITION;
5623 model_entity.position = Some(value);
5624 model_entity.position_changed = true;
5625 }
5626
5627 fn model_spawn(mask: u64) -> ModelEntity {
5628 ModelEntity {
5629 mask,
5630 position: (mask & POSITION != 0).then_some(0.0),
5631 position_changed: mask & POSITION != 0,
5632 ..Default::default()
5633 }
5634 }
5635
5636 fn apply_and_replay(
5637 world: &mut World,
5638 model: &mut std::collections::HashMap<Entity, ModelEntity>,
5639 queued: &mut Vec<ModelCommand>,
5640 handles: &mut Vec<Entity>,
5641 ) {
5642 assert_eq!(
5643 world.command_count(),
5644 queued.len(),
5645 "world and model must queue in lockstep"
5646 );
5647 world.apply_commands();
5648
5649 let mut spawned_masks: Vec<u64> = Vec::new();
5650 for command in queued.drain(..) {
5651 match command {
5652 ModelCommand::Spawn(mask) => spawned_masks.push(mask),
5653 ModelCommand::Despawn(entity) => {
5654 model.remove(&entity);
5655 }
5656 ModelCommand::AddComponents(entity, mask) => {
5657 if let Some(model_entity) = model.get_mut(&entity) {
5658 model_add_components(model_entity, mask);
5659 }
5660 }
5661 ModelCommand::RemoveComponents(entity, mask) => {
5662 if let Some(model_entity) = model.get_mut(&entity) {
5663 model_remove_components(model_entity, mask);
5664 }
5665 }
5666 ModelCommand::SetPosition(entity, value) => {
5667 if let Some(model_entity) = model.get_mut(&entity) {
5668 model_set_position(model_entity, value);
5669 }
5670 }
5671 ModelCommand::AddPlayer(entity) => {
5672 if let Some(model_entity) = model.get_mut(&entity) {
5673 model_entity.player = true;
5674 }
5675 }
5676 ModelCommand::RemovePlayer(entity) => {
5677 if let Some(model_entity) = model.get_mut(&entity) {
5678 model_entity.player = false;
5679 }
5680 }
5681 }
5682 }
5683
5684 if !spawned_masks.is_empty() {
5685 let known: std::collections::HashSet<Entity> = model.keys().copied().collect();
5686 let new_entities: Vec<Entity> = world
5687 .get_all_entities()
5688 .into_iter()
5689 .filter(|entity| !known.contains(entity))
5690 .collect();
5691 assert_eq!(
5692 new_entities.len(),
5693 spawned_masks.len(),
5694 "queued spawns must materialize exactly"
5695 );
5696
5697 let mut actual_masks: Vec<u64> = new_entities
5698 .iter()
5699 .map(|&entity| world.component_mask(entity).unwrap())
5700 .collect();
5701 actual_masks.sort_unstable();
5702 spawned_masks.sort_unstable();
5703 assert_eq!(
5704 actual_masks, spawned_masks,
5705 "queued spawn masks must match the materialized archetypes"
5706 );
5707
5708 for &entity in &new_entities {
5709 let mask = world.component_mask(entity).unwrap();
5710 model.insert(entity, model_spawn(mask));
5711 handles.push(entity);
5712 }
5713 }
5714 }
5715
5716 #[test]
5717 fn test_property_single_world_matches_model() {
5718 let component_masks = [POSITION, VELOCITY, HEALTH];
5719
5720 for seed in [1u64, 42, 4242, 987654321] {
5721 let mut rng = Lcg(seed);
5722 let mut world = World::default();
5723 let mut model: std::collections::HashMap<Entity, ModelEntity> =
5724 std::collections::HashMap::new();
5725 let mut handles: Vec<Entity> = Vec::new();
5726 let mut queued: Vec<ModelCommand> = Vec::new();
5727 let mut pending_ping_values: Vec<u32> = Vec::new();
5728 let mut total_pings: u64 = 0;
5729
5730 world.step();
5731
5732 let random_mask = |rng: &mut Lcg| {
5733 let mut mask = 0;
5734 for &component in &component_masks {
5735 if rng.next().is_multiple_of(2) {
5736 mask |= component;
5737 }
5738 }
5739 mask
5740 };
5741 let pick = |rng: &mut Lcg, handles: &[Entity]| {
5742 if handles.is_empty() {
5743 None
5744 } else {
5745 Some(handles[rng.next() as usize % handles.len()])
5746 }
5747 };
5748
5749 for _ in 0..4000 {
5750 match rng.next() % 16 {
5751 0 | 1 => {
5752 let mask = random_mask(&mut rng);
5753 let entity = world.spawn_entities(mask, 1)[0];
5754 model.insert(entity, model_spawn(mask));
5755 handles.push(entity);
5756 }
5757 2 => {
5758 if let Some(entity) = pick(&mut rng, &handles) {
5759 let despawned = world.despawn_entities(&[entity]);
5760 let was_live = model.remove(&entity).is_some();
5761 assert_eq!(
5762 despawned.len() == 1,
5763 was_live,
5764 "despawn must succeed exactly for model-live handles"
5765 );
5766 }
5767 }
5768 3 => {
5769 if let Some(entity) = pick(&mut rng, &handles) {
5770 let mask = random_mask(&mut rng);
5771 let accepted = world.add_components(entity, mask);
5772 match model.get_mut(&entity) {
5773 Some(model_entity) => {
5774 assert!(accepted);
5775 model_add_components(model_entity, mask);
5776 }
5777 None => assert!(!accepted, "stale add must be refused"),
5778 }
5779 }
5780 }
5781 4 => {
5782 if let Some(entity) = pick(&mut rng, &handles) {
5783 let mask = random_mask(&mut rng);
5784 let accepted = world.remove_components(entity, mask);
5785 match model.get_mut(&entity) {
5786 Some(model_entity) => {
5787 assert!(accepted);
5788 model_remove_components(model_entity, mask);
5789 }
5790 None => assert!(!accepted, "stale remove must be refused"),
5791 }
5792 }
5793 }
5794 5 => {
5795 if let Some(entity) = pick(&mut rng, &handles) {
5796 world.add_player(entity);
5797 if let Some(model_entity) = model.get_mut(&entity) {
5798 model_entity.player = true;
5799 }
5800 assert_eq!(
5801 world.has_player(entity),
5802 model.get(&entity).map(|m| m.player).unwrap_or(false)
5803 );
5804 }
5805 }
5806 6 => {
5807 if let Some(entity) = pick(&mut rng, &handles) {
5808 let removed = world.remove_player(entity);
5809 let expected = match model.get_mut(&entity) {
5810 Some(model_entity) => {
5811 let had = model_entity.player;
5812 model_entity.player = false;
5813 had
5814 }
5815 None => false,
5816 };
5817 assert_eq!(removed, expected);
5818 }
5819 }
5820 7 => {
5821 if let Some(entity) = pick(&mut rng, &handles) {
5822 let value = (rng.next() % 1000) as f32;
5823 world.set_position(entity, Position { x: value, y: 0.0 });
5824 match model.get_mut(&entity) {
5825 Some(model_entity) => {
5826 model_set_position(model_entity, value);
5827 assert_eq!(world.get_position(entity).unwrap().x, value);
5828 }
5829 None => assert!(
5830 world.get_position(entity).is_none(),
5831 "stale set must not resurrect a component"
5832 ),
5833 }
5834 }
5835 }
5836 8 => {
5837 if let Some(entity) = pick(&mut rng, &handles) {
5838 world.queue_despawn_entity(entity);
5839 queued.push(ModelCommand::Despawn(entity));
5840 }
5841 }
5842 9 => {
5843 if let Some(entity) = pick(&mut rng, &handles) {
5844 let mask = random_mask(&mut rng);
5845 world.queue_add_components(entity, mask);
5846 queued.push(ModelCommand::AddComponents(entity, mask));
5847 }
5848 }
5849 10 => {
5850 if let Some(entity) = pick(&mut rng, &handles) {
5851 let mask = random_mask(&mut rng);
5852 world.queue_remove_components(entity, mask);
5853 queued.push(ModelCommand::RemoveComponents(entity, mask));
5854 }
5855 }
5856 11 => {
5857 if let Some(entity) = pick(&mut rng, &handles) {
5858 let value = (rng.next() % 1000) as f32;
5859 world.queue_set_position(entity, Position { x: value, y: 0.0 });
5860 queued.push(ModelCommand::SetPosition(entity, value));
5861 }
5862 }
5863 12 => {
5864 if let Some(entity) = pick(&mut rng, &handles) {
5865 if rng.next().is_multiple_of(2) {
5866 world.queue_add_player(entity);
5867 queued.push(ModelCommand::AddPlayer(entity));
5868 } else {
5869 world.queue_remove_player(entity);
5870 queued.push(ModelCommand::RemovePlayer(entity));
5871 }
5872 }
5873 }
5874 13 => {
5875 let mask = random_mask(&mut rng);
5876 world.queue_spawn_entities(mask, 1);
5877 queued.push(ModelCommand::Spawn(mask));
5878 }
5879 14 => {
5880 let value = rng.next() as u32;
5881 world.send_ping(PingEvent { value });
5882 pending_ping_values.push(value);
5883 total_pings += 1;
5884 }
5885 _ => {
5886 apply_and_replay(&mut world, &mut model, &mut queued, &mut handles);
5887
5888 let changed: std::collections::HashSet<Entity> =
5889 world.query_entities_changed(POSITION).collect();
5890 let expected: std::collections::HashSet<Entity> = model
5891 .iter()
5892 .filter(|(_, model_entity)| {
5893 model_entity.mask & POSITION != 0 && model_entity.position_changed
5894 })
5895 .map(|(&entity, _)| entity)
5896 .collect();
5897 assert_eq!(
5898 changed, expected,
5899 "changed-query set diverged from model with seed {seed}"
5900 );
5901
5902 world.step();
5903 for model_entity in model.values_mut() {
5904 model_entity.position_changed = false;
5905 }
5906
5907 let buffered: Vec<u32> = world.read_ping().map(|ping| ping.value).collect();
5908 assert_eq!(
5909 buffered, pending_ping_values,
5910 "post-step event buffer must hold exactly the just-ended frame, in order"
5911 );
5912 assert_eq!(world.sequence_ping(), total_pings);
5913 pending_ping_values.clear();
5914 }
5915 }
5916 }
5917
5918 apply_and_replay(&mut world, &mut model, &mut queued, &mut handles);
5919
5920 assert_eq!(world.entity_count(), model.len());
5921
5922 for (&entity, model_entity) in &model {
5923 assert_eq!(world.component_mask(entity), Some(model_entity.mask));
5924 assert_eq!(
5925 world.get_position(entity).map(|position| position.x),
5926 model_entity.position,
5927 "position value diverged from model with seed {seed}"
5928 );
5929 assert_eq!(world.has_player(entity), model_entity.player);
5930 assert_eq!(world.has_enemy(entity), model_entity.enemy);
5931 assert!(world.is_alive(entity));
5932 }
5933
5934 for &handle in &handles {
5935 if !model.contains_key(&handle) {
5936 assert_eq!(world.component_mask(handle), None);
5937 assert!(!world.has_player(handle));
5938 assert!(world.get_position(handle).is_none());
5939 assert!(!world.is_alive(handle));
5940 }
5941 }
5942
5943 for mask in [
5944 POSITION,
5945 VELOCITY,
5946 HEALTH,
5947 POSITION | VELOCITY,
5948 POSITION | HEALTH,
5949 ] {
5950 let expected = model
5951 .values()
5952 .filter(|model_entity| model_entity.mask & mask == mask)
5953 .count();
5954 assert_eq!(
5955 world.query_entities(mask).count(),
5956 expected,
5957 "query count diverged from model for mask {mask:b} with seed {seed}"
5958 );
5959 }
5960
5961 let expected_players = model.values().filter(|m| m.player).count();
5962 assert_eq!(world.query_player().count(), expected_players);
5963 }
5964 }
5965
5966 #[test]
5967 fn test_allocator_batch_mixes_recycled_and_fresh() {
5968 let mut allocator = EntityAllocator::default();
5969
5970 let first = allocator.allocate();
5971 let second = allocator.allocate();
5972 assert!(allocator.deallocate(first));
5973 assert!(allocator.deallocate(second));
5974
5975 let mut entities = Vec::new();
5976 allocator.allocate_batch(4, &mut entities);
5977 assert_eq!(entities.len(), 4);
5978
5979 let recycled_count = entities
5980 .iter()
5981 .filter(|entity| entity.generation > 0)
5982 .count();
5983 assert_eq!(recycled_count, 2, "both freed ids must be recycled first");
5984
5985 for &entity in &entities {
5986 assert!(allocator.is_alive(entity));
5987 }
5988 assert!(!allocator.is_alive(first));
5989 assert!(!allocator.is_alive(second));
5990
5991 let mut seen: Vec<_> = entities
5992 .iter()
5993 .map(|entity| (entity.id, entity.generation))
5994 .collect();
5995 seen.sort_unstable();
5996 seen.dedup();
5997 assert_eq!(seen.len(), 4, "batch handles must be distinct");
5998 }
5999
6000 #[test]
6001 fn test_event_channel_cursor_consumers() {
6002 let mut channel: EventChannel<u32> = EventChannel::new();
6003 channel.send(1);
6004 channel.send(2);
6005
6006 let mut cursor_a = 0;
6007 let mut cursor_b = 0;
6008
6009 assert_eq!(channel.events_since(cursor_a), &[1, 2]);
6010 cursor_a = channel.sequence();
6011
6012 channel.send(3);
6013 assert_eq!(channel.events_since(cursor_a), &[3]);
6014 cursor_a = channel.sequence();
6015
6016 assert_eq!(channel.events_since(cursor_b), &[1, 2, 3]);
6017 cursor_b = channel.sequence();
6018
6019 assert!(channel.events_since(cursor_a).is_empty());
6020 assert!(channel.events_since(cursor_b).is_empty());
6021 }
6022
6023 #[cfg(feature = "dynamic")]
6024 mod dynamic_schema_tests {
6025 #[derive(Default, Clone, Debug)]
6026 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
6027 struct SchemaPosition {
6028 _x: f32,
6029 }
6030
6031 #[derive(Default, Clone, Debug)]
6032 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
6033 struct SchemaVelocity {
6034 _x: f32,
6035 }
6036
6037 crate::dynamic_schema! {
6038 fn register_schema {
6039 position: SchemaPosition => SCHEMA_POSITION,
6040 velocity: SchemaVelocity => SCHEMA_VELOCITY,
6041 }
6042 }
6043
6044 #[test]
6045 fn test_dynamic_schema_declares_consts_and_registry_in_order() {
6046 assert_eq!(SCHEMA_POSITION, 1);
6047 assert_eq!(SCHEMA_VELOCITY, 2);
6048
6049 let registry = register_schema();
6050 assert_eq!(registry.components.len(), 2);
6051 assert_eq!(registry.remaining_bits(), 62);
6052
6053 let mut world = crate::dynamic::DynWorld::from_registry(register_schema());
6054 let key = world.register::<SchemaVelocity>();
6055 assert_eq!(key.mask, SCHEMA_VELOCITY);
6056 }
6057
6058 #[cfg(feature = "snapshot")]
6059 crate::dynamic_schema! {
6060 serde fn register_schema_serde {
6061 position: SchemaPosition => SERDE_SCHEMA_POSITION,
6062 velocity: SchemaVelocity => SERDE_SCHEMA_VELOCITY,
6063 }
6064 }
6065
6066 #[cfg(feature = "snapshot")]
6067 #[test]
6068 fn test_dynamic_schema_serde_mode_registers_codecs() {
6069 let mut world = crate::dynamic::DynWorld::from_registry(register_schema_serde());
6070 world.spawn((SchemaPosition { _x: 1.0 }, SchemaVelocity { _x: 2.0 }));
6071
6072 let snapshot = world.snapshot().unwrap();
6073 let restored =
6074 crate::dynamic::DynWorld::from_snapshot(register_schema_serde(), &snapshot)
6075 .unwrap();
6076 assert_eq!(restored.entity_count(), 1);
6077 assert_eq!(SERDE_SCHEMA_POSITION, 1);
6078 }
6079 }
6080
6081 #[test]
6082 fn test_stages_run_in_declaration_order() {
6083 let mut stages: Stages<Vec<&'static str>> = Stages::new();
6084 stages.add_stage("input").add_stage("simulation");
6085 stages.add_stage("render");
6086
6087 stages
6088 .stage_mut("render")
6089 .push("draw", |log: &mut Vec<&'static str>| log.push("draw"));
6090 stages
6091 .stage_mut("input")
6092 .push("poll", |log: &mut Vec<&'static str>| log.push("poll"));
6093 stages
6094 .stage_mut("simulation")
6095 .push("physics", |log: &mut Vec<&'static str>| log.push("physics"))
6096 .push("ai", |log: &mut Vec<&'static str>| log.push("ai"));
6097
6098 let mut log = Vec::new();
6099 stages.run(&mut log);
6100 assert_eq!(log, vec!["poll", "physics", "ai", "draw"]);
6101
6102 log.clear();
6103 stages.run_stage("simulation", &mut log);
6104 assert_eq!(log, vec!["physics", "ai"]);
6105 }
6106
6107 #[test]
6108 #[should_panic(expected = "already declared")]
6109 fn test_stages_reject_duplicate_names() {
6110 let mut stages: Stages<u32> = Stages::new();
6111 stages.add_stage("simulation").add_stage("simulation");
6112 }
6113
6114 #[test]
6115 #[should_panic(expected = "declared stages are [\"input\"]")]
6116 fn test_stages_missing_stage_names_the_declared_set() {
6117 let mut stages: Stages<u32> = Stages::new();
6118 stages.add_stage("input");
6119 stages.stage_mut("simulation");
6120 }
6121
6122 #[test]
6123 fn test_schedule_push_if_gates_on_condition() {
6124 let mut world = World::default();
6125 world.spawn_entities(POSITION, 1);
6126
6127 let mut schedule = Schedule::new();
6128 schedule.push_if(
6129 "conditional",
6130 |world: &World| world.entity_count() > 1,
6131 |world: &mut World| {
6132 let entity = world.get_all_entities()[0];
6133 world.get_position_mut(entity).unwrap().x += 1.0;
6134 },
6135 );
6136
6137 schedule.run(&mut world);
6138 let entity = world.get_all_entities()[0];
6139 assert_eq!(world.get_position(entity).unwrap().x, 0.0);
6140
6141 world.spawn_entities(POSITION, 1);
6142 schedule.run(&mut world);
6143 assert_eq!(world.get_position(entity).unwrap().x, 1.0);
6144 }
6145
6146 #[test]
6147 fn test_event_channel_consume_is_exactly_once() {
6148 let mut channel: EventChannel<u32> = EventChannel::new();
6149 channel.send(1);
6150 channel.send(2);
6151
6152 let mut cursor_a = 0;
6153 let mut cursor_b = 0;
6154
6155 assert_eq!(channel.consume(&mut cursor_a), &[1, 2]);
6156 assert!(channel.consume(&mut cursor_a).is_empty());
6157
6158 channel.update();
6159 channel.send(3);
6160 assert_eq!(
6161 channel.consume(&mut cursor_a),
6162 &[3],
6163 "the two-frame buffer must not re-deliver"
6164 );
6165
6166 assert_eq!(channel.consume(&mut cursor_b), &[1, 2, 3]);
6167 }
6168
6169 #[test]
6170 fn test_generated_consume_event_is_exactly_once() {
6171 let mut world = World::default();
6172
6173 world.send_ping(PingEvent { value: 1 });
6174
6175 let mut cursor = 0;
6176 assert_eq!(world.consume_ping(&mut cursor).len(), 1);
6177 assert!(world.consume_ping(&mut cursor).is_empty());
6178
6179 world.step();
6180 assert!(
6181 world.consume_ping(&mut cursor).is_empty(),
6182 "collect_ would re-deliver here; consume_ must not"
6183 );
6184
6185 world.send_ping(PingEvent { value: 2 });
6186 assert_eq!(world.consume_ping(&mut cursor).len(), 1);
6187 }
6188
6189 #[test]
6190 fn test_event_channel_two_frame_expiry() {
6191 let mut channel: EventChannel<u32> = EventChannel::new();
6192 channel.send(1);
6193
6194 channel.update();
6195 assert_eq!(channel.len(), 1, "event survives its first frame boundary");
6196
6197 channel.send(2);
6198 channel.update();
6199 assert_eq!(
6200 channel.events_since(0),
6201 &[2],
6202 "first event expired, second survives"
6203 );
6204
6205 channel.update();
6206 assert!(channel.is_empty());
6207 }
6208
6209 #[test]
6210 fn test_event_channel_read_frame_is_settled_prior_frame() {
6211 let mut channel: EventChannel<u32> = EventChannel::new();
6212
6213 assert_eq!(channel.read_frame(), &[] as &[u32]);
6214
6215 channel.send(1);
6216 channel.send(2);
6217 assert_eq!(
6218 channel.read_frame(),
6219 &[] as &[u32],
6220 "this frame's sends are not settled yet"
6221 );
6222 channel.update();
6223
6224 assert_eq!(channel.read_frame(), &[1, 2]);
6225 channel.send(3);
6226 assert_eq!(
6227 channel.read_frame(),
6228 &[1, 2],
6229 "a later send does not change the settled frame"
6230 );
6231 assert_eq!(channel.read_frame(), &[1, 2], "a broadcast read repeats");
6232 channel.update();
6233
6234 assert_eq!(
6235 channel.read_frame(),
6236 &[3],
6237 "frame one expired, frame two settled"
6238 );
6239 channel.update();
6240
6241 assert_eq!(
6242 channel.read_frame(),
6243 &[] as &[u32],
6244 "an empty frame settles empty"
6245 );
6246 }
6247
6248 #[test]
6249 fn test_event_channel_trim_and_lagging_cursor() {
6250 let mut channel: EventChannel<u32> = EventChannel::new();
6251 for value in 0..10 {
6252 channel.send(value);
6253 }
6254
6255 channel.trim(4);
6256 assert_eq!(channel.len(), 6);
6257 assert_eq!(
6258 channel.events_since(0),
6259 &[4, 5, 6, 7, 8, 9],
6260 "a lagging cursor sees everything still buffered"
6261 );
6262 assert_eq!(channel.events_since(7), &[7, 8, 9]);
6263 assert_eq!(channel.sequence(), 10);
6264
6265 channel.clear();
6266 assert!(channel.is_empty());
6267 assert_eq!(
6268 channel.sequence(),
6269 10,
6270 "clearing advances past events without reusing sequences"
6271 );
6272 }
6273
6274 #[test]
6275 fn test_single_world_double_despawn() {
6276 let mut world = World::default();
6277 let entity = world.spawn_entities(POSITION, 1)[0];
6278
6279 assert_eq!(world.despawn_entities(&[entity]).len(), 1);
6280 assert!(world.despawn_entities(&[entity]).is_empty());
6281 assert!(world.despawn_entities(&[entity, entity]).is_empty());
6282
6283 let e1 = world.spawn_entities(POSITION, 1)[0];
6284 let e2 = world.spawn_entities(POSITION, 1)[0];
6285 assert_ne!((e1.id, e1.generation), (e2.id, e2.generation));
6286 }
6287
6288 #[test]
6289 fn test_single_world_duplicate_despawn_in_one_call() {
6290 let mut world = World::default();
6291 let entity = world.spawn_entities(POSITION, 1)[0];
6292
6293 let despawned = world.despawn_entities(&[entity, entity, entity]);
6294 assert_eq!(despawned.len(), 1);
6295
6296 let e1 = world.spawn_entities(POSITION, 1)[0];
6297 let e2 = world.spawn_entities(POSITION, 1)[0];
6298 assert_ne!((e1.id, e1.generation), (e2.id, e2.generation));
6299 }
6300
6301 #[test]
6302 #[should_panic(expected = "spawn masks must not contain tag bits")]
6303 fn test_spawn_with_tag_bits_panics_in_debug() {
6304 let mut world = World::default();
6305 world.spawn_entities(POSITION | PLAYER, 1);
6306 }
6307
6308 #[test]
6309 #[should_panic(expected = "component masks must not contain tag bits")]
6310 fn test_add_components_with_tag_bits_panics_in_debug() {
6311 let mut world = World::default();
6312 let entity = world.spawn_entities(POSITION, 1)[0];
6313 world.add_components(entity, VELOCITY | PLAYER);
6314 }
6315
6316 #[test]
6317 #[should_panic(expected = "component masks only")]
6318 fn test_query_entities_with_tag_bits_panics_in_debug() {
6319 let world = World::default();
6320 let _ = world.query_entities(POSITION | PLAYER);
6321 }
6322
6323 #[test]
6324 fn test_tag_masks_with_empty_sets() {
6325 let mut world = World::default();
6326 world.spawn_entities(POSITION, 3);
6327
6328 let mut count = 0;
6329 world.for_each(POSITION, PLAYER, |_entity, _table, _idx| count += 1);
6330 assert_eq!(count, 3, "excluding a tag nobody has excludes nothing");
6331
6332 count = 0;
6333 world.for_each(POSITION | PLAYER, 0, |_entity, _table, _idx| count += 1);
6334 assert_eq!(count, 0, "including a tag nobody has matches nothing");
6335
6336 count = 0;
6337 world.for_each_mut(POSITION | PLAYER, 0, |_entity, _table, _idx| count += 1);
6338 assert_eq!(count, 0);
6339
6340 count = 0;
6341 world.for_each_mut(POSITION, PLAYER, |_entity, _table, _idx| count += 1);
6342 assert_eq!(count, 3);
6343 }
6344
6345 #[test]
6346 fn test_query_component_mut_with_tag_filter() {
6347 let mut world = World::default();
6348 let e1 = world.spawn_entities(POSITION, 1)[0];
6349 let e2 = world.spawn_entities(POSITION, 1)[0];
6350 world.add_player(e1);
6351
6352 let mut visited = Vec::new();
6353 world.query_position_mut(PLAYER, |entity, position| {
6354 position.x = 42.0;
6355 visited.push(entity);
6356 });
6357
6358 assert_eq!(visited, vec![e1]);
6359 assert_eq!(world.get_position(e1).unwrap().x, 42.0);
6360 assert_eq!(world.get_position(e2).unwrap().x, 0.0);
6361 }
6362
6363 #[test]
6364 fn test_query_component_mut_marks_changed() {
6365 let mut world = World::default();
6366 let entity = world.spawn_entities(POSITION, 1)[0];
6367 world.step();
6368
6369 world.query_position_mut(0, |_entity, position| {
6370 position.x = 1.0;
6371 });
6372
6373 let changed: Vec<Entity> = world.query_entities_changed(POSITION).collect();
6374 assert_eq!(changed, vec![entity]);
6375 }
6376
6377 #[test]
6378 fn test_simd_slice_iteration_read() {
6379 let mut world = World::default();
6380 world.spawn_entities(POSITION, 3);
6381
6382 let mut total_count = 0;
6383 for slice in world.iter_position_slices() {
6384 total_count += slice.len();
6385 for pos in slice {
6386 assert_eq!(pos.x, 0.0);
6387 assert_eq!(pos.y, 0.0);
6388 }
6389 }
6390
6391 assert_eq!(total_count, 3);
6392 }
6393
6394 #[test]
6395 fn test_simd_slice_iteration_write() {
6396 let mut world = World::default();
6397 let entities = world.spawn_entities(POSITION, 5);
6398
6399 for slice in world.iter_position_slices_mut() {
6400 for pos in slice {
6401 pos.x = 10.0;
6402 pos.y = 20.0;
6403 }
6404 }
6405
6406 for entity in entities {
6407 let pos = world.get_position(entity).unwrap();
6408 assert_eq!(pos.x, 10.0);
6409 assert_eq!(pos.y, 20.0);
6410 }
6411 }
6412
6413 #[test]
6414 fn test_simd_slice_iteration_multiple_archetypes() {
6415 let mut world = World::default();
6416 world.spawn_entities(POSITION, 2);
6417 world.spawn_entities(POSITION | VELOCITY, 3);
6418 world.spawn_entities(POSITION | HEALTH, 4);
6419
6420 let mut slice_count = 0;
6421 let mut total_entities = 0;
6422
6423 for slice in world.iter_position_slices() {
6424 slice_count += 1;
6425 total_entities += slice.len();
6426 }
6427
6428 assert_eq!(slice_count, 3);
6429 assert_eq!(total_entities, 9);
6430 }
6431
6432 #[test]
6433 fn test_simd_slice_vectorizable_operation() {
6434 let mut world = World::default();
6435 world.spawn_entities(POSITION | VELOCITY, 1000);
6436
6437 for pos in world.iter_position_slices_mut() {
6438 for p in pos {
6439 p.x += 1.0;
6440 p.y += 2.0;
6441 }
6442 }
6443
6444 for vel in world.iter_velocity_slices_mut() {
6445 for v in vel {
6446 v.x *= 0.99;
6447 v.y *= 0.99;
6448 }
6449 }
6450
6451 let mut checked = 0;
6452 for slice in world.iter_position_slices() {
6453 for pos in slice {
6454 assert_eq!(pos.x, 1.0);
6455 assert_eq!(pos.y, 2.0);
6456 checked += 1;
6457 }
6458 }
6459 assert_eq!(checked, 1000);
6460 }
6461
6462 #[test]
6463 fn test_simd_slice_empty_world() {
6464 let world = World::default();
6465
6466 let mut count = 0;
6467 for _ in world.iter_position_slices() {
6468 count += 1;
6469 }
6470 assert_eq!(count, 0);
6471 }
6472
6473 #[test]
6474 fn test_simd_slice_no_matching_archetype() {
6475 let mut world = World::default();
6476 world.spawn_entities(VELOCITY, 5);
6477
6478 let mut count = 0;
6479 for _ in world.iter_position_slices() {
6480 count += 1;
6481 }
6482 assert_eq!(count, 0);
6483 }
6484
6485 #[test]
6486 fn test_sparse_set_add_remove_has() {
6487 let mut world = World::default();
6488 let entity = world.spawn_entities(POSITION, 1)[0];
6489
6490 assert!(!world.has_player(entity));
6491
6492 world.add_player(entity);
6493 assert!(world.has_player(entity));
6494
6495 world.remove_player(entity);
6496 assert!(!world.has_player(entity));
6497 }
6498
6499 #[test]
6500 fn test_sparse_set_query() {
6501 let mut world = World::default();
6502 let e1 = world.spawn_entities(POSITION, 1)[0];
6503 let e2 = world.spawn_entities(POSITION, 1)[0];
6504 let e3 = world.spawn_entities(POSITION, 1)[0];
6505
6506 world.add_player(e1);
6507 world.add_player(e3);
6508
6509 let players: Vec<Entity> = world.query_player().collect();
6510 assert_eq!(players.len(), 2);
6511 assert!(players.contains(&e1));
6512 assert!(players.contains(&e3));
6513 assert!(!players.contains(&e2));
6514 }
6515
6516 #[test]
6517 fn test_sparse_set_no_archetype_fragmentation() {
6518 let mut world = World::default();
6519 let e1 = world.spawn_entities(POSITION, 1)[0];
6520 let e2 = world.spawn_entities(POSITION, 1)[0];
6521 let e3 = world.spawn_entities(POSITION, 1)[0];
6522
6523 world.add_player(e1);
6524 world.add_enemy(e2);
6525
6526 let mut count = 0;
6527 world.for_each(POSITION, 0, |_, _, _| {
6528 count += 1;
6529 });
6530
6531 assert_eq!(count, 3);
6532
6533 let mask1 = world.component_mask(e1).unwrap();
6534 let mask2 = world.component_mask(e2).unwrap();
6535 let mask3 = world.component_mask(e3).unwrap();
6536
6537 assert_eq!(mask1, mask2);
6538 assert_eq!(mask2, mask3);
6539 }
6540
6541 #[test]
6542 fn test_sparse_set_tag_component_query() {
6543 let mut world = World::default();
6544 let e1 = world.spawn_entities(POSITION | VELOCITY, 1)[0];
6545 let e2 = world.spawn_entities(POSITION | VELOCITY, 1)[0];
6546 let e3 = world.spawn_entities(POSITION, 1)[0];
6547 let e4 = world.spawn_entities(POSITION | VELOCITY, 1)[0];
6548
6549 world.add_player(e1);
6550 world.add_player(e2);
6551
6552 let mut count = 0;
6553 world.for_each(POSITION | VELOCITY | PLAYER, 0, |_, _, _| {
6554 count += 1;
6555 });
6556 assert_eq!(count, 2);
6557
6558 let mut found = Vec::new();
6559 world.for_each(POSITION | VELOCITY | PLAYER, 0, |entity, _, _| {
6560 found.push(entity);
6561 });
6562 assert!(found.contains(&e1));
6563 assert!(found.contains(&e2));
6564 assert!(!found.contains(&e3));
6565 assert!(!found.contains(&e4));
6566 }
6567
6568 #[test]
6569 fn test_sparse_set_exclude_tag() {
6570 let mut world = World::default();
6571 let e1 = world.spawn_entities(POSITION, 1)[0];
6572 let e2 = world.spawn_entities(POSITION, 1)[0];
6573 let e3 = world.spawn_entities(POSITION, 1)[0];
6574
6575 world.add_player(e1);
6576 world.add_player(e2);
6577
6578 let mut count = 0;
6579 world.for_each(POSITION, PLAYER, |_, _, _| {
6580 count += 1;
6581 });
6582 assert_eq!(count, 1);
6583
6584 let mut found = Vec::new();
6585 world.for_each(POSITION, PLAYER, |entity, _, _| {
6586 found.push(entity);
6587 });
6588 assert_eq!(found.len(), 1);
6589 assert!(found.contains(&e3));
6590 }
6591
6592 #[test]
6593 fn test_sparse_set_cleanup_on_despawn() {
6594 let mut world = World::default();
6595 let e1 = world.spawn_entities(POSITION, 1)[0];
6596 let e2 = world.spawn_entities(POSITION, 1)[0];
6597
6598 world.add_player(e1);
6599 world.add_enemy(e2);
6600
6601 assert_eq!(world.query_player().count(), 1);
6602 assert_eq!(world.query_enemy().count(), 1);
6603
6604 world.despawn_entities(&[e1]);
6605
6606 assert_eq!(world.query_player().count(), 0);
6607 assert_eq!(world.query_enemy().count(), 1);
6608
6609 world.despawn_entities(&[e2]);
6610 assert_eq!(world.query_enemy().count(), 0);
6611 }
6612
6613 #[test]
6614 fn test_sparse_set_multiple_tags() {
6615 let mut world = World::default();
6616 let entity = world.spawn_entities(POSITION, 1)[0];
6617
6618 world.add_player(entity);
6619 world.add_active(entity);
6620
6621 assert!(world.has_player(entity));
6622 assert!(world.has_active(entity));
6623 assert!(!world.has_enemy(entity));
6624
6625 let mut count = 0;
6626 world.for_each(POSITION | PLAYER | ACTIVE, 0, |_, _, _| {
6627 count += 1;
6628 });
6629 assert_eq!(count, 1);
6630
6631 world.remove_active(entity);
6632 assert!(world.has_player(entity));
6633 assert!(!world.has_active(entity));
6634
6635 count = 0;
6636 world.for_each(POSITION | PLAYER | ACTIVE, 0, |_, _, _| {
6637 count += 1;
6638 });
6639 assert_eq!(count, 0);
6640 }
6641
6642 #[test]
6643 fn test_sparse_set_for_each_mut() {
6644 let mut world = World::default();
6645 let e1 = world.spawn_entities(POSITION, 1)[0];
6646 let e2 = world.spawn_entities(POSITION, 1)[0];
6647 let e3 = world.spawn_entities(POSITION, 1)[0];
6648
6649 world.add_player(e1);
6650 world.add_player(e3);
6651
6652 world.for_each_mut(POSITION | PLAYER, 0, |_, table, idx| {
6653 table.position[idx].x = 100.0;
6654 });
6655
6656 assert_eq!(world.get_position(e1).unwrap().x, 100.0);
6657 assert_ne!(world.get_position(e2).unwrap().x, 100.0);
6658 assert_eq!(world.get_position(e3).unwrap().x, 100.0);
6659 }
6660
6661 #[test]
6662 #[cfg(not(target_family = "wasm"))]
6663 fn test_sparse_set_par_for_each_mut() {
6664 let mut world = World::default();
6665 let entities = world.spawn_entities(POSITION, 100);
6666
6667 for &entity in &entities[0..50] {
6668 world.add_player(entity);
6669 }
6670
6671 world.par_for_each_mut(POSITION | PLAYER, 0, |_, table, idx| {
6672 table.position[idx].x = 200.0;
6673 });
6674
6675 for &entity in entities.iter().take(50) {
6676 assert_eq!(world.get_position(entity).unwrap().x, 200.0);
6677 }
6678 for &entity in entities.iter().skip(50).take(50) {
6679 assert_ne!(world.get_position(entity).unwrap().x, 200.0);
6680 }
6681 }
6682
6683 #[test]
6684 fn test_sparse_set_complex_query() {
6685 let mut world = World::default();
6686 let e1 = world.spawn_entities(POSITION | VELOCITY, 1)[0];
6687 let e2 = world.spawn_entities(POSITION | VELOCITY, 1)[0];
6688 let e3 = world.spawn_entities(POSITION | VELOCITY, 1)[0];
6689 let _e4 = world.spawn_entities(POSITION, 1)[0];
6690
6691 world.add_player(e1);
6692 world.add_enemy(e2);
6693 world.add_enemy(e3);
6694 world.add_active(e1);
6695 world.add_active(e2);
6696
6697 let mut count = 0;
6698 world.for_each(POSITION | VELOCITY | ENEMY | ACTIVE, 0, |_, _, _| {
6699 count += 1;
6700 });
6701 assert_eq!(count, 1);
6702
6703 let mut found = Vec::new();
6704 world.for_each(POSITION | VELOCITY | ENEMY | ACTIVE, 0, |entity, _, _| {
6705 found.push(entity);
6706 });
6707 assert_eq!(found.len(), 1);
6708 assert!(found.contains(&e2));
6709 }
6710
6711 #[test]
6712 fn test_command_buffer_spawn() {
6713 let mut world = World::default();
6714
6715 world.queue_spawn_entities(POSITION, 3);
6716 world.queue_spawn_entities(VELOCITY, 2);
6717
6718 assert_eq!(world.command_count(), 2);
6719 assert_eq!(world.get_all_entities().len(), 0);
6720
6721 world.apply_commands();
6722
6723 assert_eq!(world.command_count(), 0);
6724 assert_eq!(world.get_all_entities().len(), 5);
6725
6726 let mut pos_count = 0;
6727 world.for_each(POSITION, 0, |_, _, _| pos_count += 1);
6728 assert_eq!(pos_count, 3);
6729
6730 let mut vel_count = 0;
6731 world.for_each(VELOCITY, 0, |_, _, _| vel_count += 1);
6732 assert_eq!(vel_count, 2);
6733 }
6734
6735 #[test]
6736 fn test_command_buffer_despawn() {
6737 let mut world = World::default();
6738 let entities = world.spawn_entities(POSITION, 5);
6739
6740 world.queue_despawn_entity(entities[0]);
6741 world.queue_despawn_entity(entities[2]);
6742 world.queue_despawn_entity(entities[4]);
6743
6744 assert_eq!(world.command_count(), 3);
6745 assert_eq!(world.get_all_entities().len(), 5);
6746
6747 world.apply_commands();
6748
6749 assert_eq!(world.command_count(), 0);
6750 assert_eq!(world.get_all_entities().len(), 2);
6751
6752 let remaining = world.get_all_entities();
6753 assert!(remaining.contains(&entities[1]));
6754 assert!(remaining.contains(&entities[3]));
6755 }
6756
6757 #[test]
6758 fn test_command_buffer_add_remove_components() {
6759 let mut world = World::default();
6760 let entity = world.spawn_entities(POSITION, 1)[0];
6761
6762 world.queue_add_components(entity, VELOCITY);
6763 assert_eq!(world.command_count(), 1);
6764 assert!(world.get_velocity(entity).is_none());
6765
6766 world.apply_commands();
6767 assert!(world.get_velocity(entity).is_some());
6768
6769 world.queue_remove_components(entity, VELOCITY);
6770 world.apply_commands();
6771 assert!(world.get_velocity(entity).is_none());
6772 }
6773
6774 #[test]
6775 fn test_command_buffer_set_component() {
6776 let mut world = World::default();
6777 let entity = world.spawn_entities(POSITION, 1)[0];
6778
6779 world.queue_set_position(entity, Position { x: 100.0, y: 200.0 });
6780 assert_eq!(world.command_count(), 1);
6781 assert_ne!(world.get_position(entity).unwrap().x, 100.0);
6782
6783 world.apply_commands();
6784
6785 assert_eq!(world.command_count(), 0);
6786 let pos = world.get_position(entity).unwrap();
6787 assert_eq!(pos.x, 100.0);
6788 assert_eq!(pos.y, 200.0);
6789 }
6790
6791 #[test]
6792 fn test_command_buffer_tags() {
6793 let mut world = World::default();
6794 let entity = world.spawn_entities(POSITION, 1)[0];
6795
6796 world.queue_add_player(entity);
6797 world.queue_add_active(entity);
6798
6799 assert_eq!(world.command_count(), 2);
6800 assert!(!world.has_player(entity));
6801 assert!(!world.has_active(entity));
6802
6803 world.apply_commands();
6804
6805 assert!(world.has_player(entity));
6806 assert!(world.has_active(entity));
6807
6808 world.queue_remove_player(entity);
6809 world.apply_commands();
6810
6811 assert!(!world.has_player(entity));
6812 assert!(world.has_active(entity));
6813 }
6814
6815 #[test]
6816 fn test_command_buffer_mixed_operations() {
6817 let mut world = World::default();
6818 let e1 = world.spawn_entities(POSITION, 1)[0];
6819
6820 world.queue_spawn_entities(VELOCITY, 2);
6821 world.queue_add_components(e1, VELOCITY);
6822 world.queue_set_position(e1, Position { x: 50.0, y: 75.0 });
6823 world.queue_add_player(e1);
6824
6825 assert_eq!(world.command_count(), 4);
6826
6827 world.apply_commands();
6828
6829 assert_eq!(world.command_count(), 0);
6830 assert_eq!(world.get_all_entities().len(), 3);
6831 assert!(world.get_velocity(e1).is_some());
6832 assert_eq!(world.get_position(e1).unwrap().x, 50.0);
6833 assert!(world.has_player(e1));
6834 }
6835
6836 #[test]
6837 fn test_command_buffer_clear() {
6838 let mut world = World::default();
6839
6840 world.queue_spawn_entities(POSITION, 5);
6841 world.queue_spawn_entities(VELOCITY, 3);
6842
6843 assert_eq!(world.command_count(), 2);
6844
6845 world.clear_commands();
6846
6847 assert_eq!(world.command_count(), 0);
6848 assert_eq!(world.get_all_entities().len(), 0);
6849 }
6850
6851 #[test]
6852 fn test_command_buffer_multiple_batches() {
6853 let mut world = World::default();
6854
6855 world.queue_spawn_entities(POSITION, 2);
6856 world.apply_commands();
6857 assert_eq!(world.get_all_entities().len(), 2);
6858
6859 world.queue_spawn_entities(VELOCITY, 3);
6860 world.apply_commands();
6861 assert_eq!(world.get_all_entities().len(), 5);
6862
6863 world.queue_spawn_entities(POSITION | VELOCITY, 1);
6864 world.apply_commands();
6865 assert_eq!(world.get_all_entities().len(), 6);
6866 }
6867
6868 #[test]
6869 fn test_command_buffer_despawn_batch() {
6870 let mut world = World::default();
6871 let entities = world.spawn_entities(POSITION, 10);
6872
6873 let to_despawn = vec![entities[0], entities[3], entities[5], entities[9]];
6874 world.queue_despawn_entities(to_despawn.clone());
6875
6876 assert_eq!(world.command_count(), 1);
6877 world.apply_commands();
6878
6879 assert_eq!(world.get_all_entities().len(), 6);
6880
6881 let remaining = world.get_all_entities();
6882 for &entity in &to_despawn {
6883 assert!(!remaining.contains(&entity));
6884 }
6885 }
6886
6887 #[test]
6888 fn test_command_buffer_parallel_safety() {
6889 let mut world = World::default();
6890 let _ = world.spawn_entities(POSITION | VELOCITY, 100);
6891
6892 let mut to_despawn = Vec::new();
6893 world.for_each(POSITION | VELOCITY, 0, |entity, table, idx| {
6894 if table.position[idx].x < 0.0 {
6895 to_despawn.push(entity);
6896 }
6897 });
6898
6899 for entity in to_despawn {
6900 world.queue_despawn_entity(entity);
6901 }
6902
6903 let command_count_before = world.command_count();
6904 world.apply_commands();
6905
6906 assert_eq!(world.get_all_entities().len(), 100 - command_count_before);
6907 }
6908
6909 #[test]
6910 fn test_ergonomic_query_builder() {
6911 let mut world = World::default();
6912 world.spawn_entities(POSITION | VELOCITY, 3);
6913 world.spawn_entities(POSITION, 2);
6914
6915 let mut count = 0;
6916 world
6917 .query()
6918 .with(POSITION)
6919 .with(VELOCITY)
6920 .iter(|_entity, _table, _idx| {
6921 count += 1;
6922 });
6923 assert_eq!(count, 3);
6924
6925 count = 0;
6926 world
6927 .query()
6928 .with(POSITION)
6929 .without(VELOCITY)
6930 .iter(|_entity, _table, _idx| {
6931 count += 1;
6932 });
6933 assert_eq!(count, 2);
6934 }
6935
6936 #[test]
6937 fn test_ergonomic_query_builder_mut() {
6938 let mut world = World::default();
6939 let entities = world.spawn_entities(POSITION, 3);
6940
6941 world
6942 .query_mut()
6943 .with(POSITION)
6944 .iter(|_entity, table, idx| {
6945 table.position[idx].x = 100.0;
6946 });
6947
6948 for &entity in &entities {
6949 assert_eq!(world.get_position(entity).unwrap().x, 100.0);
6950 }
6951 }
6952
6953 #[test]
6954 fn test_ergonomic_single_component_iter() {
6955 let mut world = World::default();
6956 world.spawn_entities(POSITION, 5);
6957
6958 let mut count = 0;
6959 world.iter_position(|_entity, pos| {
6960 assert_eq!(pos.x, 0.0);
6961 count += 1;
6962 });
6963 assert_eq!(count, 5);
6964 }
6965
6966 #[test]
6967 fn test_ergonomic_single_component_iter_mut() {
6968 let mut world = World::default();
6969 let entities = world.spawn_entities(POSITION, 5);
6970
6971 world.iter_position_mut(|_entity, pos| {
6972 pos.x = 42.0;
6973 });
6974
6975 for &entity in &entities {
6976 assert_eq!(world.get_position(entity).unwrap().x, 42.0);
6977 }
6978 }
6979
6980 #[test]
6981 fn test_ergonomic_iter_with_entity() {
6982 let mut world = World::default();
6983 let e1 = world.spawn_entities(POSITION, 1)[0];
6984 let e2 = world.spawn_entities(POSITION, 1)[0];
6985
6986 let mut found = vec![];
6987 world.iter_position(|entity, _pos| {
6988 found.push(entity);
6989 });
6990
6991 assert_eq!(found.len(), 2);
6992 assert!(found.contains(&e1));
6993 assert!(found.contains(&e2));
6994 }
6995
6996 #[test]
6997 fn test_ergonomic_batch_spawn() {
6998 let mut world = World::default();
6999
7000 let entities = world.spawn_batch(POSITION | VELOCITY, 100, |table, idx| {
7001 table.position[idx] = Position {
7002 x: idx as f32,
7003 y: idx as f32 * 2.0,
7004 };
7005 table.velocity[idx] = Velocity { x: 1.0, y: -1.0 };
7006 });
7007
7008 assert_eq!(entities.len(), 100);
7009
7010 for (i, &entity) in entities.iter().enumerate() {
7011 let pos = world.get_position(entity).unwrap();
7012 assert_eq!(pos.x, i as f32);
7013 assert_eq!(pos.y, i as f32 * 2.0);
7014
7015 let vel = world.get_velocity(entity).unwrap();
7016 assert_eq!(vel.x, 1.0);
7017 assert_eq!(vel.y, -1.0);
7018 }
7019 }
7020
7021 #[test]
7022 fn test_ergonomic_query_with_tags() {
7023 let mut world = World::default();
7024 let e1 = world.spawn_entities(POSITION, 1)[0];
7025 let e2 = world.spawn_entities(POSITION, 1)[0];
7026 let _e3 = world.spawn_entities(POSITION, 1)[0];
7027
7028 world.add_player(e1);
7029 world.add_player(e2);
7030
7031 let mut count = 0;
7032 world
7033 .query()
7034 .with(POSITION | PLAYER)
7035 .iter(|_entity, _table, _idx| {
7036 count += 1;
7037 });
7038 assert_eq!(count, 2);
7039
7040 count = 0;
7041 world
7042 .query()
7043 .with(POSITION)
7044 .without(PLAYER)
7045 .iter(|_entity, _table, _idx| {
7046 count += 1;
7047 });
7048 assert_eq!(count, 1);
7049 }
7050
7051 #[test]
7052 fn test_query_builder_mut_without() {
7053 let mut world = World::default();
7054 let e1 = world.spawn_entities(POSITION | VELOCITY, 1)[0];
7055 let e2 = world.spawn_entities(POSITION, 1)[0];
7056
7057 world.set_position(e1, Position { x: 1.0, y: 2.0 });
7058 world.set_position(e2, Position { x: 3.0, y: 4.0 });
7059
7060 let mut count = 0;
7061 world
7062 .query_mut()
7063 .with(POSITION)
7064 .without(VELOCITY)
7065 .iter(|_entity, _table, _idx| {
7066 count += 1;
7067 });
7068 assert_eq!(count, 1);
7069 }
7070
7071 #[test]
7072 fn test_iter_methods() {
7073 let mut world = World::default();
7074 let e1 = world.spawn_entities(POSITION | VELOCITY | HEALTH, 1)[0];
7075 let e2 = world.spawn_entities(POSITION | VELOCITY, 1)[0];
7076
7077 world.set_position(e1, Position { x: 1.0, y: 2.0 });
7078 world.set_position(e2, Position { x: 3.0, y: 4.0 });
7079 world.set_velocity(e1, Velocity { x: 1.0, y: 1.0 });
7080 world.set_velocity(e2, Velocity { x: 2.0, y: 2.0 });
7081 world.set_health(e1, Health { value: 100.0 });
7082
7083 let mut sum_x = 0.0;
7084 world.iter_position(|_entity, pos| {
7085 sum_x += pos.x;
7086 });
7087 assert_eq!(sum_x, 4.0);
7088
7089 world.iter_position_mut(|_entity, pos| {
7090 pos.x *= 2.0;
7091 });
7092
7093 let mut count = 0;
7094 world.iter_velocity(|_entity, _vel| {
7095 count += 1;
7096 });
7097 assert_eq!(count, 2);
7098
7099 world.iter_velocity_mut(|_entity, vel| {
7100 vel.x *= 2.0;
7101 });
7102
7103 world.iter_health_mut(|_entity, health| {
7104 health.value += 10.0;
7105 });
7106
7107 let mut health_sum = 0.0;
7108 world.iter_health(|_entity, health| {
7109 health_sum += health.value;
7110 });
7111 assert_eq!(health_sum, 110.0);
7112
7113 let e3 = world.spawn_entities(PARENT | NODE, 1)[0];
7114 world.set_parent(e3, Parent(e1));
7115 world.set_node(
7116 e3,
7117 Node {
7118 id: e3,
7119 parent: Some(e1),
7120 children: Vec::new(),
7121 },
7122 );
7123
7124 let mut parent_count = 0;
7125 world.iter_parent(|_entity, _parent| {
7126 parent_count += 1;
7127 });
7128 assert_eq!(parent_count, 1);
7129
7130 let mut node_count = 0;
7131 world.iter_node(|_entity, _node| {
7132 node_count += 1;
7133 });
7134 assert_eq!(node_count, 1);
7135
7136 world.iter_parent_mut(|_entity, parent| {
7137 parent.0 = e2;
7138 });
7139
7140 world.iter_node_mut(|_entity, _node| {});
7141 }
7142
7143 #[test]
7144 fn test_schedule_basic() {
7145 let mut world = World::default();
7146 world.resources._delta_time = 0.016;
7147
7148 let mut schedule = Schedule::new();
7149 schedule.push("tick", |world: &mut World| {
7150 world.resources._delta_time += 0.016;
7151 });
7152
7153 schedule.run(&mut world);
7154 assert_eq!(world.resources._delta_time, 0.032);
7155
7156 schedule.run(&mut world);
7157 assert_eq!(world.resources._delta_time, 0.048);
7158 }
7159
7160 #[test]
7161 fn test_schedule_multiple_systems() {
7162 let mut world = World::default();
7163 let entity = world.spawn_entities(POSITION | VELOCITY, 1)[0];
7164 world.set_position(entity, Position { x: 0.0, y: 0.0 });
7165 world.set_velocity(entity, Velocity { x: 10.0, y: 5.0 });
7166 world.resources._delta_time = 0.1;
7167
7168 let mut schedule = Schedule::new();
7169
7170 schedule.push("physics", |world: &mut World| {
7171 let dt = world.resources._delta_time;
7172 let updates: Vec<(Entity, Velocity)> = world
7173 .query_entities(POSITION | VELOCITY)
7174 .filter_map(|entity| world.get_velocity(entity).map(|vel| (entity, vel.clone())))
7175 .collect();
7176
7177 for (entity, vel) in updates {
7178 if let Some(pos) = world.get_position_mut(entity) {
7179 pos.x += vel.x * dt;
7180 pos.y += vel.y * dt;
7181 }
7182 }
7183 });
7184
7185 schedule.push("double_dt", |world: &mut World| {
7186 world.resources._delta_time *= 2.0;
7187 });
7188
7189 schedule.run(&mut world);
7190
7191 let pos = world.get_position(entity).unwrap();
7192 assert_eq!(pos.x, 1.0);
7193 assert_eq!(pos.y, 0.5);
7194 assert_eq!(world.resources._delta_time, 0.2);
7195 }
7196
7197 #[test]
7198 fn test_schedule_builder_pattern() {
7199 let mut world = World::default();
7200 world.resources._delta_time = 1.0;
7201
7202 let mut schedule = Schedule::new();
7203 schedule
7204 .push("add", |world: &mut World| {
7205 world.resources._delta_time += 1.0;
7206 })
7207 .push("multiply", |world: &mut World| {
7208 world.resources._delta_time *= 2.0;
7209 });
7210
7211 schedule.run(&mut world);
7212 assert_eq!(world.resources._delta_time, 4.0);
7213 }
7214
7215 #[test]
7216 fn test_schedule_system_order() {
7217 let mut world = World::default();
7218 let entity = world.spawn_entities(POSITION, 1)[0];
7219 world.set_position(entity, Position { x: 0.0, y: 0.0 });
7220
7221 let mut schedule = Schedule::new();
7222
7223 schedule.push("set_x", |world: &mut World| {
7224 let entities: Vec<Entity> = world.query_entities(POSITION).collect();
7225 if let Some(pos) = world.get_position_mut(entities[0]) {
7226 pos.x = 10.0;
7227 }
7228 });
7229
7230 schedule.push("double_x", |world: &mut World| {
7231 let entities: Vec<Entity> = world.query_entities(POSITION).collect();
7232 if let Some(pos) = world.get_position_mut(entities[0]) {
7233 pos.x *= 2.0;
7234 }
7235 });
7236
7237 schedule.run(&mut world);
7238
7239 let pos = world.get_position(entity).unwrap();
7240 assert_eq!(pos.x, 20.0);
7241 }
7242
7243 #[test]
7244 fn test_schedule_with_components() {
7245 let mut world = World::default();
7246 let entity = world.spawn_entities(HEALTH, 1)[0];
7247 world.set_health(entity, Health { value: 100.0 });
7248
7249 let mut schedule = Schedule::new();
7250
7251 schedule.push("damage", |world: &mut World| {
7252 let entities: Vec<Entity> = world.query_entities(HEALTH).collect();
7253 for entity in entities {
7254 if let Some(health) = world.get_health_mut(entity) {
7255 health.value -= 10.0;
7256 }
7257 }
7258 });
7259
7260 schedule.push("decay", |world: &mut World| {
7261 let entities: Vec<Entity> = world.query_entities(HEALTH).collect();
7262 for entity in entities {
7263 if let Some(health) = world.get_health_mut(entity) {
7264 health.value *= 0.9;
7265 }
7266 }
7267 });
7268
7269 schedule.run(&mut world);
7270
7271 let health = world.get_health(entity).unwrap();
7272 assert_eq!(health.value, 81.0);
7273 }
7274
7275 #[test]
7276 fn test_schedule_insert_before() {
7277 let mut world = World::default();
7278 world.resources._delta_time = 1.0;
7279
7280 let mut schedule = Schedule::new();
7281 schedule.push("first", |world: &mut World| {
7282 world.resources._delta_time += 10.0;
7283 });
7284 schedule.push("third", |world: &mut World| {
7285 world.resources._delta_time *= 3.0;
7286 });
7287 schedule.insert_before("third", "second", |world: &mut World| {
7288 world.resources._delta_time *= 2.0;
7289 });
7290
7291 schedule.run(&mut world);
7292 assert_eq!(world.resources._delta_time, 66.0);
7293 }
7294
7295 #[test]
7296 fn test_schedule_insert_after() {
7297 let mut world = World::default();
7298 world.resources._delta_time = 1.0;
7299
7300 let mut schedule = Schedule::new();
7301 schedule.push("first", |world: &mut World| {
7302 world.resources._delta_time += 10.0;
7303 });
7304 schedule.push("third", |world: &mut World| {
7305 world.resources._delta_time *= 3.0;
7306 });
7307 schedule.insert_after("first", "second", |world: &mut World| {
7308 world.resources._delta_time *= 2.0;
7309 });
7310
7311 schedule.run(&mut world);
7312 assert_eq!(world.resources._delta_time, 66.0);
7313 }
7314
7315 #[test]
7316 fn test_schedule_remove() {
7317 let mut world = World::default();
7318 world.resources._delta_time = 1.0;
7319
7320 let mut schedule = Schedule::new();
7321 schedule.push("add", |world: &mut World| {
7322 world.resources._delta_time += 10.0;
7323 });
7324 schedule.push("multiply", |world: &mut World| {
7325 world.resources._delta_time *= 5.0;
7326 });
7327
7328 schedule.remove("multiply");
7329 schedule.run(&mut world);
7330 assert_eq!(world.resources._delta_time, 11.0);
7331 }
7332
7333 #[test]
7334 fn test_schedule_remove_nonexistent() {
7335 let mut schedule: Schedule<World> = Schedule::new();
7336 schedule.push("a", |_world: &mut World| {});
7337 schedule.remove("nonexistent");
7338 assert!(schedule.contains("a"));
7339 }
7340
7341 #[test]
7342 fn test_schedule_contains() {
7343 let mut schedule: Schedule<World> = Schedule::new();
7344 assert!(!schedule.contains("physics"));
7345
7346 schedule.push("physics", |_world: &mut World| {});
7347 assert!(schedule.contains("physics"));
7348 assert!(!schedule.contains("render"));
7349
7350 schedule.remove("physics");
7351 assert!(!schedule.contains("physics"));
7352 }
7353
7354 #[test]
7355 #[should_panic(expected = "system \"nonexistent\" not found")]
7356 fn test_schedule_insert_before_panics() {
7357 let mut schedule: Schedule<World> = Schedule::new();
7358 schedule.insert_before("nonexistent", "new", |_world: &mut World| {});
7359 }
7360
7361 #[test]
7362 #[should_panic(expected = "system \"nonexistent\" not found")]
7363 fn test_schedule_insert_after_panics() {
7364 let mut schedule: Schedule<World> = Schedule::new();
7365 schedule.insert_after("nonexistent", "new", |_world: &mut World| {});
7366 }
7367
7368 #[test]
7369 fn test_schedule_ordering_verification() {
7370 let order = std::sync::Arc::new(std::sync::Mutex::new(Vec::<&'static str>::new()));
7371
7372 let mut schedule: Schedule<World> = Schedule::new();
7373 let order_clone = order.clone();
7374 schedule.push("a", move |_world: &mut World| {
7375 order_clone.lock().unwrap().push("a");
7376 });
7377 let order_clone = order.clone();
7378 schedule.push("c", move |_world: &mut World| {
7379 order_clone.lock().unwrap().push("c");
7380 });
7381 let order_clone = order.clone();
7382 schedule.insert_before("c", "b", move |_world: &mut World| {
7383 order_clone.lock().unwrap().push("b");
7384 });
7385 let order_clone = order.clone();
7386 schedule.insert_after("c", "d", move |_world: &mut World| {
7387 order_clone.lock().unwrap().push("d");
7388 });
7389
7390 let mut world = World::default();
7391 schedule.run(&mut world);
7392 assert_eq!(*order.lock().unwrap(), vec!["a", "b", "c", "d"]);
7393 }
7394
7395 #[test]
7396 fn test_schedule_readonly_wrapper() {
7397 let mut world = World::default();
7398 world.resources._delta_time = 42.0;
7399
7400 fn read_system(world: &World) -> f32 {
7401 world.resources._delta_time
7402 }
7403
7404 let observed = std::sync::Arc::new(std::sync::Mutex::new(0.0_f32));
7405 let observed_clone = observed.clone();
7406
7407 let mut schedule = Schedule::new();
7408 schedule.push("reader", move |w: &mut World| {
7409 *observed_clone.lock().unwrap() = read_system(w);
7410 });
7411
7412 schedule.run(&mut world);
7413 assert_eq!(*observed.lock().unwrap(), 42.0);
7414 }
7415
7416 #[test]
7417 #[should_panic(expected = "already exists")]
7418 fn test_schedule_push_duplicate_panics() {
7419 let mut schedule: Schedule<World> = Schedule::new();
7420 schedule.push("a", |_w: &mut World| {});
7421 schedule.push("a", |_w: &mut World| {});
7422 }
7423
7424 #[test]
7425 #[should_panic(expected = "already exists")]
7426 fn test_schedule_insert_before_duplicate_panics() {
7427 let mut schedule: Schedule<World> = Schedule::new();
7428 schedule.push("a", |_w: &mut World| {});
7429 schedule.push("b", |_w: &mut World| {});
7430 schedule.insert_before("b", "a", |_w: &mut World| {});
7431 }
7432
7433 #[test]
7434 #[should_panic(expected = "already exists")]
7435 fn test_schedule_insert_after_duplicate_panics() {
7436 let mut schedule: Schedule<World> = Schedule::new();
7437 schedule.push("a", |_w: &mut World| {});
7438 schedule.insert_after("a", "a", |_w: &mut World| {});
7439 }
7440
7441 #[test]
7442 fn test_schedule_replace() {
7443 let mut world = World::default();
7444 world.resources._delta_time = 1.0;
7445
7446 let mut schedule = Schedule::new();
7447 schedule.push("add", |world: &mut World| {
7448 world.resources._delta_time += 10.0;
7449 });
7450
7451 schedule.run(&mut world);
7452 assert_eq!(world.resources._delta_time, 11.0);
7453
7454 schedule.replace("add", |world: &mut World| {
7455 world.resources._delta_time += 100.0;
7456 });
7457
7458 schedule.run(&mut world);
7459 assert_eq!(world.resources._delta_time, 111.0);
7460 }
7461
7462 #[test]
7463 #[should_panic(expected = "system \"nonexistent\" not found")]
7464 fn test_schedule_replace_panics_on_missing() {
7465 let mut schedule: Schedule<World> = Schedule::new();
7466 schedule.replace("nonexistent", |_w: &mut World| {});
7467 }
7468
7469 #[test]
7470 fn test_schedule_replace_preserves_order() {
7471 let mut world = World::default();
7472 world.resources._delta_time = 0.0;
7473
7474 let mut schedule = Schedule::new();
7475 schedule.push("first", |world: &mut World| {
7476 world.resources._delta_time += 1.0;
7477 });
7478 schedule.push("second", |world: &mut World| {
7479 world.resources._delta_time *= 10.0;
7480 });
7481 schedule.push("third", |world: &mut World| {
7482 world.resources._delta_time += 5.0;
7483 });
7484
7485 schedule.replace("second", |world: &mut World| {
7486 world.resources._delta_time *= 100.0;
7487 });
7488
7489 schedule.run(&mut world);
7490 assert_eq!(world.resources._delta_time, 105.0);
7491 }
7492
7493 #[test]
7494 fn test_schedule_names() {
7495 let mut schedule: Schedule<World> = Schedule::new();
7496 schedule.push("a", |_w: &mut World| {});
7497 schedule.push("b", |_w: &mut World| {});
7498 schedule.push("c", |_w: &mut World| {});
7499
7500 let names: Vec<&str> = schedule.names().collect();
7501 assert_eq!(names, vec!["a", "b", "c"]);
7502 }
7503
7504 #[test]
7505 fn test_schedule_len_and_is_empty() {
7506 let mut schedule: Schedule<World> = Schedule::new();
7507 assert!(schedule.is_empty());
7508 assert_eq!(schedule.len(), 0);
7509
7510 schedule.push("a", |_w: &mut World| {});
7511 assert!(!schedule.is_empty());
7512 assert_eq!(schedule.len(), 1);
7513
7514 schedule.push("b", |_w: &mut World| {});
7515 assert_eq!(schedule.len(), 2);
7516
7517 schedule.remove("a");
7518 assert_eq!(schedule.len(), 1);
7519
7520 schedule.remove("b");
7521 assert!(schedule.is_empty());
7522 }
7523
7524 #[test]
7525 fn test_schedule_remove_returns_bool() {
7526 let mut schedule: Schedule<World> = Schedule::new();
7527 schedule.push("a", |_w: &mut World| {});
7528
7529 assert!(schedule.remove("a"));
7530 assert!(!schedule.remove("a"));
7531 assert!(!schedule.remove("nonexistent"));
7532 }
7533
7534 #[test]
7535 fn test_schedule_remove_then_push_reuses_name() {
7536 let mut world = World::default();
7537 world.resources._delta_time = 0.0;
7538
7539 let mut schedule = Schedule::new();
7540 schedule.push("sys", |world: &mut World| {
7541 world.resources._delta_time += 1.0;
7542 });
7543
7544 schedule.remove("sys");
7545 schedule.push("sys", |world: &mut World| {
7546 world.resources._delta_time += 100.0;
7547 });
7548
7549 schedule.run(&mut world);
7550 assert_eq!(world.resources._delta_time, 100.0);
7551 }
7552
7553 #[test]
7554 fn test_schedule_insert_before_first() {
7555 let mut schedule: Schedule<World> = Schedule::new();
7556 schedule.push("b", |_w: &mut World| {});
7557 schedule.insert_before("b", "a", |_w: &mut World| {});
7558
7559 let names: Vec<&str> = schedule.names().collect();
7560 assert_eq!(names, vec!["a", "b"]);
7561 }
7562
7563 #[test]
7564 fn test_schedule_insert_after_last() {
7565 let mut schedule: Schedule<World> = Schedule::new();
7566 schedule.push("a", |_w: &mut World| {});
7567 schedule.insert_after("a", "b", |_w: &mut World| {});
7568
7569 let names: Vec<&str> = schedule.names().collect();
7570 assert_eq!(names, vec!["a", "b"]);
7571 }
7572
7573 #[test]
7574 fn test_schedule_push_readonly() {
7575 let mut world = World::default();
7576 world.resources._delta_time = 42.0;
7577
7578 let observed = std::sync::Arc::new(std::sync::Mutex::new(0.0_f32));
7579 let obs = observed.clone();
7580
7581 let mut schedule = Schedule::new();
7582 schedule.push_readonly("reader", move |world: &World| {
7583 *obs.lock().unwrap() = world.resources._delta_time;
7584 });
7585
7586 schedule.run(&mut world);
7587 assert_eq!(*observed.lock().unwrap(), 42.0);
7588 }
7589
7590 #[test]
7591 fn test_query_cache_persistence_on_new_archetype() {
7592 let mut world = World::default();
7593
7594 world.spawn_entities(POSITION, 5);
7595
7596 let mut count1 = 0;
7597 world.for_each_mut(POSITION, 0, |_entity, _table, _idx| {
7598 count1 += 1;
7599 });
7600 assert_eq!(count1, 5);
7601
7602 let cache_size_before = world.query_cache.len();
7603 assert_eq!(cache_size_before, 1);
7604
7605 world.spawn_entities(POSITION | VELOCITY, 3);
7606
7607 let cache_size_after = world.query_cache.len();
7608 assert_eq!(cache_size_after, 1);
7609
7610 let mut count2 = 0;
7611 world.for_each_mut(POSITION, 0, |_entity, _table, _idx| {
7612 count2 += 1;
7613 });
7614 assert_eq!(count2, 8);
7615
7616 world.spawn_entities(POSITION | HEALTH, 2);
7617
7618 let mut count3 = 0;
7619 world.for_each_mut(POSITION, 0, |_entity, _table, _idx| {
7620 count3 += 1;
7621 });
7622 assert_eq!(count3, 10);
7623 }
7624
7625 #[test]
7626 fn test_multi_component_add_performance() {
7627 let mut world = World::default();
7628 let entity = world.spawn_entities(POSITION, 1)[0];
7629 let source_table_index =
7630 world.entity_locations.get(entity.id).unwrap().table_index as usize;
7631
7632 world.add_components(entity, VELOCITY | HEALTH);
7633
7634 assert!(world.get_position(entity).is_some());
7635 assert!(world.get_velocity(entity).is_some());
7636 assert!(world.get_health(entity).is_some());
7637
7638 let cache_entry_in_source = world.table_edges[source_table_index]
7639 .multi_add_cache
7640 .get(&(VELOCITY | HEALTH))
7641 .copied();
7642 assert!(cache_entry_in_source.is_some());
7643
7644 let entity2 = world.spawn_entities(POSITION, 1)[0];
7645 let source_table_index2 =
7646 world.entity_locations.get(entity2.id).unwrap().table_index as usize;
7647
7648 assert_eq!(source_table_index, source_table_index2);
7649
7650 world.add_components(entity2, VELOCITY | HEALTH);
7651
7652 let cache_hit = world.table_edges[source_table_index2]
7653 .multi_add_cache
7654 .get(&(VELOCITY | HEALTH))
7655 .copied();
7656 assert_eq!(cache_hit, cache_entry_in_source);
7657 }
7658
7659 #[test]
7660 fn test_multi_component_remove_performance() {
7661 let mut world = World::default();
7662 let entity = world.spawn_entities(POSITION | VELOCITY | HEALTH, 1)[0];
7663 let source_table_index =
7664 world.entity_locations.get(entity.id).unwrap().table_index as usize;
7665
7666 world.remove_components(entity, VELOCITY | HEALTH);
7667
7668 assert!(world.get_position(entity).is_some());
7669 assert!(world.get_velocity(entity).is_none());
7670 assert!(world.get_health(entity).is_none());
7671
7672 let cache_entry_in_source = world.table_edges[source_table_index]
7673 .multi_remove_cache
7674 .get(&(VELOCITY | HEALTH))
7675 .copied();
7676 assert!(cache_entry_in_source.is_some());
7677
7678 let entity2 = world.spawn_entities(POSITION | VELOCITY | HEALTH, 1)[0];
7679 let source_table_index2 =
7680 world.entity_locations.get(entity2.id).unwrap().table_index as usize;
7681
7682 assert_eq!(source_table_index, source_table_index2);
7683
7684 world.remove_components(entity2, VELOCITY | HEALTH);
7685
7686 let cache_hit = world.table_edges[source_table_index2]
7687 .multi_remove_cache
7688 .get(&(VELOCITY | HEALTH))
7689 .copied();
7690 assert_eq!(cache_hit, cache_entry_in_source);
7691 }
7692
7693 #[test]
7694 fn test_query_cache_invalidation_selective() {
7695 let mut world = World::default();
7696
7697 world.spawn_entities(POSITION, 10);
7698 world.spawn_entities(VELOCITY, 5);
7699
7700 let mut pos_count1 = 0;
7701 world.for_each_mut(POSITION, 0, |_entity, _table, _idx| {
7702 pos_count1 += 1;
7703 });
7704 let mut vel_count1 = 0;
7705 world.for_each_mut(VELOCITY, 0, |_entity, _table, _idx| {
7706 vel_count1 += 1;
7707 });
7708 assert_eq!(pos_count1, 10);
7709 assert_eq!(vel_count1, 5);
7710
7711 let cache_size_before = world.query_cache.len();
7712 assert_eq!(cache_size_before, 2);
7713
7714 world.spawn_entities(POSITION | VELOCITY, 3);
7715
7716 let cache_size_after_archetype = world.query_cache.len();
7717 assert_eq!(cache_size_after_archetype, 2);
7718
7719 let mut pos_count2 = 0;
7720 world.for_each_mut(POSITION, 0, |_entity, _table, _idx| {
7721 pos_count2 += 1;
7722 });
7723 let mut vel_count2 = 0;
7724 world.for_each_mut(VELOCITY, 0, |_entity, _table, _idx| {
7725 vel_count2 += 1;
7726 });
7727 assert_eq!(pos_count2, 13);
7728 assert_eq!(vel_count2, 8);
7729
7730 world.spawn_entities(HEALTH, 2);
7731
7732 let cache_size_after_health = world.query_cache.len();
7733 assert_eq!(cache_size_after_health, 2);
7734
7735 let mut pos_count3 = 0;
7736 world.for_each_mut(POSITION, 0, |_entity, _table, _idx| {
7737 pos_count3 += 1;
7738 });
7739 let mut vel_count3 = 0;
7740 world.for_each_mut(VELOCITY, 0, |_entity, _table, _idx| {
7741 vel_count3 += 1;
7742 });
7743 let mut health_count = 0;
7744 world.for_each_mut(HEALTH, 0, |_entity, _table, _idx| {
7745 health_count += 1;
7746 });
7747 assert_eq!(pos_count3, 13);
7748 assert_eq!(vel_count3, 8);
7749 assert_eq!(health_count, 2);
7750
7751 let final_cache_size = world.query_cache.len();
7752 assert_eq!(final_cache_size, 3);
7753 }
7754
7755 #[test]
7756 fn test_entity_has_components_requires_all() {
7757 let mut world = World::default();
7758 let entity = world.spawn_entities(POSITION, 1)[0];
7759
7760 assert!(
7761 !world.entity_has_components(entity, POSITION | VELOCITY),
7762 "Should return false when entity only has POSITION but query asks for POSITION | VELOCITY"
7763 );
7764
7765 assert!(
7766 world.entity_has_components(entity, POSITION),
7767 "Should return true when entity has the single queried component"
7768 );
7769
7770 world.add_components(entity, VELOCITY);
7771 assert!(
7772 world.entity_has_components(entity, POSITION | VELOCITY),
7773 "Should return true when entity has all queried components"
7774 );
7775
7776 assert!(
7777 !world.entity_has_components(entity, POSITION | VELOCITY | HEALTH),
7778 "Should return false when entity is missing one of three queried components"
7779 );
7780 }
7781
7782 #[test]
7783 fn test_entity_has_generated_methods_consistency() {
7784 let mut world = World::default();
7785 let entity = world.spawn_entities(POSITION | VELOCITY, 1)[0];
7786
7787 assert!(world.entity_has_position(entity));
7788 assert!(world.entity_has_velocity(entity));
7789 assert!(!world.entity_has_health(entity));
7790
7791 assert!(world.entity_has_components(entity, POSITION));
7792 assert!(world.entity_has_components(entity, VELOCITY));
7793 assert!(world.entity_has_components(entity, POSITION | VELOCITY));
7794 assert!(!world.entity_has_components(entity, HEALTH));
7795 assert!(!world.entity_has_components(entity, POSITION | HEALTH));
7796 }
7797
7798 #[test]
7799 fn test_despawn_preserves_change_vec_consistency() {
7800 let mut world = World::default();
7801 let entities = world.spawn_entities(POSITION, 5);
7802
7803 world.step();
7804
7805 world.get_position_mut(entities[0]).unwrap().x = 10.0;
7806 world.get_position_mut(entities[4]).unwrap().x = 40.0;
7807
7808 world.despawn_entities(&[entities[2]]);
7809
7810 for table in &world.tables {
7811 if table.mask & POSITION != 0 {
7812 let entity_count = table.entity_indices.len();
7813 assert_eq!(
7814 table.position.len(),
7815 entity_count,
7816 "Position vec length should match entity count after despawn"
7817 );
7818 assert_eq!(
7819 table.position_changed.len(),
7820 entity_count,
7821 "Position changed vec length should match entity count after despawn"
7822 );
7823 }
7824 }
7825 }
7826
7827 #[test]
7828 fn test_change_detection_after_despawn() {
7829 let mut world = World::default();
7830 let e1 = world.spawn_entities(POSITION, 1)[0];
7831 let e2 = world.spawn_entities(POSITION, 1)[0];
7832 let e3 = world.spawn_entities(POSITION, 1)[0];
7833
7834 world.set_position(e1, Position { x: 1.0, y: 0.0 });
7835 world.set_position(e2, Position { x: 2.0, y: 0.0 });
7836 world.set_position(e3, Position { x: 3.0, y: 0.0 });
7837
7838 world.step();
7839
7840 world.get_position_mut(e3).unwrap().x = 30.0;
7841
7842 world.despawn_entities(&[e2]);
7843
7844 let mut changed_entities = Vec::new();
7845 world.for_each_mut_changed(POSITION, 0, |entity, _table, _idx| {
7846 changed_entities.push(entity);
7847 });
7848
7849 assert!(
7850 changed_entities.contains(&e3),
7851 "e3 was modified and should be detected as changed"
7852 );
7853 assert!(
7854 !changed_entities.contains(&e1),
7855 "e1 was not modified and should not be detected as changed"
7856 );
7857 }
7858
7859 #[test]
7860 fn test_change_detection_only_checks_queried_components() {
7861 let mut world = World::default();
7862 let entity = world.spawn_entities(POSITION | VELOCITY, 1)[0];
7863
7864 world.step();
7865
7866 world.get_velocity_mut(entity).unwrap().x = 99.0;
7867
7868 let mut pos_changed_count = 0;
7869 world.for_each_mut_changed(POSITION, 0, |_entity, _table, _idx| {
7870 pos_changed_count += 1;
7871 });
7872 assert_eq!(
7873 pos_changed_count, 0,
7874 "Changing velocity should not trigger position change detection"
7875 );
7876
7877 let mut vel_changed_count = 0;
7878 world.for_each_mut_changed(VELOCITY, 0, |_entity, _table, _idx| {
7879 vel_changed_count += 1;
7880 });
7881 assert_eq!(
7882 vel_changed_count, 1,
7883 "Changing velocity should trigger velocity change detection"
7884 );
7885 }
7886
7887 #[test]
7888 fn test_change_detection_multi_component_query_only_relevant() {
7889 let mut world = World::default();
7890 let e1 = world.spawn_entities(POSITION | VELOCITY | HEALTH, 1)[0];
7891 let e2 = world.spawn_entities(POSITION | VELOCITY | HEALTH, 1)[0];
7892
7893 world.step();
7894
7895 world.get_position_mut(e1).unwrap().x = 5.0;
7896 world.get_health_mut(e2).unwrap().value = 50.0;
7897
7898 let mut changed_entities = Vec::new();
7899 world.for_each_mut_changed(POSITION | VELOCITY, 0, |entity, _table, _idx| {
7900 changed_entities.push(entity);
7901 });
7902
7903 assert!(
7904 changed_entities.contains(&e1),
7905 "e1 had position changed, which is in the query"
7906 );
7907 assert!(
7908 !changed_entities.contains(&e2),
7909 "e2 only had health changed, which is NOT in the query"
7910 );
7911 }
7912
7913 #[test]
7914 fn test_entity_count() {
7915 let mut world = World::default();
7916 assert_eq!(world.entity_count(), 0);
7917
7918 world.spawn_entities(POSITION, 5);
7919 assert_eq!(world.entity_count(), 5);
7920
7921 world.spawn_entities(VELOCITY, 3);
7922 assert_eq!(world.entity_count(), 8);
7923
7924 let entities = world.spawn_entities(POSITION | VELOCITY, 2);
7925 assert_eq!(world.entity_count(), 10);
7926
7927 world.despawn_entities(&[entities[0]]);
7928 assert_eq!(world.entity_count(), 9);
7929 }
7930
7931 #[test]
7932 fn test_entity_count_matches_get_all_entities() {
7933 let mut world = World::default();
7934
7935 world.spawn_entities(POSITION, 10);
7936 world.spawn_entities(VELOCITY, 5);
7937 world.spawn_entities(POSITION | VELOCITY | HEALTH, 3);
7938
7939 assert_eq!(world.entity_count(), world.get_all_entities().len());
7940 }
7941
7942 #[test]
7943 fn test_entity_query_iter_size_hint() {
7944 let mut world = World::default();
7945
7946 world.spawn_entities(POSITION | VELOCITY, 5);
7947 world.spawn_entities(POSITION, 3);
7948 world.spawn_entities(VELOCITY | HEALTH, 2);
7949
7950 let iter = world.query_entities(POSITION);
7951 let (lower, upper) = iter.size_hint();
7952 assert_eq!(lower, 8);
7953 assert_eq!(upper, Some(8));
7954
7955 let count = world.query_entities(POSITION).count();
7956 assert_eq!(count, 8);
7957
7958 let iter = world.query_entities(POSITION | VELOCITY);
7959 let (lower, upper) = iter.size_hint();
7960 assert_eq!(lower, 5);
7961 assert_eq!(upper, Some(5));
7962 }
7963
7964 #[test]
7965 fn test_entity_query_iter_size_hint_decreases() {
7966 let mut world = World::default();
7967 world.spawn_entities(POSITION, 3);
7968
7969 let mut iter = world.query_entities(POSITION);
7970 assert_eq!(iter.size_hint(), (3, Some(3)));
7971
7972 iter.next();
7973 assert_eq!(iter.size_hint(), (2, Some(2)));
7974
7975 iter.next();
7976 assert_eq!(iter.size_hint(), (1, Some(1)));
7977
7978 iter.next();
7979 assert_eq!(iter.size_hint(), (0, Some(0)));
7980 }
7981
7982 #[test]
7983 fn test_component_query_iter_size_hint() {
7984 let mut world = World::default();
7985 world.spawn_entities(POSITION, 4);
7986 world.spawn_entities(POSITION | VELOCITY, 3);
7987
7988 let iter = world.query_position();
7989 let (lower, upper) = iter.size_hint();
7990 assert_eq!(lower, 7);
7991 assert_eq!(upper, Some(7));
7992
7993 let count = world.query_position().count();
7994 assert_eq!(count, 7);
7995 }
7996
7997 #[test]
7998 fn test_query_entities_collect_preallocates() {
7999 let mut world = World::default();
8000 world.spawn_entities(POSITION | VELOCITY, 100);
8001
8002 let entities: Vec<Entity> = world.query_entities(POSITION | VELOCITY).collect();
8003 assert_eq!(entities.len(), 100);
8004 }
8005
8006 #[test]
8007 fn test_despawn_multiple_same_table_change_vecs() {
8008 let mut world = World::default();
8009 let entities = world.spawn_entities(POSITION | VELOCITY, 6);
8010
8011 world.step();
8012
8013 world.get_position_mut(entities[0]).unwrap().x = 10.0;
8014 world.get_position_mut(entities[5]).unwrap().x = 50.0;
8015
8016 world.despawn_entities(&[entities[1], entities[3]]);
8017
8018 for table in &world.tables {
8019 if table.mask & POSITION != 0 {
8020 let entity_count = table.entity_indices.len();
8021 assert_eq!(table.position.len(), entity_count);
8022 assert_eq!(table.position_changed.len(), entity_count);
8023 }
8024 if table.mask & VELOCITY != 0 {
8025 let entity_count = table.entity_indices.len();
8026 assert_eq!(table.velocity.len(), entity_count);
8027 assert_eq!(table.velocity_changed.len(), entity_count);
8028 }
8029 }
8030
8031 let remaining = world.get_all_entities();
8032 assert_eq!(remaining.len(), 4);
8033 assert!(world.get_position(entities[0]).is_some());
8034 assert!(world.get_position(entities[5]).is_some());
8035 }
8036
8037 mod cfg_test {
8038 #[derive(Default, Debug, Clone)]
8039 pub struct BaseComponent {
8040 pub value: i32,
8041 }
8042
8043 #[derive(Default, Debug, Clone)]
8044 pub struct DebugComponent {
8045 pub debug_value: i32,
8046 }
8047
8048 crate::ecs! {
8049 CfgWorld {
8050 base: BaseComponent => BASE,
8051 #[cfg(debug_assertions)]
8052 debug_only: DebugComponent => DEBUG_ONLY,
8053 }
8054 CfgResources {
8055 counter: i32,
8056 }
8057 }
8058
8059 #[test]
8060 fn test_cfg_attribute_on_components() {
8061 let mut world = CfgWorld::default();
8062
8063 let entities = world.spawn_entities(BASE, 3);
8064 assert_eq!(entities.len(), 3);
8065
8066 world.set_base(entities[0], BaseComponent { value: 10 });
8067 assert_eq!(world.get_base(entities[0]).unwrap().value, 10);
8068
8069 world.resources.counter = 42;
8070 assert_eq!(world.resources.counter, 42);
8071
8072 let mut count = 0;
8073 world.query().with(BASE).iter(|_entity, _table, _idx| {
8074 count += 1;
8075 });
8076 assert_eq!(count, 3);
8077
8078 world.query_mut().with(BASE).iter(|_entity, table, idx| {
8079 table.base[idx].value += 1;
8080 });
8081 assert_eq!(world.get_base(entities[0]).unwrap().value, 11);
8082
8083 let mut without_count = 0;
8084 world
8085 .query()
8086 .with(BASE)
8087 .without(0)
8088 .iter(|_entity, _table, _idx| {
8089 without_count += 1;
8090 });
8091 assert_eq!(without_count, 3);
8092
8093 let mut without_mut_count = 0;
8094 world
8095 .query_mut()
8096 .with(BASE)
8097 .without(0)
8098 .iter(|_entity, _table, _idx| {
8099 without_mut_count += 1;
8100 });
8101 assert_eq!(without_mut_count, 3);
8102
8103 let mut iter_count = 0;
8104 world.iter_base(|_entity, _base| {
8105 iter_count += 1;
8106 });
8107 assert_eq!(iter_count, 3);
8108
8109 world.iter_base_mut(|_entity, base| {
8110 base.value *= 2;
8111 });
8112 assert_eq!(world.get_base(entities[0]).unwrap().value, 22);
8113
8114 #[cfg(debug_assertions)]
8115 {
8116 world.set_debug_only(entities[0], DebugComponent { debug_value: 42 });
8117 assert_eq!(world.get_debug_only(entities[0]).unwrap().debug_value, 42);
8118
8119 let debug_entities = world.spawn_entities(DEBUG_ONLY, 2);
8120 assert_eq!(debug_entities.len(), 2);
8121
8122 let mut debug_count = 0;
8123 world.iter_debug_only(|_entity, _debug| {
8124 debug_count += 1;
8125 });
8126 assert_eq!(debug_count, 3);
8127
8128 world.iter_debug_only_mut(|_entity, debug| {
8129 debug.debug_value += 1;
8130 });
8131 assert_eq!(world.get_debug_only(entities[0]).unwrap().debug_value, 43);
8132 }
8133 }
8134 }
8135
8136 mod multi_world_test {
8137 use super::*;
8138
8139 #[derive(Default, Debug, Clone, PartialEq)]
8140 pub struct Position {
8141 pub x: f32,
8142 pub y: f32,
8143 }
8144
8145 #[derive(Default, Debug, Clone, PartialEq)]
8146 pub struct Velocity {
8147 pub x: f32,
8148 pub y: f32,
8149 }
8150
8151 #[derive(Default, Debug, Clone, PartialEq)]
8152 pub struct Sprite {
8153 pub id: u32,
8154 }
8155
8156 #[derive(Default, Debug, Clone, PartialEq)]
8157 pub struct Color {
8158 pub r: f32,
8159 pub g: f32,
8160 pub b: f32,
8161 }
8162
8163 #[derive(Debug, Clone)]
8164 #[allow(dead_code)]
8165 pub struct CollisionEvent {
8166 pub entity_a: Entity,
8167 pub entity_b: Entity,
8168 }
8169
8170 crate::ecs! {
8171 GameEcs {
8172 CoreWorld {
8173 position: Position => MW_POSITION,
8174 velocity: Velocity => MW_VELOCITY,
8175 }
8176 RenderWorld {
8177 sprite: Sprite => MW_SPRITE,
8178 color: Color => MW_COLOR,
8179 }
8180 }
8181 Tags {
8182 player => MW_PLAYER,
8183 }
8184 Events {
8185 collision: CollisionEvent,
8186 }
8187 GameResources {
8188 delta_time: f32,
8189 }
8190 }
8191
8192 #[test]
8193 fn test_multi_world_spawn() {
8194 let mut ecs = GameEcs::default();
8195 let entity = ecs.spawn();
8196 assert_eq!(entity.id, 0);
8197 assert_eq!(entity.generation, 0);
8198 }
8199
8200 #[test]
8201 fn test_multi_world_per_world_components() {
8202 let mut ecs = GameEcs::default();
8203 let entity = ecs.spawn();
8204
8205 ecs.core_world
8206 .add_components(entity, MW_POSITION | MW_VELOCITY);
8207 ecs.core_world
8208 .set_position(entity, Position { x: 1.0, y: 2.0 });
8209
8210 assert_eq!(ecs.core_world.get_position(entity).unwrap().x, 1.0);
8211 assert_eq!(ecs.core_world.get_position(entity).unwrap().y, 2.0);
8212 assert!(ecs.core_world.get_velocity(entity).is_some());
8213 }
8214
8215 #[test]
8216 fn test_multi_world_cross_world_access() {
8217 let mut ecs = GameEcs::default();
8218 let entity = ecs.spawn();
8219
8220 ecs.core_world
8221 .set_position(entity, Position { x: 5.0, y: 10.0 });
8222 ecs.render_world.set_sprite(entity, Sprite { id: 42 });
8223
8224 assert_eq!(ecs.core_world.get_position(entity).unwrap().x, 5.0);
8225 assert_eq!(ecs.render_world.get_sprite(entity).unwrap().id, 42);
8226 }
8227
8228 #[test]
8229 fn test_multi_world_split_borrow() {
8230 let mut ecs = GameEcs::default();
8231 let entity = ecs.spawn();
8232
8233 ecs.core_world
8234 .set_position(entity, Position { x: 1.0, y: 2.0 });
8235 ecs.render_world.set_sprite(entity, Sprite { id: 1 });
8236
8237 let GameEcs {
8238 core_world,
8239 render_world,
8240 ..
8241 } = &mut ecs;
8242 core_world.for_each_mut(MW_POSITION, 0, |entity, table, idx| {
8243 if let Some(sprite) = render_world.get_sprite(entity) {
8244 table.position[idx].x += sprite.id as f32;
8245 }
8246 });
8247
8248 assert_eq!(ecs.core_world.get_position(entity).unwrap().x, 2.0);
8249 }
8250
8251 #[test]
8252 fn test_multi_world_despawn_cascades() {
8253 let mut ecs = GameEcs::default();
8254 let entity = ecs.spawn();
8255
8256 ecs.core_world
8257 .set_position(entity, Position { x: 1.0, y: 2.0 });
8258 ecs.render_world.set_sprite(entity, Sprite { id: 1 });
8259 ecs.add_player(entity);
8260
8261 assert!(ecs.core_world.get_position(entity).is_some());
8262 assert!(ecs.render_world.get_sprite(entity).is_some());
8263 assert!(ecs.has_player(entity));
8264
8265 ecs.despawn(entity);
8266
8267 assert!(ecs.core_world.get_position(entity).is_none());
8268 assert!(ecs.render_world.get_sprite(entity).is_none());
8269 assert!(!ecs.has_player(entity));
8270 }
8271
8272 #[test]
8273 fn test_multi_world_entity_in_one_world_only() {
8274 let mut ecs = GameEcs::default();
8275 let entity = ecs.spawn();
8276
8277 ecs.core_world
8278 .set_position(entity, Position { x: 1.0, y: 2.0 });
8279
8280 assert!(ecs.core_world.get_position(entity).is_some());
8281 assert!(ecs.render_world.get_sprite(entity).is_none());
8282 assert!(ecs.render_world.get_color(entity).is_none());
8283 }
8284
8285 #[test]
8286 fn test_multi_world_entity_in_no_world() {
8287 let mut ecs = GameEcs::default();
8288 let entity = ecs.spawn();
8289
8290 assert!(ecs.core_world.get_position(entity).is_none());
8291 assert!(ecs.render_world.get_sprite(entity).is_none());
8292 }
8293
8294 #[test]
8295 fn test_multi_world_entity_builder() {
8296 let mut ecs = GameEcs::default();
8297 let entities = EntityBuilder::new()
8298 .with_position(Position { x: 1.0, y: 2.0 })
8299 .with_sprite(Sprite { id: 42 })
8300 .spawn(&mut ecs, 2);
8301
8302 assert_eq!(entities.len(), 2);
8303 assert_eq!(ecs.core_world.get_position(entities[0]).unwrap().x, 1.0);
8304 assert_eq!(ecs.core_world.get_position(entities[1]).unwrap().x, 1.0);
8305 assert_eq!(ecs.render_world.get_sprite(entities[0]).unwrap().id, 42);
8306 assert_eq!(ecs.render_world.get_sprite(entities[1]).unwrap().id, 42);
8307 }
8308
8309 #[test]
8310 fn test_multi_world_tags() {
8311 let mut ecs = GameEcs::default();
8312 let entity = ecs.spawn();
8313
8314 ecs.add_player(entity);
8315 assert!(ecs.has_player(entity));
8316
8317 let players: Vec<Entity> = ecs.query_player().collect();
8318 assert_eq!(players.len(), 1);
8319
8320 ecs.remove_player(entity);
8321 assert!(!ecs.has_player(entity));
8322 }
8323
8324 #[test]
8325 fn test_multi_world_tag_split_borrow() {
8326 let mut ecs = GameEcs::default();
8327 let e1 = ecs.spawn();
8328 let e2 = ecs.spawn();
8329
8330 ecs.core_world.set_position(e1, Position { x: 1.0, y: 0.0 });
8331 ecs.core_world.set_position(e2, Position { x: 2.0, y: 0.0 });
8332 ecs.add_player(e1);
8333
8334 let GameEcs {
8335 core_world, player, ..
8336 } = &mut ecs;
8337 let mut player_positions = Vec::new();
8338 core_world.for_each_mut(MW_POSITION, 0, |entity, table, idx| {
8339 if player.contains(entity) {
8340 player_positions.push(table.position[idx].clone());
8341 }
8342 });
8343
8344 assert_eq!(player_positions.len(), 1);
8345 assert_eq!(player_positions[0].x, 1.0);
8346 }
8347
8348 #[test]
8349 fn test_multi_world_events() {
8350 let mut ecs = GameEcs::default();
8351 let e1 = ecs.spawn();
8352 let e2 = ecs.spawn();
8353
8354 ecs.send_collision(CollisionEvent {
8355 entity_a: e1,
8356 entity_b: e2,
8357 });
8358
8359 let events: Vec<_> = ecs.collect_collision();
8360 assert_eq!(events.len(), 1);
8361 assert_eq!(events[0].entity_a, e1);
8362 }
8363
8364 #[test]
8365 fn test_multi_world_resources() {
8366 let mut ecs = GameEcs::default();
8367 ecs.resources.delta_time = 0.016;
8368 assert_eq!(ecs.resources.delta_time, 0.016);
8369 }
8370
8371 #[test]
8372 fn test_multi_world_schedule() {
8373 let mut ecs = GameEcs::default();
8374 let entity = ecs.spawn();
8375 ecs.core_world
8376 .set_position(entity, Position { x: 0.0, y: 0.0 });
8377 ecs.core_world
8378 .set_velocity(entity, Velocity { x: 1.0, y: 2.0 });
8379 ecs.resources.delta_time = 0.1;
8380
8381 let mut schedule: Schedule<GameEcs> = Schedule::new();
8382 schedule.push("physics", |ecs: &mut GameEcs| {
8383 let dt = ecs.resources.delta_time;
8384 ecs.core_world
8385 .for_each_mut(MW_POSITION | MW_VELOCITY, 0, |_entity, table, idx| {
8386 table.position[idx].x += table.velocity[idx].x * dt;
8387 table.position[idx].y += table.velocity[idx].y * dt;
8388 });
8389 });
8390
8391 schedule.run(&mut ecs);
8392
8393 let pos = ecs.core_world.get_position(entity).unwrap();
8394 assert!((pos.x - 0.1).abs() < f32::EPSILON);
8395 assert!((pos.y - 0.2).abs() < f32::EPSILON);
8396 }
8397
8398 #[test]
8399 fn test_multi_world_mask_independence() {
8400 assert_eq!(MW_POSITION, 1 << 0);
8401 assert_eq!(MW_VELOCITY, 1 << 1);
8402 assert_eq!(MW_SPRITE, 1 << 0);
8403 assert_eq!(MW_COLOR, 1 << 1);
8404 }
8405
8406 #[test]
8407 fn test_multi_world_command_buffer() {
8408 let mut ecs = GameEcs::default();
8409 let entity = ecs.spawn();
8410
8411 ecs.core_world
8412 .set_position(entity, Position { x: 0.0, y: 0.0 });
8413
8414 ecs.queue_set_position(entity, Position { x: 10.0, y: 20.0 });
8415 ecs.queue_add_player(entity);
8416
8417 assert_eq!(ecs.core_world.get_position(entity).unwrap().x, 0.0);
8418 assert!(!ecs.has_player(entity));
8419
8420 ecs.apply_commands();
8421
8422 assert_eq!(ecs.core_world.get_position(entity).unwrap().x, 10.0);
8423 assert!(ecs.has_player(entity));
8424 }
8425
8426 #[test]
8427 fn test_multi_world_step() {
8428 let mut ecs = GameEcs::default();
8429
8430 assert_eq!(ecs.core_world.current_tick(), 0);
8431 assert_eq!(ecs.render_world.current_tick(), 0);
8432
8433 ecs.step();
8434
8435 assert_eq!(ecs.core_world.current_tick(), 1);
8436 assert_eq!(ecs.render_world.current_tick(), 1);
8437 }
8438
8439 #[test]
8440 fn test_multi_world_for_each_within_world() {
8441 let mut ecs = GameEcs::default();
8442 let e1 = ecs.spawn();
8443 let e2 = ecs.spawn();
8444
8445 ecs.core_world.set_position(e1, Position { x: 1.0, y: 0.0 });
8446 ecs.core_world.set_position(e2, Position { x: 2.0, y: 0.0 });
8447 ecs.core_world
8448 .set_velocity(e2, Velocity { x: 10.0, y: 0.0 });
8449
8450 let mut count = 0;
8451 ecs.core_world
8452 .for_each(MW_POSITION, 0, |_entity, _table, _idx| {
8453 count += 1;
8454 });
8455 assert_eq!(count, 2);
8456
8457 count = 0;
8458 ecs.core_world
8459 .for_each(MW_POSITION | MW_VELOCITY, 0, |_entity, _table, _idx| {
8460 count += 1;
8461 });
8462 assert_eq!(count, 1);
8463 }
8464
8465 #[test]
8466 fn test_multi_world_query_builder() {
8467 let mut ecs = GameEcs::default();
8468 let e1 = ecs.spawn();
8469 let e2 = ecs.spawn();
8470
8471 ecs.core_world.set_position(e1, Position { x: 1.0, y: 0.0 });
8472 ecs.core_world.set_position(e2, Position { x: 2.0, y: 0.0 });
8473 ecs.core_world
8474 .set_velocity(e2, Velocity { x: 10.0, y: 0.0 });
8475
8476 let mut count = 0;
8477 ecs.core_world
8478 .query()
8479 .with(MW_POSITION)
8480 .without(MW_VELOCITY)
8481 .iter(|_entity, _table, _idx| {
8482 count += 1;
8483 });
8484 assert_eq!(count, 1);
8485 }
8486
8487 #[test]
8488 fn test_multi_world_generational_reuse() {
8489 let mut ecs = GameEcs::default();
8490
8491 let e1 = ecs.spawn();
8492 ecs.core_world.set_position(e1, Position { x: 1.0, y: 0.0 });
8493
8494 ecs.despawn(e1);
8495 assert!(ecs.core_world.get_position(e1).is_none());
8496
8497 let e2 = ecs.spawn();
8498 assert_eq!(e2.id, e1.id);
8499 assert_eq!(e2.generation, e1.generation + 1);
8500
8501 assert!(ecs.core_world.get_position(e1).is_none());
8502 }
8503
8504 #[test]
8505 fn test_multi_world_ghost_entity_guard() {
8506 let mut ecs = GameEcs::default();
8507 let e1 = ecs.spawn();
8508 ecs.core_world.set_position(e1, Position { x: 1.0, y: 0.0 });
8509 ecs.despawn(e1);
8510
8511 let e2 = ecs.spawn();
8512 assert_eq!(e2.id, e1.id);
8513
8514 ecs.core_world.set_position(e2, Position { x: 5.0, y: 5.0 });
8515 assert_eq!(ecs.core_world.get_position(e2).unwrap().x, 5.0);
8516
8517 assert!(ecs.core_world.get_position(e1).is_none());
8518 }
8519
8520 #[test]
8521 fn test_multi_world_for_each_with_tags() {
8522 let mut ecs = GameEcs::default();
8523 let e1 = ecs.spawn();
8524 let e2 = ecs.spawn();
8525 let e3 = ecs.spawn();
8526
8527 ecs.core_world.set_position(e1, Position { x: 1.0, y: 0.0 });
8528 ecs.core_world.set_position(e2, Position { x: 2.0, y: 0.0 });
8529 ecs.core_world.set_position(e3, Position { x: 3.0, y: 0.0 });
8530
8531 ecs.add_player(e1);
8532 ecs.add_player(e3);
8533
8534 let GameEcs {
8535 core_world, player, ..
8536 } = &ecs;
8537
8538 let mut count = 0;
8539 core_world.for_each_with_tags(
8540 MW_POSITION,
8541 0,
8542 &[player],
8543 &[],
8544 |_entity, _table, _idx| {
8545 count += 1;
8546 },
8547 );
8548 assert_eq!(count, 2);
8549
8550 count = 0;
8551 core_world.for_each_with_tags(
8552 MW_POSITION,
8553 0,
8554 &[],
8555 &[player],
8556 |_entity, _table, _idx| {
8557 count += 1;
8558 },
8559 );
8560 assert_eq!(count, 1);
8561 }
8562
8563 #[test]
8564 fn test_multi_world_for_each_mut_with_tags() {
8565 let mut ecs = GameEcs::default();
8566 let e1 = ecs.spawn();
8567 let e2 = ecs.spawn();
8568
8569 ecs.core_world.set_position(e1, Position { x: 1.0, y: 0.0 });
8570 ecs.core_world.set_position(e2, Position { x: 2.0, y: 0.0 });
8571
8572 ecs.add_player(e1);
8573
8574 let player_set = ecs.player.clone();
8575 ecs.core_world.for_each_mut_with_tags(
8576 MW_POSITION,
8577 0,
8578 &[&player_set],
8579 &[],
8580 |_entity, table, idx| {
8581 table.position[idx].x += 100.0;
8582 },
8583 );
8584
8585 assert_eq!(ecs.core_world.get_position(e1).unwrap().x, 101.0);
8586 assert_eq!(ecs.core_world.get_position(e2).unwrap().x, 2.0);
8587 }
8588
8589 #[test]
8590 fn test_multi_world_command_buffer_spawn_despawn() {
8591 let mut ecs = GameEcs::default();
8592 ecs.queue_spawn(3);
8593 assert_eq!(ecs.command_count(), 1);
8594 ecs.apply_commands();
8595
8596 let next = ecs.spawn();
8597 assert_eq!(next.id, 3);
8598
8599 let entity = ecs.spawn();
8600 ecs.core_world
8601 .set_position(entity, Position { x: 1.0, y: 0.0 });
8602 ecs.queue_despawn_entity(entity);
8603 ecs.apply_commands();
8604 assert!(ecs.core_world.get_position(entity).is_none());
8605 }
8606
8607 #[test]
8608 fn test_multi_world_command_buffer_component_add_remove() {
8609 let mut ecs = GameEcs::default();
8610 let entity = ecs.spawn();
8611
8612 ecs.queue_add_position(entity);
8613 ecs.apply_commands();
8614 assert!(ecs.core_world.get_position(entity).is_some());
8615
8616 ecs.queue_remove_position(entity);
8617 ecs.apply_commands();
8618 assert!(ecs.core_world.get_position(entity).is_none());
8619 }
8620
8621 #[test]
8622 fn test_multi_world_per_world_spawn_entities() {
8623 let mut ecs = GameEcs::default();
8624 let entities = {
8625 let GameEcs {
8626 core_world,
8627 allocator,
8628 ..
8629 } = &mut ecs;
8630 core_world.spawn_entities(allocator, MW_POSITION | MW_VELOCITY, 3)
8631 };
8632 assert_eq!(entities.len(), 3);
8633 for &entity in &entities {
8634 assert!(ecs.core_world.get_position(entity).is_some());
8635 assert!(ecs.core_world.get_velocity(entity).is_some());
8636 }
8637
8638 let next = ecs.spawn();
8639 assert_eq!(next.id, 3);
8640 }
8641
8642 #[test]
8643 fn test_multi_world_change_detection() {
8644 let mut ecs = GameEcs::default();
8645 let e1 = ecs.spawn();
8646 let e2 = ecs.spawn();
8647 ecs.core_world.set_position(e1, Position { x: 1.0, y: 0.0 });
8648 ecs.core_world.set_position(e2, Position { x: 2.0, y: 0.0 });
8649
8650 ecs.step();
8651
8652 ecs.core_world.get_position_mut(e1).unwrap().x = 10.0;
8653
8654 let changed: Vec<Entity> = ecs.core_world.query_entities_changed(MW_POSITION).collect();
8655 assert_eq!(changed, vec![e1]);
8656
8657 let mut visited = Vec::new();
8658 ecs.core_world
8659 .for_each_mut_changed(MW_POSITION, 0, |entity, _table, _idx| {
8660 visited.push(entity);
8661 });
8662 assert_eq!(visited, vec![e1]);
8663 }
8664
8665 #[test]
8666 fn test_multi_world_structural_log() {
8667 let mut ecs = GameEcs::default();
8668 let entity = ecs.spawn();
8669 ecs.core_world
8670 .set_position(entity, Position { x: 1.0, y: 0.0 });
8671 ecs.render_world.set_sprite(entity, Sprite { id: 1 });
8672
8673 let core_changes = ecs.core_world.structural_changes_since(0);
8674 assert_eq!(core_changes.len(), 1);
8675 assert_eq!(core_changes[0].kind, StructuralChangeKind::Spawned);
8676 assert_eq!(core_changes[0].mask, MW_POSITION);
8677
8678 let render_changes = ecs.render_world.structural_changes_since(0);
8679 assert_eq!(render_changes.len(), 1);
8680 assert_eq!(render_changes[0].mask, MW_SPRITE);
8681
8682 ecs.despawn(entity);
8683
8684 let core_changes = ecs.core_world.structural_changes_since(0);
8685 assert_eq!(core_changes.len(), 2);
8686 assert_eq!(core_changes[1].kind, StructuralChangeKind::Despawned);
8687 }
8688
8689 #[test]
8690 fn test_multi_world_event_cursor() {
8691 let mut ecs = GameEcs::default();
8692 let entity = ecs.spawn();
8693
8694 ecs.send_collision(CollisionEvent {
8695 entity_a: entity,
8696 entity_b: entity,
8697 });
8698
8699 let mut cursor = 0;
8700 assert_eq!(ecs.read_collision_since(cursor).len(), 1);
8701 cursor = ecs.sequence_collision();
8702 assert!(ecs.read_collision_since(cursor).is_empty());
8703
8704 ecs.send_collision(CollisionEvent {
8705 entity_a: entity,
8706 entity_b: entity,
8707 });
8708 assert_eq!(ecs.read_collision_since(cursor).len(), 1);
8709 }
8710
8711 #[test]
8712 fn test_multi_world_event_lifecycle() {
8713 let mut ecs = GameEcs::default();
8714 let entity = ecs.spawn();
8715
8716 ecs.send_collision(CollisionEvent {
8717 entity_a: entity,
8718 entity_b: entity,
8719 });
8720 assert_eq!(ecs.len_collision(), 1);
8721
8722 ecs.step();
8723 assert_eq!(ecs.len_collision(), 1);
8724
8725 ecs.step();
8726 assert_eq!(ecs.len_collision(), 0);
8727 }
8728
8729 #[test]
8730 fn test_multi_world_despawn_entities_batch() {
8731 let mut ecs = GameEcs::default();
8732 let e1 = ecs.spawn();
8733 let e2 = ecs.spawn();
8734 let e3 = ecs.spawn();
8735 for &entity in &[e1, e2, e3] {
8736 ecs.core_world.set_position(entity, Position::default());
8737 }
8738
8739 ecs.despawn_entities(&[e1, e3]);
8740
8741 assert!(ecs.core_world.get_position(e1).is_none());
8742 assert!(ecs.core_world.get_position(e2).is_some());
8743 assert!(ecs.core_world.get_position(e3).is_none());
8744 }
8745
8746 #[test]
8747 fn test_multi_world_entity_builder_single_world_components() {
8748 let mut ecs = GameEcs::default();
8749 let entities = EntityBuilder::new()
8750 .with_color(Color {
8751 r: 1.0,
8752 g: 0.0,
8753 b: 0.0,
8754 })
8755 .spawn(&mut ecs, 1);
8756
8757 assert!(ecs.render_world.get_color(entities[0]).is_some());
8758 assert!(ecs.core_world.get_position(entities[0]).is_none());
8759 assert!(ecs.render_world.get_sprite(entities[0]).is_none());
8760 }
8761
8762 #[test]
8763 fn test_multi_world_double_despawn_is_rejected() {
8764 let mut ecs = GameEcs::default();
8765 let entity = ecs.spawn();
8766 ecs.core_world.set_position(entity, Position::default());
8767
8768 assert!(ecs.despawn(entity));
8769 assert!(!ecs.despawn(entity));
8770
8771 let e1 = ecs.spawn();
8772 let e2 = ecs.spawn();
8773 assert_ne!(
8774 (e1.id, e1.generation),
8775 (e2.id, e2.generation),
8776 "double despawn must never mint two identical live handles"
8777 );
8778
8779 ecs.core_world.set_position(e1, Position { x: 1.0, y: 0.0 });
8780 ecs.core_world.set_position(e2, Position { x: 2.0, y: 0.0 });
8781 assert_eq!(ecs.core_world.get_position(e1).unwrap().x, 1.0);
8782 assert_eq!(ecs.core_world.get_position(e2).unwrap().x, 2.0);
8783 }
8784
8785 #[test]
8786 fn test_multi_world_componentless_double_despawn_is_rejected() {
8787 let mut ecs = GameEcs::default();
8788 let entity = ecs.spawn();
8789
8790 assert!(ecs.despawn(entity));
8791 assert!(!ecs.despawn(entity));
8792
8793 let e1 = ecs.spawn();
8794 let e2 = ecs.spawn();
8795 assert_ne!((e1.id, e1.generation), (e2.id, e2.generation));
8796 }
8797
8798 #[test]
8799 fn test_multi_world_stale_despawn_cannot_kill_reused_id() {
8800 let mut ecs = GameEcs::default();
8801 let old = ecs.spawn();
8802 ecs.core_world.set_position(old, Position::default());
8803 assert!(ecs.despawn(old));
8804
8805 let reused = ecs.spawn();
8806 assert_eq!(reused.id, old.id);
8807 ecs.core_world
8808 .set_position(reused, Position { x: 7.0, y: 0.0 });
8809
8810 assert!(!ecs.despawn(old), "stale handle must not despawn anything");
8811 assert_eq!(
8812 ecs.core_world.get_position(reused).unwrap().x,
8813 7.0,
8814 "live entity must survive a stale despawn attempt"
8815 );
8816
8817 let fresh = ecs.spawn();
8818 assert_ne!(
8819 (fresh.id, fresh.generation),
8820 (reused.id, reused.generation),
8821 "stale despawn must not recycle a live handle"
8822 );
8823 }
8824
8825 #[test]
8826 fn test_multi_world_stale_handle_cannot_resurrect() {
8827 let mut ecs = GameEcs::default();
8828 let old = ecs.spawn();
8829 ecs.core_world.set_position(old, Position::default());
8830 assert!(ecs.despawn(old));
8831
8832 assert!(
8833 !ecs.core_world.add_components(old, MW_POSITION),
8834 "stale add must be refused in a world that stored the entity"
8835 );
8836 ecs.core_world
8837 .set_position(old, Position { x: 9.0, y: 0.0 });
8838 assert!(ecs.core_world.get_position(old).is_none());
8839
8840 assert!(
8841 !ecs.render_world.add_components(old, MW_SPRITE),
8842 "stale add must be refused in a world that never stored the entity"
8843 );
8844 ecs.render_world.set_sprite(old, Sprite { id: 3 });
8845 assert!(ecs.render_world.get_sprite(old).is_none());
8846
8847 assert!(
8848 ecs.core_world.structural_changes_since(0).len() <= 2,
8849 "refused stale writes must not append structural log entries"
8850 );
8851 }
8852
8853 #[test]
8854 fn test_multi_world_reused_id_still_gets_components() {
8855 let mut ecs = GameEcs::default();
8856 let old = ecs.spawn();
8857 ecs.core_world.set_position(old, Position::default());
8858 assert!(ecs.despawn(old));
8859
8860 let reused = ecs.spawn();
8861 assert_eq!(reused.id, old.id);
8862
8863 ecs.core_world
8864 .set_position(reused, Position { x: 4.0, y: 0.0 });
8865 ecs.render_world.set_sprite(reused, Sprite { id: 5 });
8866
8867 assert_eq!(ecs.core_world.get_position(reused).unwrap().x, 4.0);
8868 assert_eq!(ecs.render_world.get_sprite(reused).unwrap().id, 5);
8869 assert!(ecs.core_world.get_position(old).is_none());
8870 }
8871
8872 #[test]
8873 #[cfg(not(target_family = "wasm"))]
8874 fn test_multi_world_par_for_each_mut_with_tags() {
8875 let mut ecs = GameEcs::default();
8876 let e1 = ecs.spawn();
8877 let e2 = ecs.spawn();
8878
8879 ecs.core_world.set_position(e1, Position { x: 1.0, y: 0.0 });
8880 ecs.core_world.set_position(e2, Position { x: 2.0, y: 0.0 });
8881 ecs.add_player(e1);
8882
8883 let player_set = ecs.player.clone();
8884 ecs.core_world.par_for_each_mut_with_tags(
8885 MW_POSITION,
8886 0,
8887 &[&player_set],
8888 &[],
8889 |_entity, table, index| {
8890 table.position[index].x += 100.0;
8891 },
8892 );
8893
8894 assert_eq!(ecs.core_world.get_position(e1).unwrap().x, 101.0);
8895 assert_eq!(ecs.core_world.get_position(e2).unwrap().x, 2.0);
8896
8897 ecs.core_world.par_for_each_mut_with_tags(
8898 MW_POSITION,
8899 0,
8900 &[],
8901 &[&player_set],
8902 |_entity, table, index| {
8903 table.position[index].y = 7.0;
8904 },
8905 );
8906
8907 assert_eq!(ecs.core_world.get_position(e1).unwrap().y, 0.0);
8908 assert_eq!(ecs.core_world.get_position(e2).unwrap().y, 7.0);
8909 }
8910
8911 #[derive(Default, Clone)]
8912 struct MultiModelEntity {
8913 core_mask: Option<u64>,
8914 render_mask: Option<u64>,
8915 position: Option<f32>,
8916 position_changed: bool,
8917 sprite: Option<u32>,
8918 player: bool,
8919 }
8920
8921 enum MultiModelCommand {
8922 Spawn,
8923 Despawn(Entity),
8924 SetPosition(Entity, f32),
8925 AddPosition(Entity),
8926 RemovePosition(Entity),
8927 SetSprite(Entity, u32),
8928 AddPlayer(Entity),
8929 RemovePlayer(Entity),
8930 }
8931
8932 fn multi_model_set_position(model_entity: &mut MultiModelEntity, value: f32) {
8933 let mask = model_entity.core_mask.get_or_insert(0);
8934 *mask |= MW_POSITION;
8935 model_entity.position = Some(value);
8936 model_entity.position_changed = true;
8937 }
8938
8939 fn multi_model_add_position(model_entity: &mut MultiModelEntity) {
8940 let mask = model_entity.core_mask.get_or_insert(0);
8941 if *mask & MW_POSITION == 0 {
8942 *mask |= MW_POSITION;
8943 model_entity.position = Some(0.0);
8944 model_entity.position_changed = true;
8945 }
8946 }
8947
8948 fn multi_model_add_velocity(model_entity: &mut MultiModelEntity) {
8949 let mask = model_entity.core_mask.get_or_insert(0);
8950 let migrated = *mask & MW_VELOCITY == 0;
8951 *mask |= MW_VELOCITY;
8952 if migrated && *mask & MW_POSITION != 0 {
8953 model_entity.position_changed = true;
8954 }
8955 }
8956
8957 fn multi_model_remove_position(model_entity: &mut MultiModelEntity) {
8958 if let Some(mask) = model_entity.core_mask.as_mut() {
8959 *mask &= !MW_POSITION;
8960 model_entity.position = None;
8961 }
8962 }
8963
8964 fn multi_model_set_sprite(model_entity: &mut MultiModelEntity, id: u32) {
8965 let mask = model_entity.render_mask.get_or_insert(0);
8966 *mask |= MW_SPRITE;
8967 model_entity.sprite = Some(id);
8968 }
8969
8970 fn multi_apply_and_replay(
8971 ecs: &mut GameEcs,
8972 model: &mut std::collections::HashMap<Entity, MultiModelEntity>,
8973 queued: &mut Vec<MultiModelCommand>,
8974 handles: &mut Vec<Entity>,
8975 ) {
8976 assert_eq!(
8977 ecs.command_count(),
8978 queued.len(),
8979 "ecs and model must queue in lockstep"
8980 );
8981
8982 let cursor = ecs.structural_sequence();
8983 ecs.apply_commands();
8984
8985 let mut queued_spawn_count = 0usize;
8986 for command in queued.drain(..) {
8987 match command {
8988 MultiModelCommand::Spawn => queued_spawn_count += 1,
8989 MultiModelCommand::Despawn(entity) => {
8990 model.remove(&entity);
8991 }
8992 MultiModelCommand::SetPosition(entity, value) => {
8993 if let Some(model_entity) = model.get_mut(&entity) {
8994 multi_model_set_position(model_entity, value);
8995 }
8996 }
8997 MultiModelCommand::AddPosition(entity) => {
8998 if let Some(model_entity) = model.get_mut(&entity) {
8999 multi_model_add_position(model_entity);
9000 }
9001 }
9002 MultiModelCommand::RemovePosition(entity) => {
9003 if let Some(model_entity) = model.get_mut(&entity) {
9004 multi_model_remove_position(model_entity);
9005 }
9006 }
9007 MultiModelCommand::SetSprite(entity, id) => {
9008 if let Some(model_entity) = model.get_mut(&entity) {
9009 multi_model_set_sprite(model_entity, id);
9010 }
9011 }
9012 MultiModelCommand::AddPlayer(entity) => {
9013 if let Some(model_entity) = model.get_mut(&entity) {
9014 model_entity.player = true;
9015 }
9016 }
9017 MultiModelCommand::RemovePlayer(entity) => {
9018 if let Some(model_entity) = model.get_mut(&entity) {
9019 model_entity.player = false;
9020 }
9021 }
9022 }
9023 }
9024
9025 let spawned: Vec<Entity> = ecs
9026 .structural_changes_since(cursor)
9027 .iter()
9028 .filter(|change| change.kind == StructuralChangeKind::Spawned)
9029 .map(|change| change.entity)
9030 .collect();
9031 assert_eq!(
9032 spawned.len(),
9033 queued_spawn_count,
9034 "the ecs lifecycle log must record exactly the queued spawns"
9035 );
9036 for entity in spawned {
9037 assert!(ecs.is_alive(entity));
9038 model.insert(entity, MultiModelEntity::default());
9039 handles.push(entity);
9040 }
9041 }
9042
9043 #[test]
9044 fn test_property_multi_world_matches_model() {
9045 for seed in [7u64, 1337, 24601] {
9046 let mut rng = Lcg(seed);
9047 let mut ecs = GameEcs::default();
9048 let mut model: std::collections::HashMap<Entity, MultiModelEntity> =
9049 std::collections::HashMap::new();
9050 let mut handles: Vec<Entity> = Vec::new();
9051 let mut queued: Vec<MultiModelCommand> = Vec::new();
9052 let mut pending_collisions: Vec<(u32, u32)> = Vec::new();
9053 let mut total_collisions: u64 = 0;
9054
9055 ecs.step();
9056
9057 let pick = |rng: &mut Lcg, handles: &[Entity]| {
9058 if handles.is_empty() {
9059 None
9060 } else {
9061 Some(handles[rng.next() as usize % handles.len()])
9062 }
9063 };
9064
9065 for _ in 0..3000 {
9066 match rng.next() % 16 {
9067 0 | 1 => {
9068 let entity = ecs.spawn();
9069 model.insert(entity, MultiModelEntity::default());
9070 handles.push(entity);
9071 }
9072 2 => {
9073 if let Some(entity) = pick(&mut rng, &handles) {
9074 let accepted = ecs.despawn(entity);
9075 let was_live = model.remove(&entity).is_some();
9076 assert_eq!(
9077 accepted, was_live,
9078 "despawn must succeed exactly for model-live handles"
9079 );
9080 }
9081 }
9082 3 => {
9083 if let Some(entity) = pick(&mut rng, &handles) {
9084 let value = (rng.next() % 1000) as f32;
9085 ecs.core_world
9086 .set_position(entity, Position { x: value, y: 0.0 });
9087 match model.get_mut(&entity) {
9088 Some(model_entity) => {
9089 multi_model_set_position(model_entity, value);
9090 assert_eq!(
9091 ecs.core_world.get_position(entity).unwrap().x,
9092 value
9093 );
9094 }
9095 None => assert!(
9096 ecs.core_world.get_position(entity).is_none(),
9097 "stale cross-world set must not resurrect"
9098 ),
9099 }
9100 }
9101 }
9102 4 => {
9103 if let Some(entity) = pick(&mut rng, &handles) {
9104 let id = rng.next() as u32 % 1000;
9105 ecs.render_world.set_sprite(entity, Sprite { id });
9106 match model.get_mut(&entity) {
9107 Some(model_entity) => {
9108 multi_model_set_sprite(model_entity, id);
9109 assert_eq!(
9110 ecs.render_world.get_sprite(entity).unwrap().id,
9111 id
9112 );
9113 }
9114 None => {
9115 assert!(ecs.render_world.get_sprite(entity).is_none())
9116 }
9117 }
9118 }
9119 }
9120 5 => {
9121 if let Some(entity) = pick(&mut rng, &handles) {
9122 let accepted = ecs.core_world.add_components(entity, MW_VELOCITY);
9123 match model.get_mut(&entity) {
9124 Some(model_entity) => {
9125 assert!(accepted);
9126 multi_model_add_velocity(model_entity);
9127 }
9128 None => assert!(!accepted, "stale add must be refused"),
9129 }
9130 }
9131 }
9132 6 => {
9133 if let Some(entity) = pick(&mut rng, &handles) {
9134 let accepted =
9135 ecs.core_world.remove_components(entity, MW_POSITION);
9136 match model.get_mut(&entity) {
9137 Some(model_entity) => {
9138 if model_entity.core_mask.is_some() {
9139 assert!(accepted);
9140 multi_model_remove_position(model_entity);
9141 } else {
9142 assert!(
9143 !accepted,
9144 "remove without a row must report false"
9145 );
9146 }
9147 }
9148 None => assert!(!accepted),
9149 }
9150 }
9151 }
9152 7 => {
9153 if let Some(entity) = pick(&mut rng, &handles) {
9154 ecs.add_player(entity);
9155 if let Some(model_entity) = model.get_mut(&entity) {
9156 model_entity.player = true;
9157 }
9158 assert_eq!(
9159 ecs.has_player(entity),
9160 model.get(&entity).map(|m| m.player).unwrap_or(false)
9161 );
9162 }
9163 }
9164 8 => {
9165 if let Some(entity) = pick(&mut rng, &handles) {
9166 ecs.queue_despawn_entity(entity);
9167 queued.push(MultiModelCommand::Despawn(entity));
9168 }
9169 }
9170 9 => {
9171 if let Some(entity) = pick(&mut rng, &handles) {
9172 let value = (rng.next() % 1000) as f32;
9173 ecs.queue_set_position(entity, Position { x: value, y: 0.0 });
9174 queued.push(MultiModelCommand::SetPosition(entity, value));
9175 }
9176 }
9177 10 => {
9178 if let Some(entity) = pick(&mut rng, &handles) {
9179 ecs.queue_add_position(entity);
9180 queued.push(MultiModelCommand::AddPosition(entity));
9181 }
9182 }
9183 11 => {
9184 if let Some(entity) = pick(&mut rng, &handles) {
9185 ecs.queue_remove_position(entity);
9186 queued.push(MultiModelCommand::RemovePosition(entity));
9187 }
9188 }
9189 12 => {
9190 if let Some(entity) = pick(&mut rng, &handles) {
9191 let id = rng.next() as u32 % 1000;
9192 ecs.queue_set_sprite(entity, Sprite { id });
9193 queued.push(MultiModelCommand::SetSprite(entity, id));
9194 }
9195 }
9196 13 => {
9197 if let Some(entity) = pick(&mut rng, &handles) {
9198 if rng.next().is_multiple_of(2) {
9199 ecs.queue_add_player(entity);
9200 queued.push(MultiModelCommand::AddPlayer(entity));
9201 } else {
9202 ecs.queue_remove_player(entity);
9203 queued.push(MultiModelCommand::RemovePlayer(entity));
9204 }
9205 } else {
9206 ecs.queue_spawn(1);
9207 queued.push(MultiModelCommand::Spawn);
9208 }
9209 }
9210 14 => {
9211 let entity_a = pick(&mut rng, &handles).unwrap_or(Entity {
9212 id: 0,
9213 generation: 0,
9214 });
9215 let entity_b = pick(&mut rng, &handles).unwrap_or(entity_a);
9216 ecs.send_collision(CollisionEvent { entity_a, entity_b });
9217 pending_collisions.push((entity_a.id, entity_b.id));
9218 total_collisions += 1;
9219 }
9220 _ => {
9221 multi_apply_and_replay(&mut ecs, &mut model, &mut queued, &mut handles);
9222
9223 let changed: std::collections::HashSet<Entity> =
9224 ecs.core_world.query_entities_changed(MW_POSITION).collect();
9225 let expected: std::collections::HashSet<Entity> = model
9226 .iter()
9227 .filter(|(_, model_entity)| {
9228 model_entity
9229 .core_mask
9230 .is_some_and(|mask| mask & MW_POSITION != 0)
9231 && model_entity.position_changed
9232 })
9233 .map(|(&entity, _)| entity)
9234 .collect();
9235 assert_eq!(
9236 changed, expected,
9237 "core changed-query set diverged from model with seed {seed}"
9238 );
9239
9240 ecs.step();
9241 for model_entity in model.values_mut() {
9242 model_entity.position_changed = false;
9243 }
9244
9245 let buffered: Vec<(u32, u32)> = ecs
9246 .read_collision()
9247 .map(|event| (event.entity_a.id, event.entity_b.id))
9248 .collect();
9249 assert_eq!(
9250 buffered, pending_collisions,
9251 "post-step event buffer must hold exactly the just-ended frame"
9252 );
9253 assert_eq!(ecs.sequence_collision(), total_collisions);
9254 pending_collisions.clear();
9255 }
9256 }
9257 }
9258
9259 multi_apply_and_replay(&mut ecs, &mut model, &mut queued, &mut handles);
9260
9261 for (&entity, model_entity) in &model {
9262 assert!(ecs.is_alive(entity));
9263 assert_eq!(
9264 ecs.core_world.component_mask(entity),
9265 model_entity.core_mask,
9266 "core mask diverged with seed {seed}"
9267 );
9268 assert_eq!(
9269 ecs.render_world.component_mask(entity),
9270 model_entity.render_mask,
9271 "render mask diverged with seed {seed}"
9272 );
9273 assert_eq!(
9274 ecs.core_world
9275 .get_position(entity)
9276 .map(|position| position.x),
9277 model_entity.position,
9278 "position value diverged with seed {seed}"
9279 );
9280 assert_eq!(
9281 ecs.render_world.get_sprite(entity).map(|sprite| sprite.id),
9282 model_entity.sprite,
9283 "sprite value diverged with seed {seed}"
9284 );
9285 assert_eq!(ecs.has_player(entity), model_entity.player);
9286 }
9287
9288 for &handle in &handles {
9289 if !model.contains_key(&handle) {
9290 assert!(!ecs.is_alive(handle));
9291 assert_eq!(ecs.core_world.component_mask(handle), None);
9292 assert_eq!(ecs.render_world.component_mask(handle), None);
9293 assert!(!ecs.has_player(handle));
9294 assert!(!ecs.despawn(handle), "double despawn must stay refused");
9295 }
9296 }
9297
9298 let expected_core_rows = model.values().filter(|m| m.core_mask.is_some()).count();
9299 assert_eq!(ecs.core_world.entity_count(), expected_core_rows);
9300
9301 let expected_render_rows =
9302 model.values().filter(|m| m.render_mask.is_some()).count();
9303 assert_eq!(ecs.render_world.entity_count(), expected_render_rows);
9304
9305 let expected_players = model.values().filter(|m| m.player).count();
9306 assert_eq!(ecs.query_player().count(), expected_players);
9307 }
9308 }
9309
9310 #[test]
9311 fn test_multi_world_repeated_reuse_cycle_stays_consistent() {
9312 let mut ecs = GameEcs::default();
9313 let mut previous = ecs.spawn();
9314 ecs.render_world.set_sprite(previous, Sprite { id: 0 });
9315
9316 for cycle in 1..5u32 {
9317 assert!(ecs.despawn(previous));
9318 let entity = ecs.spawn();
9319 assert_eq!(entity.id, previous.id);
9320 assert_eq!(entity.generation, cycle);
9321
9322 ecs.render_world.set_sprite(entity, Sprite { id: cycle });
9323 assert_eq!(ecs.render_world.get_sprite(entity).unwrap().id, cycle);
9324 assert!(ecs.render_world.get_sprite(previous).is_none());
9325
9326 previous = entity;
9327 }
9328 }
9329 }
9330}