namespace OECS; /// /// Accumulates 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. /// internal class ChangeSet { private readonly List _changes = new(); private readonly Dictionary<(Entity Entity, ChangeKind Kind, Type? ComponentType), int> _dedup = new(); /// /// All accumulated changes in insertion order. /// public IReadOnlyList Changes => _changes; /// /// Number of changes in this set. /// public int Count => _changes.Count; /// /// Records that an entity was created. /// public void MarkEntityAdded(Entity entity) { TryAdd(new EntityChange(entity, ChangeKind.EntityAdded)); } /// /// Records that an entity was destroyed. /// public void MarkEntityRemoved(Entity entity) { TryAdd(new EntityChange(entity, ChangeKind.EntityRemoved)); } /// /// Records that a component was added to an entity. /// public void MarkComponentAdded(Entity entity, Type componentType) { TryAdd(new EntityChange(entity, ChangeKind.ComponentAdded, componentType)); } /// /// Records that a component was removed from an entity. /// public void MarkComponentRemoved(Entity entity, Type componentType) { TryAdd(new EntityChange(entity, ChangeKind.ComponentRemoved, componentType)); } /// /// Records that a component's value was modified. /// Must be called explicitly by user code via . /// public void MarkComponentModified(Entity entity, Type componentType) { TryAdd(new EntityChange(entity, ChangeKind.ComponentModified, componentType)); } /// /// Clears all accumulated changes. /// 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); } }