Skip to content

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:

csharp
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 == 1

Aspect.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:

csharp
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

Neofox: hug 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:

csharp
world.Template()
    .Add(new Position(1, 2))   // -> "visuals"
    .Add(new CrewData(5))      // -> "game"
    .Spawn(100)
    .Dispose();

// world.Count == 100, visuals.Count == 100, game.Count == 100

Batches 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:

Neofox: peek_knife ONE ASPECT PER BATCH

csharp
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:

csharp
using (var worldLock = world.Lock())
{
    entity.Add(new Position(1, 2)); // deferred join...
    // visuals.Count still 0 here!
}
// ...applied now: visuals.Count == 1

Despawning

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>() and query.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.
csharp
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>());         // True

Relations 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:

csharp
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.