Membership & Lifecycle
club rules: bring a Component, stay for the party
All Aspects of a World share the same Entities – an Entity is one identity that can have component data in several Aspects at once. Membership is simply "does this Entity currently have data here?"
Lazy Membership
Entities don't join an Aspect until they actually bring something to store. A freshly spawned Entity is a member of Main and nothing else:
var world = new World();
var visuals = world.AddAspect("visuals").Owns<Position>().Owns<Matrix>();
var game = world.AddAspect("game").Owns<CrewData>();
var entity = world.Spawn();
// world.Count == 1, visuals.Count == 0, game.Count == 0
entity.Add(new Position(1, 2)); // joins "visuals"
// visuals.Count == 1
entity.Add(new CrewData(5)); // also joins "game"
// game.Count == 1Aspect.Count counts member Entities, not components – an Entity with both Position and Matrix is still just one very stylish member of "visuals".
Eviction (and Rejoining)
Removing an Entity's last owned component evicts it from that Aspect. Removing merely one of several does not:
var entity = world.Spawn().Add(new Position(1, 2)).Add(new Matrix(7));
entity.Remove<Position>(); // still a member - Matrix remains
entity.Remove<Matrix>(); // evicted from "visuals"
// but very much alive, and still a member of Main:
Console.WriteLine(world.IsAlive(entity)); // True
entity.Add(new Position(5, 6)); // rejoins "visuals" - no hard feelings
NO FOX LEFT BEHIND
Eviction never despawns. An Aspect losing interest in an Entity is a structural change, not a death sentence – the Entity keeps living in Main (and any other Aspect it has data in).
Everyone lives in Main
Every living Entity is a member of Main, always – even one whose components live entirely in other Aspects. world.Count (which is Main's count) is therefore always your total population, while aspect.Count counts that Aspect's members only.
Templates, Batches & Deferred Mode
Bulk operations are fully Aspect-aware, and mostly Just Work™:
EntityTemplates split their template across Aspects automatically – no special ceremony needed:
world.Template()
.Add(new Position(1, 2)) // -> "visuals"
.Add(new CrewData(5)) // -> "game"
.Spawn(100)
.Dispose();
// world.Count == 100, visuals.Count == 100, game.Count == 100Batches operate within a Query – and since a Query belongs to a single Aspect (more on that in Queries & Streams), a Batch may only add or remove types of that same Aspect:
ONE ASPECT PER BATCH
var query = game.Query<CrewData>().Compile();
query.Batch().Add(new Cargo(50)).Submit(); // ✅ Cargo lives in "game" too
query.Batch().Add(new Position(1, 2)); // 💥 InvalidOperationException -
// Position belongs to "visuals"Bulk-removing the last owned component evicts the whole matched set at once – all those Entities live on in Main.
Deferred mode works exactly as you'd hope: while a World Lock is held, lazy joins, evictions, and despawns are queued, then routed to their correct Aspects on catch-up:
using (var worldLock = world.Lock())
{
entity.Add(new Position(1, 2)); // deferred join...
// visuals.Count still 0 here!
}
// ...applied now: visuals.Count == 1Despawning
Despawning removes an Entity from all Aspects – its data is cleaned up in every storage universe it ever joined. This includes the fancy cases:
world.DespawnAllWith<T>()andquery.Truncate(n)cascade across all Aspects of the affected Entities.- Despawning the target of a Relation cleans up relation components in every Aspect that stored one – while leaving the holders' other components untouched.
var target = world.Spawn();
var follower = world.Spawn().Add(new Position(1, 2)).Add(new Follows(1), target);
target.Despawn();
// the relation is gone (from its Aspect), the rest survives:
Console.WriteLine(follower.Has<Follows>(Match.Any)); // False
Console.WriteLine(follower.Has<Position>()); // TrueRelations also happily target Entities that never joined the relation's own Aspect – a "game" crew member can point at a ship that only has "visuals" data.
Enumerating an Aspect
Aspects are IEnumerable<Entity> over their members – handy for world setup, debugging, and unit tests, and yes, you may LINQ it up:
foreach (var member in visuals) { /* ... */ }
var stragglers = visuals.Where(e => !e.Has<Matrix>()).ToArray();Meanwhile, entity.Components and entity.Dump() aggregate across all Aspects – from the Entity's point of view, it's all just one big component collection.
Where to next?
Members sorted! Now learn to query them: Queries & Streams.