155 lines
3.3 KiB
C#
155 lines
3.3 KiB
C#
using System.Collections.Concurrent;
|
|
using Engine.Util;
|
|
|
|
namespace Engine.Scene;
|
|
|
|
public class Hierarchy<T>
|
|
where T : class
|
|
{
|
|
internal Dictionary<T, T?>.KeyCollection Objects => _parentLookup.Keys;
|
|
|
|
private readonly Dictionary<NullableObject<T>, IList<T>> _childrenLookup = new();
|
|
private readonly Dictionary<T, T?> _parentLookup = new();
|
|
private readonly ConcurrentQueue<Action> _hierarchyActions = new();
|
|
|
|
public Hierarchy()
|
|
{
|
|
_childrenLookup.Add(new NullableObject<T>(), new List<T>());
|
|
}
|
|
|
|
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<T>());
|
|
_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<T> GetChildren(T? parParent = null)
|
|
{
|
|
return _childrenLookup.TryGetValue(parParent, out var children) ? children : Enumerable.Empty<T>();
|
|
}
|
|
|
|
public IEnumerable<T> 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);
|
|
}
|
|
} |