Skip to main content

freecs/
lib.rs

1//! A high-performance, archetype-based Entity Component System (ECS) for Rust.
2//!
3//! freecs provides a table-based storage system where entities with identical component sets
4//! are stored together in contiguous memory (Structure of Arrays layout), optimizing for cache
5//! coherency and SIMD operations.
6//!
7//! # Key Features
8//!
9//! - **Zero-cost Abstractions**: Fully statically dispatched, no custom traits
10//! - **Parallel Processing**: Multi-threaded iteration using Rayon (automatically enabled on non-WASM platforms)
11//! - **Sparse Set Tags**: Deterministic, generation-checked markers that don't fragment archetypes
12//! - **Command Buffers**: Queue structural changes during iteration
13//! - **Change Detection**: Track component modifications for incremental updates
14//! - **Events**: Sequence-numbered channels with exactly-once cursor consumption
15//! - **Structural Change Log**: Cursor-based log of spawns, despawns, component moves, and tag flips
16//! - **Multi-World**: Split components across multiple worlds for >64 component types
17//! - **Dynamic Worlds** (optional `dynamic` feature): the primary entry point.
18//!   Runtime component registration with bundle spawns, typed queries, joins,
19//!   prepared queries, snapshots and deltas, same storage underneath; new
20//!   surface lands here first, and the macro tier stays the executable
21//!   specification it is tested against
22//!
23//! The `ecs!` macro generates the entire ECS at compile time using only plain data structures, functions, and zero unsafe code.
24//!
25//! # Quick Start
26//!
27//! ```rust
28//! use freecs::{ecs, Entity};
29//!
30//! // First, define components (must implement Default)
31//! #[derive(Default, Clone, Debug)]
32//! pub struct Position { pub x: f32, pub y: f32 }
33//!
34//! #[derive(Default, Clone, Debug)]
35//! pub struct Velocity { pub x: f32, pub y: f32 }
36//!
37//! #[derive(Default, Clone, Debug)]
38//! pub struct Health { pub value: f32 }
39//!
40//! // Then, create a world with the `ecs!` macro
41//! ecs! {
42//!   World {
43//!     position: Position => POSITION,
44//!     velocity: Velocity => VELOCITY,
45//!     health: Health => HEALTH,
46//!   }
47//!   Tags {
48//!     player => PLAYER,
49//!     enemy => ENEMY,
50//!   }
51//!   Events {
52//!     collision: CollisionEvent,
53//!   }
54//!   Resources {
55//!     delta_time: f32
56//!   }
57//! }
58//!
59//! #[derive(Debug, Clone)]
60//! pub struct CollisionEvent {
61//!     pub entity_a: Entity,
62//!     pub entity_b: Entity,
63//! }
64//! ```
65//!
66//! ## Entity and Component Access
67//!
68//! ```rust
69//! # use freecs::ecs;
70//! # #[derive(Default, Clone)] pub struct Position { x: f32, y: f32 }
71//! # #[derive(Default, Clone)] pub struct Velocity { x: f32, y: f32 }
72//! # #[derive(Default, Clone)] pub struct Health { value: f32 }
73//! # ecs! { World { position: Position => POSITION, velocity: Velocity => VELOCITY, health: Health => HEALTH, } Resources { delta_time: f32 } }
74//! let mut world = World::default();
75//!
76//! // Spawn entities with components by mask
77//! let entity = world.spawn_entities(POSITION | VELOCITY, 1)[0];
78//!
79//! // Lookup and modify a component using generated methods
80//! if let Some(pos) = world.get_position_mut(entity) {
81//!     pos.x += 1.0;
82//! }
83//!
84//! // Read components
85//! if let Some(pos) = world.get_position(entity) {
86//!     println!("Position: ({}, {})", pos.x, pos.y);
87//! }
88//!
89//! // Set components (adds if not present)
90//! world.set_position(entity, Position { x: 10.0, y: 20.0 });
91//! world.set_velocity(entity, Velocity { x: 1.0, y: 0.0 });
92//!
93//! // Add new components to an entity by mask
94//! world.add_components(entity, HEALTH | VELOCITY);
95//!
96//! // Or use the generated add methods
97//! world.add_health(entity);
98//!
99//! // Remove components from an entity by mask
100//! world.remove_components(entity, VELOCITY | POSITION);
101//!
102//! // Or use the generated remove methods
103//! world.remove_velocity(entity);
104//!
105//! // Check if entity has components
106//! if world.entity_has_position(entity) {
107//!     println!("Entity has position component");
108//! }
109//!
110//! // Query all entities
111//! let entities = world.get_all_entities();
112//! println!("All entities: {entities:?}");
113//!
114//! // Query entities, iterating over all entities matching the component mask
115//! let entities = world.query_entities(POSITION | VELOCITY);
116//!
117//! // Query for the first entity matching the component mask, returning early when found
118//! let player = world.query_first_entity(POSITION | VELOCITY);
119//! ```
120//!
121//! ## Tags
122//!
123//! Tags are lightweight markers that don't cause archetype fragmentation:
124//!
125//! ```rust
126//! # use freecs::{ecs, Entity};
127//! # #[derive(Default, Clone)] pub struct Position { x: f32, y: f32 }
128//! # ecs! { World { position: Position => POSITION, } Tags { player => PLAYER, enemy => ENEMY, } Resources { delta_time: f32 } }
129//! # let mut world = World::default();
130//! # let entity = world.spawn_entities(POSITION, 1)[0];
131//! // Add tags to entities
132//! world.add_player(entity);
133//!
134//! // Check if entity has a tag
135//! if world.has_player(entity) {
136//!     println!("Entity is a player");
137//! }
138//!
139//! // Query entities by tag
140//! for entity in world.query_player() {
141//!     println!("Player: {:?}", entity);
142//! }
143//!
144//! // Remove tags
145//! world.remove_player(entity);
146//! ```
147//!
148//! ## Events
149//!
150//! Events provide type-safe communication between systems:
151//!
152//! ```rust
153//! # use freecs::{ecs, Entity};
154//! # #[derive(Default, Clone)] pub struct Position { x: f32, y: f32 }
155//! # #[derive(Debug, Clone)] pub struct CollisionEvent { entity_a: Entity, entity_b: Entity }
156//! # ecs! { World { position: Position => POSITION, } Events { collision: CollisionEvent, } Resources { delta_time: f32 } }
157//! # let mut world = World::default();
158//! # let entity = world.spawn_entities(POSITION, 1)[0];
159//! // Send events
160//! world.send_collision(CollisionEvent {
161//!     entity_a: entity,
162//!     entity_b: entity,
163//! });
164//!
165//! // Process events in systems
166//! for event in world.collect_collision() {
167//!     println!("Collision: {:?} and {:?}", event.entity_a, event.entity_b);
168//! }
169//!
170//! // Clean up events and increment tick at end of frame
171//! world.step();
172//! ```
173//!
174//! ## Systems
175//!
176//! Systems are functions that query entities and transform their components.
177//! For maximum performance, use the query builder API for direct table access:
178//!
179//! ```rust
180//! # use freecs::ecs;
181//! # #[derive(Default, Clone)] pub struct Position { x: f32, y: f32 }
182//! # #[derive(Default, Clone)] pub struct Velocity { x: f32, y: f32 }
183//! # ecs! { World { position: Position => POSITION, velocity: Velocity => VELOCITY, } Resources { delta_time: f32 } }
184//! fn physics_system(world: &mut World) {
185//!     let dt = world.resources.delta_time;
186//!
187//!     // Method 1: High-performance query builder (recommended)
188//!     world.query_mut()
189//!         .with(POSITION | VELOCITY)
190//!         .iter(|entity, table, idx| {
191//!             table.position[idx].x += table.velocity[idx].x * dt;
192//!             table.position[idx].y += table.velocity[idx].y * dt;
193//!         });
194//!
195//!     // Method 2: Per-entity lookups (simpler but slower)
196//!     let entities: Vec<_> = world.query_entities(POSITION | VELOCITY).collect();
197//!     for entity in entities {
198//!         if let Some(velocity) = world.get_velocity(entity).cloned() {
199//!             if let Some(position) = world.get_position_mut(entity) {
200//!                 position.x += velocity.x * dt;
201//!                 position.y += velocity.y * dt;
202//!             }
203//!         }
204//!     }
205//! }
206//! ```
207//!
208//! ## Parallel Processing
209//!
210//! Process large entity counts across multiple CPU cores using Rayon. Parallel iteration is
211//! automatically available on non-WASM platforms:
212//!
213//! ```rust
214//! # use freecs::ecs;
215//! # #[derive(Default, Clone)] pub struct Position { x: f32, y: f32 }
216//! # #[derive(Default, Clone)] pub struct Velocity { x: f32, y: f32 }
217//! # ecs! { World { position: Position => POSITION, velocity: Velocity => VELOCITY, } Resources { delta_time: f32 } }
218//! use freecs::rayon::prelude::*;
219//!
220//! fn parallel_physics(world: &mut World) {
221//!     let dt = world.resources.delta_time;
222//!
223//!     world.par_for_each_mut(POSITION | VELOCITY, 0, |entity, table, idx| {
224//!         table.position[idx].x += table.velocity[idx].x * dt;
225//!         table.position[idx].y += table.velocity[idx].y * dt;
226//!     });
227//! }
228//! ```
229//!
230//! Parallel iteration is best suited for processing 100K+ entities with non-trivial
231//! per-entity computation.
232//!
233//! ## Command Buffers
234//!
235//! Queue structural changes during iteration to avoid borrow conflicts:
236//!
237//! ```rust
238//! # use freecs::{ecs, Entity};
239//! # #[derive(Default, Clone)] pub struct Position { x: f32, y: f32 }
240//! # #[derive(Default, Clone)] pub struct Health { value: f32 }
241//! # ecs! { World { position: Position => POSITION, health: Health => HEALTH, } Resources { delta_time: f32 } }
242//! # let mut world = World::default();
243//! # world.spawn_entities(POSITION | HEALTH, 10);
244//! // Queue despawns during iteration
245//! let entities_to_despawn: Vec<Entity> = world
246//!     .query_entities(HEALTH)
247//!     .filter(|&entity| {
248//!         world.get_health(entity).map_or(false, |h| h.value <= 0.0)
249//!     })
250//!     .collect();
251//!
252//! for entity in entities_to_despawn {
253//!     world.queue_despawn_entity(entity);
254//! }
255//!
256//! // Apply all queued commands at once
257//! world.apply_commands();
258//! ```
259//!
260//! ## Change Detection
261//!
262//! Track which components have been modified since the last frame:
263//!
264//! ```rust
265//! # use freecs::{ecs, Entity};
266//! # #[derive(Default, Clone)] pub struct Position { x: f32, y: f32 }
267//! # ecs! { World { position: Position => POSITION, } Resources { delta_time: f32 } }
268//! # let mut world = World::default();
269//! // Process only entities with changed components
270//! world.for_each_mut_changed(POSITION, 0, |entity, table, idx| {
271//!     // Only processes entities where position changed since last step()
272//! });
273//!
274//! // Automatically increments tick counter
275//! world.step();
276//! ```
277//!
278//! Writes through `set_*`, `get_*_mut`, and `modify_*` mark the slot as
279//! changed, as do spawns and component add/remove migrations. Raw table
280//! access does not mark. Changed queries skip whole tables that no write
281//! has touched since the last `step()`, using a per-table high-water tick
282//! per component.
283//!
284//! ## System Scheduling
285//!
286//! Organize systems into a schedule:
287//!
288//! ```rust
289//! # use freecs::{ecs, Schedule, Entity};
290//! # #[derive(Default, Clone)] pub struct Position { x: f32, y: f32 }
291//! # ecs! { World { position: Position => POSITION, } Resources { delta_time: f32 } }
292//! # fn input_system(world: &mut World) {}
293//! # fn physics_system(world: &mut World) {}
294//! # fn render_system(world: &World) {}
295//! let mut world = World::default();
296//! let mut schedule = Schedule::new();
297//!
298//! schedule
299//!     .push("input", input_system)
300//!     .push("physics", physics_system)
301//!     .push_readonly("render", render_system);
302//!
303//! // Game loop
304//! loop {
305//!     schedule.run(&mut world);
306//!     world.step();
307//! #   break;
308//! }
309//! ```
310//!
311//! ## Entity Builder
312//!
313//! ```rust
314//! # use freecs::ecs;
315//! # #[derive(Default, Clone)] pub struct Position { x: f32, y: f32 }
316//! # #[derive(Default, Clone)] pub struct Velocity { x: f32, y: f32 }
317//! # ecs! { World { position: Position => POSITION, velocity: Velocity => VELOCITY, } Resources { delta_time: f32 } }
318//! let mut world = World::default();
319//! let entities = EntityBuilder::new()
320//!     .with_position(Position { x: 1.0, y: 2.0 })
321//!     .with_velocity(Velocity { x: 0.0, y: 1.0 })
322//!     .spawn(&mut world, 2);
323//!
324//! // Access the spawned entities
325//! let first_pos = world.get_position(entities[0]).unwrap();
326//! assert_eq!(first_pos.x, 1.0);
327//! ```
328//!
329//! # Advanced Features
330//!
331//! ## Batch Spawning
332//!
333//! ```rust
334//! # use freecs::{ecs, Entity};
335//! # #[derive(Default, Clone)] pub struct Position { x: f32, y: f32 }
336//! # #[derive(Default, Clone)] pub struct Velocity { x: f32, y: f32 }
337//! # ecs! { World { position: Position => POSITION, velocity: Velocity => VELOCITY, } Resources { delta_time: f32 } }
338//! # let mut world = World::default();
339//! // Spawn with initialization callback
340//! let entities = world.spawn_batch(POSITION | VELOCITY, 1000, |table, idx| {
341//!     table.position[idx] = Position { x: idx as f32, y: 0.0 };
342//!     table.velocity[idx] = Velocity { x: 1.0, y: 0.0 };
343//! });
344//! ```
345//!
346//! ## Per-Component Iteration
347//!
348//! ```rust
349//! # use freecs::{ecs, Entity};
350//! # #[derive(Default, Clone)] pub struct Position { x: f32, y: f32 }
351//! # ecs! { World { position: Position => POSITION, } Resources { delta_time: f32 } }
352//! # let mut world = World::default();
353//! // Iterate over single component
354//! world.iter_position_mut(|_entity, position| {
355//!     position.x += 1.0;
356//! });
357//!
358//! // Slice-based iteration (most efficient)
359//! for slice in world.iter_position_slices_mut() {
360//!     for position in slice {
361//!         position.x *= 2.0;
362//!     }
363//! }
364//! ```
365//!
366//! ## Low-Level Iteration
367//!
368//! ```rust
369//! # use freecs::{ecs, Entity};
370//! # #[derive(Default, Clone)] pub struct Position { x: f32, y: f32 }
371//! # #[derive(Default, Clone)] pub struct Velocity { x: f32, y: f32 }
372//! # ecs! { World { position: Position => POSITION, velocity: Velocity => VELOCITY, } Tags { player => PLAYER, } Resources { delta_time: f32 } }
373//! # let mut world = World::default();
374//! // Include/exclude with masks
375//! world.for_each_mut(POSITION | VELOCITY, PLAYER, |entity, table, idx| {
376//!     // Process non-player entities
377//!     table.position[idx].x += table.velocity[idx].x;
378//! });
379//! ```
380//!
381//! ## Advanced Command Operations
382//!
383//! ```rust
384//! # use freecs::{ecs, Entity};
385//! # #[derive(Default, Clone)] pub struct Position { x: f32, y: f32 }
386//! # ecs! { World { position: Position => POSITION, } Tags { player => PLAYER, } Resources { delta_time: f32 } }
387//! # let mut world = World::default();
388//! # let entity = world.spawn_entities(POSITION, 1)[0];
389//! // Queue batch operations
390//! world.queue_spawn_entities(POSITION, 100);
391//! world.queue_set_position(entity, Position { x: 10.0, y: 20.0 });
392//! world.queue_add_player(entity);
393//!
394//! // Check command buffer status
395//! if world.command_count() > 100 {
396//!     world.apply_commands();
397//! }
398//!
399//! // Clear without applying
400//! world.clear_commands();
401//! ```
402//!
403//! ## Event Management
404//!
405//! ```rust
406//! # use freecs::{ecs, Entity};
407//! # #[derive(Default, Clone)] pub struct Position { x: f32, y: f32 }
408//! # #[derive(Debug, Clone)] pub struct CollisionEvent { entity_a: Entity, entity_b: Entity }
409//! # ecs! { World { position: Position => POSITION, } Events { collision: CollisionEvent, } Resources { delta_time: f32 } }
410//! # let mut world = World::default();
411//! # let entity = world.spawn_entities(POSITION, 1)[0];
412//! # world.send_collision(CollisionEvent { entity_a: entity, entity_b: entity });
413//! // Peek at events without consuming
414//! if let Some(event) = world.peek_collision() {
415//!     println!("Next collision: {:?}", event.entity_a);
416//! }
417//!
418//! // Check event count
419//! if !world.is_empty_collision() {
420//!     let count = world.len_collision();
421//!     println!("Processing {} events", count);
422//! }
423//!
424//! // Cursor-based, exactly-once consumption: record where you left off,
425//! // read everything newer, then advance your cursor.
426//! let mut cursor = 0;
427//! for event in world.read_collision_since(cursor) {
428//!     // Process event
429//! }
430//! cursor = world.sequence_collision();
431//! assert!(world.read_collision_since(cursor).is_empty());
432//! ```
433//!
434//! ## Conditional Compilation
435//!
436//! Both components and resources support `#[cfg(...)]` attributes for conditional compilation.
437//! This is useful for debug-only components, optional features, or platform-specific functionality:
438//!
439//! ```rust,ignore
440//! ecs! {
441//!     World {
442//!         position: Position => POSITION,
443//!         velocity: Velocity => VELOCITY,
444//!         #[cfg(debug_assertions)]
445//!         debug_info: DebugInfo => DEBUG_INFO,
446//!         #[cfg(feature = "physics")]
447//!         rigid_body: RigidBody => RIGID_BODY,
448//!     }
449//!     Resources {
450//!         delta_time: f32,
451//!         #[cfg(feature = "audio")]
452//!         audio_engine: AudioEngine,
453//!     }
454//! }
455//! ```
456//!
457//! When a component or resource has a `#[cfg(...)]` attribute, all related generated code
458//! (struct fields, accessor methods, mask constants, etc.) is conditionally compiled.
459
460pub 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/// Declares a dynamic world's schema in one place: the mask constants (bits
472/// assigned in declaration order, which is the registration order and
473/// therefore the snapshot schema) and the registration function that builds
474/// a [`dynamic::ComponentRegistry`] in that exact order, asserting each
475/// key's mask against its constant. Declare every component on every build
476/// configuration, and only ever append, so masks stay identical across
477/// feature sets and saves stay loadable.
478///
479/// The leading field name documents intent and keeps the shape drop-in
480/// compatible with `ecs!` component blocks; only the type and constant are
481/// used. Prefix the function with `serde` to register every component with
482/// a snapshot codec (requires the `snapshot` feature and serde derives on
483/// the components).
484///
485/// ```rust
486/// #[derive(Default, Clone, Debug)]
487/// struct Position { x: f32, y: f32 }
488///
489/// #[derive(Default, Clone, Debug)]
490/// struct Velocity { x: f32, y: f32 }
491///
492/// freecs::dynamic_schema! {
493///     pub fn register_components {
494///         position: Position => POSITION,
495///         velocity: Velocity => VELOCITY,
496///     }
497/// }
498///
499/// let world = freecs::dynamic::DynWorld::from_registry(register_components());
500/// assert_eq!(POSITION, 1);
501/// assert_eq!(VELOCITY, 2);
502/// assert_eq!(world.remaining_bits(), 62);
503/// ```
504#[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/// Declares a [`dynamic::DynEcs`] group's member worlds in one place: the
559/// index constants in declaration order and the build function that adds
560/// each member's registry at its asserted index, replacing the hand-written
561/// const-add-assert dance. Pair each member with a registration function,
562/// typically from [`dynamic_schema!`]. Apps extending a built group add
563/// their own member with [`dynamic::DynEcs::add_world_at`].
564///
565/// ```rust
566/// freecs::dynamic_schema! {
567///     pub fn register_main {
568///         value: u32 => VALUE,
569///     }
570/// }
571///
572/// freecs::dynamic_worlds! {
573///     pub fn build_ecs {
574///         MAIN => register_main,
575///     }
576/// }
577///
578/// let ecs = build_ecs();
579/// assert_eq!(MAIN, 0);
580/// assert_eq!(ecs.worlds.len(), 1);
581/// ```
582#[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/// Generates the macro-world accessor ergonomics over the keyed tier: a
608/// keys struct holding one [`dynamic::ComponentKey`] or
609/// [`dynamic::TagKey`] per entry, a `resolve` constructor registering them
610/// in declaration order, and named methods on your wrapper type —
611/// `get_<name>` / `get_<name>_mut` / `set_<name>` / `remove_<name>` /
612/// `has_<name>` per component, `add_<name>` / `remove_<name>` /
613/// `has_<name>` / `query_<name>` per tag. The wrapper must expose the named
614/// world and keys fields. Accessors run at keyed speed and stamp change
615/// ticks exactly like the generated macro world's. Component and tag names
616/// share one method namespace (`remove_` and `has_` exist for both), so an
617/// identifier may appear in only one of the two blocks; reusing one is a
618/// duplicate-method compile error.
619///
620/// ```rust
621/// use freecs::dynamic::DynWorld;
622///
623/// #[derive(Default, Clone, Debug)]
624/// struct Position { x: f32 }
625///
626/// struct Boss;
627///
628/// struct Game {
629///     world: DynWorld,
630///     keys: GameKeys,
631/// }
632///
633/// freecs::dynamic_accessors! {
634///     pub struct GameKeys for Game { world, keys }
635///     components {
636///         position: Position,
637///     }
638///     tags {
639///         boss: Boss,
640///     }
641/// }
642///
643/// let mut world = DynWorld::new();
644/// let keys = GameKeys::resolve(&mut world);
645/// let mut game = Game { world, keys };
646///
647/// let entity = game.world.spawn((Position { x: 1.0 },));
648/// game.set_position(entity, Position { x: 2.0 });
649/// game.add_boss(entity);
650/// assert_eq!(game.get_position(entity).unwrap().x, 2.0);
651/// assert!(game.has_boss(entity));
652/// assert_eq!(game.query_boss().count(), 1);
653/// ```
654#[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/// Liveness record for one entity id: the generation currently associated
744/// with the id and whether that handle is live.
745#[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/// Allocates generational entity handles and tracks which handles are live.
753///
754/// Liveness is authoritative here: `deallocate` refuses stale or already-freed
755/// handles, so an id can never enter the free list twice and two live entities
756/// can never share an id and generation.
757#[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    /// Forces a handle live at its exact id and generation, the primitive a
767    /// replica uses when applying a replicated spawn. Extends the slot
768    /// table as needed, removes the id from the free list, and keeps
769    /// `next_id` ahead of it.
770    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    /// Allocates `count` handles into `entities`, recycling freed ids first.
815    /// Fresh ids are contiguous, so their liveness slots are written with one
816    /// bulk fill instead of a store per entity.
817    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    /// Frees the handle if it is currently live. Returns false for stale
855    /// generations and double frees, leaving the allocator untouched.
856    #[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/// Returns true if `tick` was stamped after `since_tick`, treating ticks as a
927/// wrapping sequence so detection keeps working after `u32` overflow.
928#[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/// A sparse set of entities backing one tag.
936///
937/// Dense storage gives contiguous, deterministic iteration; the sparse index
938/// gives O(1) insert, remove, and contains without hashing. Membership is
939/// generation-checked, so a stale handle never matches a reused id.
940///
941/// # Examples
942///
943/// ```
944/// use freecs::{Entity, SparseTagSet};
945///
946/// let mut set = SparseTagSet::default();
947/// let entity = Entity { id: 3, generation: 0 };
948///
949/// assert!(set.insert(entity));
950/// assert!(set.contains(entity));
951/// assert_eq!(set.iter().collect::<Vec<_>>(), vec![entity]);
952///
953/// let stale = Entity { id: 3, generation: 1 };
954/// assert!(!set.contains(stale));
955///
956/// assert!(set.remove(entity));
957/// assert!(set.is_empty());
958/// ```
959#[derive(Default, Clone, Debug)]
960pub struct SparseTagSet {
961    pub dense: Vec<Entity>,
962    pub sparse: Vec<u32>,
963}
964
965impl SparseTagSet {
966    /// Adds the entity, replacing any stale entry for the same id. Returns
967    /// false if this exact handle was already present.
968    #[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/// Archetype graph edges for one table: which table an entity lands in when a
1032/// single component bit is added or removed, plus memoized targets for
1033/// multi-bit changes. Shared by the macro-generated worlds and the dynamic
1034/// world, since none of it depends on component types.
1035#[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
1054/// Registers a newly pushed table with the archetype routing structures:
1055/// inserts the mask lookup, appends the table's edge record, extends every
1056/// query-cache entry the new table satisfies, and wires single-component
1057/// edges from existing tables toward the new one. `table_masks` must iterate
1058/// every table including the new one, in index order.
1059pub 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
1098/// Returns the memoized list of table indices whose masks contain `mask`,
1099/// computing and caching it on first use. Taking the cache and the table
1100/// masks as separate parameters keeps the borrows disjoint, so callers can
1101/// mutate tables while holding the returned slice.
1102pub 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
1119/// Backstop for worlds whose structural log is never consumed. When the log
1120/// reaches this length it is cleared wholesale, so a world with no consumer
1121/// stays bounded instead of leaking. Consumers that drain every frame never
1122/// come near it.
1123pub 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/// One structural mutation recorded by the world: a spawn, a despawn, or a
1137/// component add or remove. `mask` holds the components involved: the full
1138/// mask for spawns and despawns, the delta for adds and removes. Consumers
1139/// track their own `sequence` cursor via `structural_changes_since` and the
1140/// owner trims consumed entries with `trim_structural_log`.
1141#[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
1150/// Backstop for event channels whose events are never consumed or expired.
1151/// When the buffer reaches this length the oldest events are dropped, so a
1152/// channel with no consumer stays bounded instead of leaking.
1153pub const EVENT_CHANNEL_CAPACITY: usize = 262_144;
1154
1155/// A sequence-numbered event channel for inter-system communication.
1156///
1157/// Events live in one flat `Vec` and every event gets a monotonically
1158/// increasing sequence number, the same cursor scheme the structural log
1159/// uses. Two consumption styles are supported:
1160///
1161/// - **Frame-scoped**: [`read()`](EventChannel::read) sees every buffered
1162///   event, and [`update()`](EventChannel::update) once per frame expires
1163///   events after they have been visible for two frames, matching the old
1164///   double-buffer lifetime.
1165/// - **Cursor-based, exactly-once**: each consumer records
1166///   [`sequence()`](EventChannel::sequence) after reading
1167///   [`events_since(cursor)`](EventChannel::events_since). Multiple consumers
1168///   each see every event exactly once, and a consumer that skips a frame
1169///   catches up instead of double-processing or missing events.
1170///
1171/// # Examples
1172///
1173/// ```
1174/// use freecs::EventChannel;
1175///
1176/// #[derive(Debug, Clone)]
1177/// struct DamageEvent { amount: i32 }
1178///
1179/// let mut channel = EventChannel::new();
1180///
1181/// channel.send(DamageEvent { amount: 10 });
1182/// assert_eq!(channel.len(), 1);
1183///
1184/// let mut cursor = 0;
1185/// let seen = channel.events_since(cursor);
1186/// assert_eq!(seen.len(), 1);
1187/// cursor = channel.sequence();
1188///
1189/// assert!(channel.events_since(cursor).is_empty(), "cursor consumers see each event once");
1190///
1191/// channel.update();
1192/// assert_eq!(channel.len(), 1, "event persists after first update");
1193///
1194/// channel.update();
1195/// assert_eq!(channel.len(), 0, "event expired after second update");
1196/// ```
1197#[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    /// Sends an event. It stays readable until it expires two `update()`
1220    /// calls later or a cursor consumer trims past it.
1221    #[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    /// The sequence number of the most recently sent event. Record this as
1237    /// your cursor after consuming `events_since`.
1238    #[inline]
1239    pub fn sequence(&self) -> u64 {
1240        self.base_sequence + self.events.len() as u64
1241    }
1242
1243    /// All buffered events sent after `cursor`, oldest first. A cursor older
1244    /// than the buffer yields everything still buffered.
1245    #[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    /// The exactly-once read: yields every event sent after `cursor` and
1254    /// advances the cursor past them, so calling this every frame delivers
1255    /// each event to this consumer exactly once. Each consumer owns one
1256    /// `u64` cursor; the buffer itself is untouched, so other consumers and
1257    /// the two-frame expiry are unaffected. This is the spelling to reach
1258    /// for by default; `read()` re-reads the whole buffer every call.
1259    #[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    /// Returns an iterator over every buffered event, oldest first.
1267    pub fn read(&self) -> impl Iterator<Item = &T> {
1268        self.events.iter()
1269    }
1270
1271    /// The events settled at the last [`update`](Self::update): the previous
1272    /// frame's complete set, frozen and broadcast to every reader without a
1273    /// cursor. Events sent since that `update` (this frame's sends) are
1274    /// excluded, so the slice does not change as more events arrive during the
1275    /// frame. A reader that calls this once per frame therefore sees each
1276    /// frame's events exactly once, one frame after they were sent, regardless
1277    /// of where in the frame it reads and of interleaving with senders. This is
1278    /// the broadcast, single-frame counterpart to the cursor-based
1279    /// [`consume`](Self::consume): use it when every reader must observe the
1280    /// same frozen set for a frame, as a deferred event pipeline does.
1281    #[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    /// Returns a reference to the oldest buffered event, if any.
1291    pub fn peek(&self) -> Option<&T> {
1292        self.events.first()
1293    }
1294
1295    /// Drops all events up to and including `up_to_sequence`. Call with the
1296    /// minimum cursor across consumers to reclaim memory early.
1297    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    /// Expires events that have now been visible for two frames. Call once
1306    /// per frame; `step()` on a generated world does this for every channel.
1307    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    /// Immediately drops every buffered event, advancing past them.
1314    pub fn clear(&mut self) {
1315        let sequence = self.sequence();
1316        self.trim(sequence);
1317    }
1318
1319    /// Returns the number of buffered events.
1320    pub fn len(&self) -> usize {
1321        self.events.len()
1322    }
1323
1324    /// Returns `true` if no events are buffered.
1325    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    /// Adds a system that runs only when the condition holds. The condition
1373    /// reads the world at each pass; a false skips the system for that pass
1374    /// without removing it from the schedule.
1375    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
1481/// An ordered set of named stages, each its own [`Schedule`], for programs
1482/// assembled from independent parts: the app declares the stage order once,
1483/// and each part pushes systems into stages by name, so composition stays
1484/// deterministic without labels or ordering constraints. Stages run in
1485/// declaration order; systems within a stage run in push order. Plain data,
1486/// inspectable through [`stages`](Self::stages).
1487pub 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    /// Appends a stage at the end of the run order. Panics on a duplicate
1497    /// name, since two parts declaring the same stage is a wiring bug.
1498    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    /// The stage's schedule, for pushing systems into it
1508    /// (`stages.stage_mut("simulation").push(...)`). Panics with the
1509    /// declared stage list when the stage does not exist, so a part pushing
1510    /// into a missing stage fails at wiring time, not silently.
1511    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    /// Runs every stage in declaration order.
1530    pub fn run(&mut self, world: &mut W) {
1531        for (_name, schedule) in &mut self.stages {
1532            schedule.run(world);
1533        }
1534    }
1535
1536    /// Runs one stage by name, for loops that drive stages on different
1537    /// cadences. Panics like [`stage_mut`](Self::stage_mut) when missing.
1538    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/// Emits the shared archetype kernel for one world type: component constants,
1769/// table storage, entity location bookkeeping, structural log, change-tick
1770/// tracking, and every component accessor. The invoking macro defines the
1771/// world struct itself (which must carry the kernel fields) and everything
1772/// tag-, event-, and command-related, since those differ per mode.
1773///
1774/// `$allow_insert` controls whether `add_components` may materialize a row
1775/// for a live entity this world has never stored. Multi-world worlds need
1776/// that (entities gain components per world lazily); a single world never
1777/// does, because spawning always creates the row.
1778///
1779/// # Extending these macros
1780///
1781/// `macro_rules!` cannot nest repetitions from different capture groups: a
1782/// `$($tag_name ...)*` inside a `$($name ...)*` fails with "meta-variable
1783/// repeats N times, but ... repeats M times" because the expander tries to
1784/// iterate the groups in lockstep. That single constraint shaped the tag
1785/// architecture, and any new feature that needs component and tag captures
1786/// in one body must use one of the three patterns already in this file:
1787///
1788/// - Generate the code at method level, outside any per-component
1789///   repetition, where each group can repeat independently (the mode-level
1790///   `for_each*` wrappers and their filter closures).
1791/// - Route the shared body through a kernel free function that takes the
1792///   tables and query cache as separate parameters, and pass tag state in
1793///   as a closure built where tag names are in scope, destructuring `self`
1794///   into disjoint fields when a `&mut` path needs it (`tables_for_each_*`).
1795/// - Inside a per-component repetition, avoid capturing tag names at all
1796///   and call a whole-`self` helper like `entity_matches_tags` between
1797///   short-lived borrows (`query_<name>_mut`).
1798///
1799/// Generated items carry `#[allow(unused)]` because a user's declaration
1800/// legitimately uses a fraction of the emitted surface. The cost is that
1801/// genuinely dead generated code will not warn, so removals here need a
1802/// manual search for remaining emit sites.
1803#[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                /// Stamps every row of the masked columns as changed at
1848                /// `tick`, the bulk opt-in for whole-column raw writes:
1849                /// after filling columns through `query_mut()` closures or
1850                /// `for_each_*_mut` table loops, one call here makes the
1851                /// pass visible to tick-diffing consumers at zero per-row
1852                /// cost during the write. Pass the world's `current_tick`.
1853                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                /// Frees each live handle through the allocator and retires its
2679                /// row. Stale or already-despawned handles are skipped.
2680                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                /// Removes the entity's row if present and records the next
2696                /// generation for this id, so stale writes can be refused even
2697                /// in worlds that never stored the entity. Must only be called
2698                /// with a handle the allocator confirmed live; in multi-world
2699                /// mode this is the per-world eviction step that `despawn`
2700                /// drives across every world. Calling it directly on a
2701                /// single world leaks the handle: the row disappears while the
2702                /// allocator still counts it live, so the id is never
2703                /// recycled. Use `despawn_entities` there instead.
2704                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                /// Adds components, migrating the entity's row. In
2748                /// multi-world mode a live handle this world has never stored
2749                /// gets a row inserted directly; the generation check refuses
2750                /// stale handles for any id this ECS has despawned, but a
2751                /// handle forged for an id the allocator never issued cannot
2752                /// be detected here because worlds hold no allocator access.
2753                /// Only handles minted by the shared allocator are supported.
2754                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                /// Explicitly stamps change ticks for the masked components
2948                /// on one entity. This is the opt-in for raw-tier writes:
2949                /// `query_mut()` closures, `for_each_*_mut`, and slice
2950                /// iterators do not stamp, so follow such writes with this
2951                /// call when downstream consumers diff by ticks. Returns
2952                /// false if the entity is missing or its table lacks every
2953                /// masked component.
2954                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            /// Returns None when an included tag has no members, since nothing
3265            /// can match. Otherwise drops excluded tags whose sets are empty,
3266            /// so queries like "not DEAD" stay on the unfiltered fast path
3267            /// while no entity carries the tag.
3268            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                    /// Visits the component mutably for every entity matching
3472                    /// `mask` (components and tags), stamping change ticks.
3473                    /// Scans tables by mask rather than the query cache: the
3474                    /// per-entity tag checks here live inside a per-component
3475                    /// macro repetition, which rules out the borrow shapes the
3476                    /// cached paths use (see the note on `ecs_kernel_impl`).
3477                    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                    /// The exactly-once read: yields events sent after the
3559                    /// cursor and advances it past them, so a handler calling
3560                    /// this every frame sees each event once. Events stay
3561                    /// buffered for two frames, so `read_` and `collect_`
3562                    /// re-deliver on the second frame; keep one `u64` cursor
3563                    /// per consumer and reach for this by default.
3564                    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                    /// Expires this channel's events after their two-frame
3581                    /// window. `step()` already calls this once per frame for
3582                    /// every channel; calling both halves event lifetime, so
3583                    /// use this directly only when managing frame boundaries
3584                    /// yourself.
3585                    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                /// Despawns the entity across every world, dropping its tags.
4070                /// Returns false for stale or already-despawned handles, which
4071                /// are left untouched. The per-world retirement broadcast
4072                /// grows each world's location table to cover the despawned
4073                /// id, 16 bytes per id per world even in worlds that never
4074                /// stored the entity; that footprint is what makes stale
4075                /// writes refusable everywhere.
4076                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                /// The ECS-level lifecycle log: handle allocation (`Spawned`
4110                /// with mask 0), handle death (`Despawned` with mask 0), and
4111                /// tag flips. Row-level history lives in each world's own log,
4112                /// where an entity is `Spawned` with a component mask when its
4113                /// first components arrive there. Sync world contents from the
4114                /// world logs and handle lifetime or tags from this one; a
4115                /// consumer merging both will see one entity spawn twice.
4116                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                    /// The exactly-once read: yields events sent after the
4168                    /// cursor and advances it past them, so a handler calling
4169                    /// this every frame sees each event once. Events stay
4170                    /// buffered for two frames, so `read_` and `collect_`
4171                    /// re-deliver on the second frame; keep one `u64` cursor
4172                    /// per consumer and reach for this by default.
4173                    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                    /// Expires this channel's events after their two-frame
4190                    /// window. `step()` already calls this once per frame for
4191                    /// every channel; calling both halves event lifetime, so
4192                    /// use this directly only when managing frame boundaries
4193                    /// yourself.
4194                    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}