Skip to content

Entity Liveness Neofox: comfy ​

Neofox: thumbsup Is It Alive?

Every entity knows whether it's alive or not. This is fundamental to safely working with entities in fennecs!

Entity.Alive ​

The Alive property tells you whether an entity currently exists in its World.

cs
var fox = world.Spawn();
Console.WriteLine(fox.Alive);  // true

fox.Despawn();
Console.WriteLine(fox.Alive);  // false
cs
var entity = world.Spawn();
Console.WriteLine($"{entity} alive: {entity.Alive}");
entity.Despawn();
Console.WriteLine($"{entity} alive: {entity.Alive}");
plaintext
E-00000001:00001 alive: True
E-00000001:00001 alive: False

Implicit Bool Conversion Neofox: heart ​

Entities can be used directly in boolean contexts – they implicitly convert to their Alive state:

cs
var entity = world.Spawn();

if (entity)  // Same as: if (entity.Alive)
{
    Console.WriteLine("Entity is alive!");
}

entity.Despawn();

if (!entity)  // Same as: if (!entity.Alive)
{
    Console.WriteLine("Entity is dead!");
}

How Identity Recycling Works Neofox: science ​

Entities use a generational index system (similar to a Sparse Set):

  1. Each entity has an index (which slot it occupies)
  2. Each entity has a generation (how many times that slot has been reused)
  3. When despawned, the generation increments and the slot returns to the pool

This means:

  • Entity IDs are always unique within a World's lifetime
  • Stale entity handles can be detected (generation mismatch)
  • Memory is reused efficiently
cs
var first = world.Spawn();   // Index 1, Generation 1
first.Despawn();

var second = world.Spawn();  // Index 1, Generation 2 (reused slot!)

Console.WriteLine(first.Alive);   // false (generation mismatch)
Console.WriteLine(second.Alive);  // true

Deferred Operations & Liveness Neofox: think ​

Neofox: owo Entities Stay "Alive" During Deferred Mode

When inside a Stream runner or while holding a World lock, structural changes (including despawns) are deferred. The entity will report as Alive until the deferred operations are applied!

cs
var stream = world.Query<Health>().Stream();

stream.For((in entity, ref health) =>
{
    entity.Despawn();
    
    // Still reports alive during deferred execution!
    Console.WriteLine(entity.Alive);  // true
});

// After runner completes, despawns are applied
// Now the entities would report as dead

Manual World Locking ​

You can manually defer operations using a World lock:

cs
using var worldLock = world.Lock();

foreach (var entity in query)
{
    if (Random.Shared.NextSingle() >= 0.5f)
    {
        entity.Despawn();
    }
    
    // Entity still "Alive" while lock is held
    if (entity.Alive) 
    {
        Console.WriteLine("Dead Fox Walking!");
    }
}

// Lock disposed here - despawns are applied

Best Practices ​

Check Before Operating on Stored Entities ​

If you store entity references (in lists, dictionaries, components), check liveness before using them:

cs
// Stored entity reference
private Entity _target;

public void Update()
{
    if (!_target.Alive)
    {
        _target = FindNewTarget();
    }
    
    // Safe to use now
    ref var pos = ref _target.Ref<Position>();
}

Don't Store Entity References Long-Term (If Possible) ​

Queries and Streams give you fresh, valid entity references each iteration. Prefer these over storing entities when possible.

Handle Dead Entities Gracefully ​

cs
public void DamageEntity(Entity target, int damage)
{
    if (!target.Alive)
    {
        Console.WriteLine("Target already dead!");
        return;
    }
    
    ref var health = ref target.Ref<Health>();
    health.Value -= damage;
    
    if (health.Value <= 0)
    {
        target.Despawn();
    }
}

Quick Reference ​

Property/PatternDescription
entity.AliveReturns true if entity exists in world
if (entity)Implicit bool conversion to Alive
Deferred modeEntity reports Alive until lock released
Generational IDPrevents stale handle collisions