using System.Collections.Concurrent; using Engine.Util; namespace Engine.Scene; public class Hierarchy where T : class { internal Dictionary.KeyCollection Objects => _parentLookup.Keys; private readonly Dictionary, IList> _childrenLookup = new(); private readonly Dictionary _parentLookup = new(); private readonly ConcurrentQueue _hierarchyActions = new(); public Hierarchy() { _childrenLookup.Add(new NullableObject(), new List()); } internal void ProcessChanges() { while (_hierarchyActions.TryDequeue(out var action)) { action(); } } public void Add(T parObj) { _hierarchyActions.Enqueue(() => { if (_parentLookup.ContainsKey(parObj)) { return; } _childrenLookup.Add(parObj, new List()); _parentLookup.Add(parObj, null); _childrenLookup[null].Add(parObj); }); } public void Remove(T parObj) { foreach (var child in GetChildren(parObj)) { Remove(child); } _hierarchyActions.Enqueue(() => { if (!Contains(parObj)) { return; } var parent = GetParent(parObj); _childrenLookup[parent].Remove(parObj); _parentLookup.Remove(parObj); _childrenLookup.Remove(parObj); }); } public void AddChild(T parParent, T parChild) { SetParent(parChild, parParent); } private void SetParent(T parChild, T? parParent) { if (parChild.Equals(parParent)) { throw new InvalidOperationException("Child cannot be parent"); } _hierarchyActions.Enqueue(() => { if (IsInHierarchy(parChild, parParent)) { throw new InvalidOperationException("Parent is a child of child"); } var oldParent = GetParent(parChild); _childrenLookup[oldParent].Remove(parChild); _childrenLookup[parParent].Add(parChild); _parentLookup[parChild] = parParent; }); } public bool Contains(T parObj) { return _parentLookup.ContainsKey(parObj) && _childrenLookup.ContainsKey(parObj); } public T? GetParent(T parChild) { return _parentLookup.TryGetValue(parChild, out var parent) ? parent : throw new InvalidOperationException($"Child {parChild} is not in hierarchy"); } public IEnumerable GetChildren(T? parParent = null) { return _childrenLookup.TryGetValue(parParent, out var children) ? children : Enumerable.Empty(); } public IEnumerable GetAllChildren(T? parParent = null) { var children = GetChildren(parParent); foreach (var child in children) { foreach (var descendant in GetAllChildren(child)) { yield return descendant; } yield return child; } } public bool IsInHierarchy(T? parAncestor, T? parChild) { if (parChild == null) // if child is null (root), then it is not in hierarchy, as root can not have a parent { return false; } if (parAncestor == null) // if ancestor is null (root), then child is not in hierarchy, as root is not a parent { return false; } if (parAncestor.Equals(parChild)) { return true; } var parent = GetParent(parChild); if (parent == null) { return false; } if (parAncestor.Equals(parent)) { return true; } return IsInHierarchy(parAncestor, parent); } }