oecs-sharp/OECS/ChangeSet.cs

116 lines
3.7 KiB
C#
Raw Normal View History

namespace OECS;
/// <summary>
/// Accumulates <see cref="EntityChange"/> entries during a system run.
///
/// Deduplication: if the same (entity, kind, componentType) change is marked
/// multiple times, only one entry is kept. However, if a structurally opposed
/// change arrives (Added vs Removed) for the same (entity, componentType),
/// the old entry is removed so observers see the full state transition.
/// </summary>
internal class ChangeSet
{
private readonly List<EntityChange> _changes = new();
private readonly Dictionary<(Entity Entity, ChangeKind Kind, Type? ComponentType), int> _dedup = new();
/// <summary>
/// All accumulated changes in insertion order.
/// </summary>
public IReadOnlyList<EntityChange> Changes => _changes;
/// <summary>
/// Number of changes in this set.
/// </summary>
public int Count => _changes.Count;
/// <summary>
/// Records that an entity was created.
/// </summary>
public void MarkEntityAdded(Entity entity)
{
TryAdd(new EntityChange(entity, ChangeKind.EntityAdded));
}
/// <summary>
/// Records that an entity was destroyed.
/// </summary>
public void MarkEntityRemoved(Entity entity)
{
TryAdd(new EntityChange(entity, ChangeKind.EntityRemoved));
}
/// <summary>
/// Records that a component was added to an entity.
/// </summary>
public void MarkComponentAdded(Entity entity, Type componentType)
{
TryAdd(new EntityChange(entity, ChangeKind.ComponentAdded, componentType));
}
/// <summary>
/// Records that a component was removed from an entity.
/// </summary>
public void MarkComponentRemoved(Entity entity, Type componentType)
{
TryAdd(new EntityChange(entity, ChangeKind.ComponentRemoved, componentType));
}
/// <summary>
/// Records that a component's value was modified.
/// Must be called explicitly by user code via <see cref="World.MarkModified{T}"/>.
/// </summary>
public void MarkComponentModified(Entity entity, Type componentType)
{
TryAdd(new EntityChange(entity, ChangeKind.ComponentModified, componentType));
}
/// <summary>
/// Records that the source set for a relationship type on a target entity
/// was reordered.
/// </summary>
public void MarkRelationshipReordered(Entity target, Type relationshipType)
{
TryAdd(new EntityChange(target, ChangeKind.RelationshipReordered, relationshipType));
}
/// <summary>
/// Clears all accumulated changes.
/// </summary>
public void Clear()
{
_changes.Clear();
_dedup.Clear();
}
private void TryAdd(EntityChange change)
{
var key = (change.Entity, change.Kind, change.ComponentType);
if (_dedup.ContainsKey(key))
{
// Same (entity, kind, componentType) already recorded — deduplicate.
return;
}
// When a component is re-added after being removed within the same
// batch, remove the old ComponentRemoved entry so observers see the
// full Add → Remove → Add sequence.
if (change.Kind == ChangeKind.ComponentAdded && change.ComponentType != null)
{
var removedKey = (change.Entity, ChangeKind.ComponentRemoved, change.ComponentType);
if (_dedup.Remove(removedKey, out int oldIndex))
{
_changes.RemoveAt(oldIndex);
// Adjust indices for all entries that shifted down.
foreach (var k in _dedup.Keys.ToList())
{
if (_dedup[k] > oldIndex)
_dedup[k]--;
}
}
}
_dedup[key] = _changes.Count;
_changes.Add(change);
}
}