Surrogate Components
Docs / Guides / Surrogate components
Use surrogate components to bridge between different component representations or handle legacy data.
Overview
Surrogate components are temporary components used to:
- Convert data: Transform between component formats
- Handle migrations: Support legacy component structures
- Bridge systems: Connect incompatible component types
- Temporary state: Store intermediate computation results
Use Cases
Data Conversion
Convert between component formats:
// Old component format
public struct OldPosition
{
public float X, Y;
}
// New component format
public struct Position
{
public float X, Y, Z;
}
// Surrogate for conversion
public struct PositionSurrogate
{
public float X, Y, Z;
}
// Conversion system
[FixedGroup]
public class ConversionSystem : ISystem
{
public void Run(IWorld world, float deltaTime)
{
using var cmd = world.BeginWrite();
foreach (var (entity, oldPos) in world.Query<OldPosition>())
{
// Convert to new format
cmd.AddComponent(entity, new Position
{
X = oldPos.X,
Y = oldPos.Y,
Z = 0
});
// Remove old component
cmd.RemoveComponent<OldPosition>(entity);
}
}
}
Legacy Support
Support legacy component structures:
// Legacy component
public struct LegacyHealth
{
public int CurrentHP;
public int MaxHP;
}
// Modern component
public struct Health
{
public float Current;
public float Max;
}
// Surrogate for compatibility
public struct HealthSurrogate
{
public float Current;
public float Max;
public static HealthSurrogate FromLegacy(LegacyHealth legacy)
{
return new HealthSurrogate
{
Current = legacy.CurrentHP,
Max = legacy.MaxHP
};
}
}
Patterns
Migration Pattern
Use surrogates during migration:
[FixedGroup]
public class MigrationSystem : ISystem
{
public void Run(IWorld world, float deltaTime)
{
using var cmd = world.BeginWrite();
// Find entities with old components
foreach (var (entity, oldComponent) in world.Query<OldComponent>())
{
// Create surrogate
var surrogate = ConvertToSurrogate(oldComponent);
cmd.AddComponent(entity, surrogate);
// Remove old component
cmd.RemoveComponent<OldComponent>(entity);
}
// Convert surrogates to new format
foreach (var (entity, surrogate) in world.Query<SurrogateComponent>())
{
var newComponent = ConvertFromSurrogate(surrogate);
cmd.AddComponent(entity, newComponent);
cmd.RemoveComponent<SurrogateComponent>(entity);
}
}
}
Temporary State Pattern
Use surrogates for temporary computation:
// Surrogate for intermediate calculation
public struct VelocitySurrogate
{
public float X, Y;
}
[FixedGroup]
public class PhysicsSystem : ISystem
{
public void Run(IWorld world, float deltaTime)
{
using var cmd = world.BeginWrite();
// Calculate velocity in surrogate
foreach (var (entity, pos, force) in world.Query<Position, Force>())
{
var velocity = CalculateVelocity(force);
cmd.AddOrReplaceComponent(entity, new VelocitySurrogate
{
X = velocity.X,
Y = velocity.Y
});
}
// Apply surrogate to actual component
foreach (var (entity, surrogate) in world.Query<VelocitySurrogate>())
{
cmd.AddOrReplaceComponent(entity, new Velocity
{
X = surrogate.X,
Y = surrogate.Y
});
cmd.RemoveComponent<VelocitySurrogate>(entity);
}
}
}
Best Practices
✅ Do
- Use for migration: Temporary during transitions
- Clear after use: Remove surrogates when done
- Document purpose: Explain why surrogates exist
- Keep minimal: Only when necessary
❌ Don't
- Don't keep long-term: Remove after migration
- Don't overuse: Prefer direct components
- Don't break contracts: Maintain component contracts
See Also
- Migration & PostMig - Version migration
- Components - Component system
- Advanced Topics - Advanced patterns