Despawning Entities
The Circle of Life
What is spawned must eventually despawn. It's natural, it's healthy, and it keeps your World tidy!
Entity.Despawn()
Removes an entity from its World, freeing its identity for reuse and cleaning up all its components.
var fox = world.Spawn();
fox.Add(new Position { X = 10, Y = 20 });
fox.Add<Fluffy>();
fox.Despawn(); // Goodbye, little fox! 🦊
Console.WriteLine(fox.Alive); // falseWhat Happens When You Despawn?
When an entity is despawned:
- All components are removed - The entity's data is cleaned up
- Relations are severed - Any relations pointing to this entity from others are also removed
- Identity is recycled - The slot becomes available for new entities
- The handle becomes stale - The
Entitystruct still exists, butAlivereturnsfalse
The Handle Lives On (But Shouldn't Be Used)
After despawning, you still have the Entity struct in your variable – it's just a value type. But it's now a "dead" reference. Attempting CRUD operations on it will throw.
var fox = world.Spawn();
fox.Despawn();
fox.Add<Fluffy>(); // ❌ Throws ObjectDisposedException!Usage Examples
Basic Despawn
var enemy = world.Spawn().Add<Enemy>();
// ... enemy gets defeated ...
enemy.Despawn();Conditional Despawn
if (entity.Has<Health>())
{
ref var health = ref entity.Ref<Health>();
if (health.Value <= 0)
{
entity.Despawn();
}
}Bulk Despawn via Query
// Despawn all dead entities
var deadQuery = world.Query<Health>().Build();
foreach (var entity in deadQuery)
{
if (entity.Ref<Health>().Value <= 0)
{
entity.Despawn();
}
}Despawn with Deferred Execution
// Inside a Stream runner, despawns are deferred
var stream = world.Query<Health>().Stream();
stream.For((Entity entity, ref Health health) =>
{
if (health.Value <= 0)
{
entity.Despawn(); // Deferred until runner completes
}
});
// All despawns happen here, after the runner finishesDeferred Despawning
When you're inside a Stream runner (like For, Job, or Raw), despawns are deferred until the runner completes. This is important!
stream.For((Entity entity, ref Health health) =>
{
entity.Despawn();
// Entity is still "Alive" here during deferred mode!
Console.WriteLine(entity.Alive); // true (until runner ends)
});
// NOW they're all despawned
Why Deferred?
Immediate despawns during iteration would invalidate the iterator and cause chaos. fennecs automatically batches structural changes (spawns, despawns, component adds/removes) until it's safe to apply them.
To manually control this behavior, you can use World locks:
using var worldLock = world.Lock();
foreach (var entity in query)
{
if (shouldDespawn)
{
entity.Despawn(); // Deferred while lock is held
Console.WriteLine(entity.Alive); // Still true!
}
}
// Lock disposed here - despawns appliedRelations and Despawning
When you despawn an entity that is the target of relations, those relations are automatically cleaned up:
var parent = world.Spawn();
var child = world.Spawn();
child.Add<ChildOf>(parent); // child --ChildOf--> parent
parent.Despawn(); // The ChildOf relation on 'child' is also removed!
Console.WriteLine(child.Has<ChildOf>(parent)); // false
No Dangling Relations
fennecs ensures you never have relations pointing to dead entities. When a relation target is despawned, all relations to it are automatically removed.
When to Despawn
| Scenario | Approach |
|---|---|
| Entity defeated/destroyed | Despawn immediately or after death animation |
| Temporary effects | Despawn when duration expires |
| Pooled objects | Consider removing components instead of despawning |
| Scene transitions | Despawn all entities, or use multiple Worlds |
Constraints
- Only living entities can be despawned (despawning twice throws)
- Despawn is immediate outside of locked/runner contexts
- Despawn is deferred inside Stream runners or while World is locked