Code/Util/DynamicDirectory.cs
namespace Nodebox;
public class DynamicDirectory<TBase> : IDynamicDirectory<TBase> where TBase : class, DynamicDirectory<TBase>.IElement {
private Dictionary<Type, BiDictionary<Guid, TBase>> _store = [];
internal BiDictionary<Guid, TBase> GetStore(Type type) {
Assert.AreEqual(TypeLibrary.GetType(type).BaseType.TargetType, typeof(TBase), $"{type} has to be a direct descendant of {nameof(TBase)}");
return _store.GetOrCreate(type);
}
internal BiDictionary<Guid, TBase> GetStore<T>() => GetStore(typeof(T));
public int CountOf(Type type) => GetStore(type).Count;
public int CountOf<T>() => CountOf(typeof(T));
public IEnumerable<TBase> GetAll(Type type) => GetStore(type).Reverse.Keys;
public IEnumerable<T> GetAll<T>() where T : TBase => GetAll(typeof(T)).Select(x => (T)x);
private bool IsFreeId(Guid id) => _store.All(x => !x.Value.Forward.ContainsKey(id));
private Guid UniqueGuid() {
var guid = Guid.NewGuid();
while (!IsFreeId(guid)) {
guid = Guid.NewGuid();
}
return guid;
}
private void TBaseOnDestroy(TBase element) {
Remove(element.DirectoryType, element);
}
public T Add<T>(T element) where T : TBase {
// Log.Info(element);
var store = GetStore(element.DirectoryType);
if (store.Reverse.ContainsKey(element)) { return element; }
store.Add(UniqueGuid(), element);
element.OnDestroy += TBaseOnDestroy;
return element;
}
public T Add<T>(T element, Guid previous) where T : TBase {
var store = GetStore(element.DirectoryType);
if (store.Forward.TryGetValue(previous, out var existing) && existing.Equals(element)) {
store.Remove(previous);
}
return Add(element);
}
public void AddRange<T>(IEnumerable<T> elements) where T : TBase {
foreach (var element in elements) {
Add(element);
}
}
public bool Remove(Type type, TBase element) {
var success = GetStore(type).RemoveReverse(element);
if (success) {
element.OnDestroy -= TBaseOnDestroy;
}
return success;
}
public bool Remove<T>(T element) where T : TBase => Remove(typeof(T), element);
public bool RemoveByGuid(Type type, Guid guid) {
if (!GetStore(type).Forward.TryGetValue(guid, out var element)) {
return false;
}
Remove(type, element);
element.OnDestroy -= TBaseOnDestroy;
return true;
}
public bool RemoveByGuid<T>(Guid guid) where T : TBase => RemoveByGuid(typeof(T), guid);
public TBase FindByGuid(Type type, Guid guid) {
if (GetStore(type).Forward.TryGetValue(guid, out var found)) {
return found;
}
return null;
}
public T FindByGuid<T>(Guid guid) where T : TBase => (T)FindByGuid(typeof(T), guid);
public Guid? Find(Type type, TBase element) {
if (GetStore(type).Reverse.TryGetValue(element, out var found)) {
return found;
}
return null;
}
public Guid? Find<T>(TBase element) where T : TBase => Find(typeof(T), element);
public bool Contains(Type type, TBase element) => Find(type, element).HasValue;
public bool Contains<T>(TBase element) where T : TBase => Find<T>(element).HasValue;
public void Clear() {
_store.Clear();
}
public interface IElement {
public Type DirectoryType { get; }
public Action<TBase> OnDestroy { get; set; }
}
}
public interface IDynamicDirectory<TBase> {
public int CountOf(Type type);
public int CountOf<T>();
public IEnumerable<TBase> GetAll(Type type);
public IEnumerable<T> GetAll<T>() where T : TBase;
public T Add<T>(T element) where T : TBase;
public T Add<T>(T element, Guid previous) where T : TBase;
public void AddRange<T>(IEnumerable<T> elements) where T : TBase;
public bool Remove(Type type, TBase element);
public bool Remove<T>(T element) where T : TBase;
public bool RemoveByGuid(Type type, Guid guid);
public bool RemoveByGuid<T>(Guid guid) where T : TBase;
public TBase FindByGuid(Type type, Guid guid);
public T FindByGuid<T>(Guid guid) where T : TBase;
public Guid? Find(Type type, TBase element);
public Guid? Find<T>(TBase element) where T : TBase;
public bool Contains(Type type, TBase element);
public bool Contains<T>(TBase element) where T : TBase;
public void Clear();
}
public static class DynamicDirectoryIElementExtensions {
extension<T>(DynamicDirectory<T>.IElement element) where T : class, DynamicDirectory<T>.IElement {
public Action<T> OnDestroy {
get => element.OnDestroy;
set => element.OnDestroy = value;
}
}
}