Entity Relationships Guide
Entity relationships enable hierarchical structures like scene graphs, UI layouts, and skeletal animations. This guide covers parent-child relationships in KeenEyes.
Overview
KeenEyes provides built-in parent-child relationship management:
- Parent lookup - O(1) via dictionary
- Children enumeration - O(C) where C is child count
- Cycle prevention - Automatic detection and rejection
- Cascading despawn - Destroy entity and all descendants
Basic Relationship Operations
Setting a Parent
Use SetParent() to establish a parent-child relationship:
using var world = new World();
var parent = world.Spawn().With(new Position { X = 0, Y = 0 }).Build();
var child = world.Spawn().With(new Position { X = 10, Y = 10 }).Build();
// Establish relationship
world.SetParent(child, parent);
Adding Children
The AddChild() method provides an alternative syntax:
var parent = world.Spawn().Build();
var child = world.Spawn().Build();
// Equivalent to SetParent(child, parent)
world.AddChild(parent, child);
Getting Parent and Children
// Get parent (returns Entity.Null if no parent)
Entity parentEntity = world.GetParent(child);
if (parentEntity.IsValid)
{
Console.WriteLine($"Parent: {parentEntity}");
}
// Get all immediate children
foreach (var c in world.GetChildren(parent))
{
Console.WriteLine($"Child: {c}");
}
Removing Relationships
// Remove parent (make child a root entity)
world.SetParent(child, Entity.Null);
// Or use RemoveChild
bool removed = world.RemoveChild(parent, child);
Hierarchy Traversal
Get All Descendants
GetDescendants() returns all descendants in breadth-first order:
// Create hierarchy: root -> child -> grandchild
var root = world.Spawn().Build();
var child = world.Spawn().Build();
var grandchild = world.Spawn().Build();
world.SetParent(child, root);
world.SetParent(grandchild, child);
// Returns: child, grandchild
foreach (var descendant in world.GetDescendants(root))
{
Console.WriteLine($"Descendant: {descendant}");
}
Get All Ancestors
GetAncestors() walks up the hierarchy:
// Returns: child, root (from grandchild's perspective)
foreach (var ancestor in world.GetAncestors(grandchild))
{
Console.WriteLine($"Ancestor: {ancestor}");
}
Find Root Entity
GetRoot() finds the topmost ancestor:
Entity rootEntity = world.GetRoot(grandchild);
// rootEntity == root
// Root entities return themselves
Entity selfRoot = world.GetRoot(root);
// selfRoot == root
Cascading Despawn
DespawnRecursive
To destroy an entity and all its descendants, use DespawnRecursive():
var root = world.Spawn().Build();
var child1 = world.Spawn().Build();
var child2 = world.Spawn().Build();
var grandchild = world.Spawn().Build();
world.SetParent(child1, root);
world.SetParent(child2, root);
world.SetParent(grandchild, child1);
// Destroy root and all 3 descendants
int count = world.DespawnRecursive(root);
Console.WriteLine($"Destroyed {count} entities"); // 4
// All are now dead
Console.WriteLine(world.IsAlive(root)); // False
Console.WriteLine(world.IsAlive(child1)); // False
Console.WriteLine(world.IsAlive(child2)); // False
Console.WriteLine(world.IsAlive(grandchild)); // False
Regular Despawn Behavior
Regular Despawn() removes only the entity itself. Children become orphaned (root entities):
world.SetParent(child, parent);
world.Despawn(parent);
// Child is still alive but now has no parent
Console.WriteLine(world.IsAlive(child)); // True
Console.WriteLine(world.GetParent(child).IsValid); // False
Cycle Prevention
KeenEyes automatically prevents circular hierarchies:
var a = world.Spawn().Build();
var b = world.Spawn().Build();
var c = world.Spawn().Build();
world.SetParent(b, a); // a -> b
world.SetParent(c, b); // a -> b -> c
// This would create a cycle: a -> b -> c -> a
try
{
world.SetParent(a, c); // Throws InvalidOperationException
}
catch (InvalidOperationException ex)
{
Console.WriteLine(ex.Message);
// "Cannot set entity ... as parent ... would create circular hierarchy"
}
Use Cases
Scene Graph
// Create scene hierarchy
var scene = world.Spawn("Scene").Build();
var player = world.Spawn("Player")
.With(new Position { X = 0, Y = 0 })
.Build();
var weapon = world.Spawn("Weapon")
.With(new Position { X = 1, Y = 0 })
.Build();
world.SetParent(player, scene);
world.SetParent(weapon, player);
// Weapon moves with player
UI Layout
var window = world.Spawn("Window")
.With(new UIRect { X = 100, Y = 100, Width = 400, Height = 300 })
.Build();
var titleBar = world.Spawn("TitleBar")
.With(new UIRect { X = 0, Y = 0, Width = 400, Height = 30 })
.Build();
var content = world.Spawn("Content")
.With(new UIRect { X = 0, Y = 30, Width = 400, Height = 270 })
.Build();
world.SetParent(titleBar, window);
world.SetParent(content, window);
// Close window destroys all UI elements
world.DespawnRecursive(window);
Transform Propagation
public class TransformPropagationSystem : SystemBase
{
public override void Update(float deltaTime)
{
// Process root entities first
foreach (var entity in World.Query<Position>())
{
var parent = World.GetParent(entity);
if (!parent.IsValid) continue; // Skip roots
// Get parent's world position
ref readonly var parentPos = ref World.Get<Position>(parent);
ref var childPos = ref World.Get<Position>(entity);
// Child position is relative to parent
// (In real code, you'd store local vs world positions separately)
}
}
}
Performance Characteristics
| Operation | Complexity | Notes |
|---|---|---|
SetParent |
O(D) | D = hierarchy depth (cycle check) |
GetParent |
O(1) | Dictionary lookup |
GetChildren |
O(C) | C = child count |
GetDescendants |
O(N) | N = total descendants |
GetAncestors |
O(D) | D = hierarchy depth |
GetRoot |
O(D) | D = hierarchy depth |
DespawnRecursive |
O(N) | N = subtree size |
Best Practices
Do: Validate Before Access
var parent = world.GetParent(entity);
if (parent.IsValid && world.IsAlive(parent))
{
ref var parentPos = ref world.Get<Position>(parent);
}
Do: Use DespawnRecursive for Hierarchies
// Clean up entire hierarchy
world.DespawnRecursive(rootEntity);
Don't: Create Deep Hierarchies
// Avoid: Very deep nesting hurts traversal performance
// a -> b -> c -> d -> e -> f -> g -> h -> ...
// Better: Flatter structures with grouped children
// root -> [child1, child2, child3, ...]
Don't: Store Stale Parent References
// Bad: Parent may be despawned
var parent = world.GetParent(child);
// ... much later ...
world.Get<Position>(parent); // May throw!
// Good: Always validate
var parent = world.GetParent(child);
if (parent.IsValid && world.IsAlive(parent))
{
ref var pos = ref world.Get<Position>(parent);
}
Next Steps
- Events Guide - React to hierarchy changes
- Command Buffer - Deferred hierarchy operations
- Entities Guide - Entity lifecycle basics