.
This commit is contained in:
140
Engine/src/Scene/Hierarchy.cs
Normal file
140
Engine/src/Scene/Hierarchy.cs
Normal file
@@ -0,0 +1,140 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using Engine.Util;
|
||||
|
||||
namespace Engine.Scene;
|
||||
|
||||
public class Hierarchy<T> : IEnumerable<T>
|
||||
where T : class
|
||||
{
|
||||
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 obj)
|
||||
{
|
||||
_hierarchyActions.Enqueue(() =>
|
||||
{
|
||||
if (_parentLookup.ContainsKey(obj))
|
||||
throw new ArgumentException("Object is already added to hierarchy");
|
||||
|
||||
_childrenLookup.Add(obj, new List<T>());
|
||||
_parentLookup.Add(obj, null);
|
||||
_childrenLookup[null].Add(obj);
|
||||
});
|
||||
}
|
||||
|
||||
public void Remove(T obj)
|
||||
{
|
||||
foreach (var child in GetChildren(obj))
|
||||
Remove(child);
|
||||
|
||||
_hierarchyActions.Enqueue(() =>
|
||||
{
|
||||
var parent = GetParent(obj);
|
||||
_childrenLookup[parent].Remove(obj);
|
||||
|
||||
_parentLookup.Remove(obj);
|
||||
_childrenLookup.Remove(obj);
|
||||
});
|
||||
}
|
||||
|
||||
public void AddChild(T parent, T child)
|
||||
{
|
||||
SetParent(child, parent);
|
||||
}
|
||||
|
||||
private void SetParent(T child, T? parent)
|
||||
{
|
||||
if (child.Equals(parent))
|
||||
throw new InvalidOperationException("Child cannot be parent");
|
||||
|
||||
_hierarchyActions.Enqueue(() =>
|
||||
{
|
||||
if (IsInHierarchy(child, parent))
|
||||
throw new InvalidOperationException("Parent is a child of child");
|
||||
|
||||
var oldParent = GetParent(child);
|
||||
_childrenLookup[oldParent].Remove(child);
|
||||
|
||||
_childrenLookup[parent].Add(child);
|
||||
_parentLookup[child] = parent;
|
||||
});
|
||||
}
|
||||
|
||||
public bool Contains(T obj)
|
||||
{
|
||||
return _parentLookup.ContainsKey(obj) && _childrenLookup.ContainsKey(obj);
|
||||
}
|
||||
|
||||
public T? GetParent(T child)
|
||||
{
|
||||
return _parentLookup.TryGetValue(child, out var parent)
|
||||
? parent
|
||||
: throw new InvalidOperationException($"Child {child} is not in hierarchy");
|
||||
}
|
||||
|
||||
public IEnumerable<T> GetChildren(T? obj = null)
|
||||
{
|
||||
return _childrenLookup.TryGetValue(obj, out var children) ? children : Enumerable.Empty<T>();
|
||||
}
|
||||
|
||||
public bool IsInHierarchy(T? ancestor, T? child)
|
||||
{
|
||||
if (child == null) // if child is null (root), then it is not in hierarchy, as root can not have a parent
|
||||
return false;
|
||||
|
||||
if (ancestor == null) // if ancestor is null (root), then child is not in hierarchy, as root is not a parent
|
||||
return false;
|
||||
|
||||
if (ancestor.Equals(child))
|
||||
return true;
|
||||
|
||||
var parent = GetParent(child);
|
||||
|
||||
if (parent == null)
|
||||
return false;
|
||||
|
||||
if (ancestor.Equals(parent))
|
||||
return true;
|
||||
|
||||
return IsInHierarchy(ancestor, parent);
|
||||
}
|
||||
|
||||
public IEnumerable<T> GetAllChildren(T? obj = null)
|
||||
{
|
||||
var children = GetChildren(obj);
|
||||
|
||||
foreach (var child in children)
|
||||
{
|
||||
yield return child;
|
||||
|
||||
foreach (var descendant in GetAllChildren(child))
|
||||
{
|
||||
yield return descendant;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
return _parentLookup.Keys.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user