using System.Runtime.CompilerServices; namespace OECS; /// /// Maintains a reverse index for relationship components, mapping /// target entities to the set of source entities that point to them. /// /// Updated automatically by when relationship /// components are added, removed, or when entities are destroyed. /// internal class RelationshipIndex { /// /// Per relationship type: target entity → set of source entities. /// private readonly Dictionary>> _index = new(); /// /// Registers that a source entity now has a relationship pointing to a target. /// public void OnAdded(Type relationshipType, Entity source, Entity target) { if (!_index.TryGetValue(relationshipType, out var targetMap)) { targetMap = new Dictionary>(); _index[relationshipType] = targetMap; } if (!targetMap.TryGetValue(target, out var sources)) { sources = new HashSet(); targetMap[target] = sources; } sources.Add(source); } /// /// Unregisters a relationship from a source entity pointing to a target. /// public void OnRemoved(Type relationshipType, Entity source, Entity target) { if (!_index.TryGetValue(relationshipType, out var targetMap)) return; if (!targetMap.TryGetValue(target, out var sources)) return; sources.Remove(source); if (sources.Count == 0) targetMap.Remove(target); } /// /// Returns all source entities that have a relationship of the given type /// pointing to the specified target entity. /// public IReadOnlyCollection GetSources(Entity target) where T : struct, IRelationship { var type = typeof(T); if (!_index.TryGetValue(type, out var targetMap)) return Array.Empty(); if (!targetMap.TryGetValue(target, out var sources)) return Array.Empty(); return sources; } /// /// Returns all target entities that the given source entity points to /// via the specified relationship type. Returns an empty collection if none. /// public IReadOnlyCollection GetTargets(Type relationshipType, Entity source) { if (!_index.TryGetValue(relationshipType, out var targetMap)) return Array.Empty(); var result = new List(); foreach (var (target, sources) in targetMap) { if (sources.Contains(source)) result.Add(target); } return result; } /// /// Removes all index entries where the given entity appears as a source. /// Called before the entity's components are removed during destruction. /// public void RemoveAllSourcesForEntity(Entity entity) { foreach (var (_, targetMap) in _index) { foreach (var (target, sources) in targetMap) { sources.Remove(entity); } } // Clean up empty target entries. foreach (var (_, targetMap) in _index) { var emptyTargets = new List(); foreach (var (target, sources) in targetMap) { if (sources.Count == 0) emptyTargets.Add(target); } foreach (var target in emptyTargets) { targetMap.Remove(target); } } } /// /// Returns all relationship types for which the given entity is a target, /// along with the set of source entities pointing to it. /// Used during entity destruction to cascade-remove incoming relationships. /// public IEnumerable<(Type RelationshipType, IReadOnlyCollection Sources)> GetIncomingRelationships(Entity target) { foreach (var (relType, targetMap) in _index) { if (targetMap.TryGetValue(target, out var sources) && sources.Count > 0) { yield return (relType, sources); } } } /// /// Returns true if the index has any entries for the given relationship type. /// public bool HasType(Type relationshipType) { return _index.ContainsKey(relationshipType); } }