diff --git a/Engine.Core/Helpers/FastListOrdered.cs b/Engine.Core/Helpers/FastListOrdered.cs new file mode 100644 index 0000000..d99c826 --- /dev/null +++ b/Engine.Core/Helpers/FastListOrdered.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Engine.Core; + +/// +/// TODO This is VEERY experimental, and doesn't work well with the indices access. Use with caution +/// +/// +/// +public class FastListOrdered : IList, IReadOnlyList, IEnumerable where TItem : notnull where TIndex : IComparable +{ + private readonly SortedDictionary> items = null!; + + private readonly Func getIndexFunc = null!; + private readonly IComparer sortBy = null!; + + private int count = 0; + public int Count => count; + + public bool IsReadOnly { get; set; } = false; + + public TItem this[int index] + { + get { (TIndex tIndex, int i) = GetAt(index); return items[tIndex][i]; } + set + { + if (IsReadOnly) + throw new System.Data.ReadOnlyException(); + (TIndex tIndex, int i) = GetAt(index); items[tIndex][i] = value; + } + } + + private (TIndex TIndex, int i) GetAt(Index index) + { + int actualIndex = index.IsFromEnd + ? count - index.Value + : index.Value; + + if (actualIndex < 0 || actualIndex >= count) + throw new IndexOutOfRangeException(); + + int leftIndex = actualIndex; + foreach ((TIndex i, FastList list) in items) + { + if (leftIndex < list.Count) + return (i, leftIndex); + leftIndex -= list.Count; + } + throw new IndexOutOfRangeException(); + } + + public int IndexOf(TItem item) + { + int indexCounter = 0; + foreach ((TIndex index, FastList list) in items) + { + int i = list.IndexOf(item); + if (i != -1) + return indexCounter + i; + indexCounter += list.Count; + } + + return -1; + } + + public void Add(TItem item) + { + if (IsReadOnly) + throw new System.Data.ReadOnlyException(); + + TIndex key = getIndexFunc(item); + if (!items.TryGetValue(key, out FastList? list)) + items[key] = list = []; + + list.Add(item); + count++; + } + + public void Insert(int index, TItem item) + { + if (IsReadOnly) + throw new System.Data.ReadOnlyException(); + + TIndex tIndex = getIndexFunc(item); + if (!items.TryGetValue(tIndex, out FastList? list)) + items[tIndex] = list = []; + + list.Insert(index, item); + count++; + } + + public bool Remove(TItem item) + { + if (IsReadOnly) + throw new System.Data.ReadOnlyException(); + + TIndex index = getIndexFunc(item); + if (!items.TryGetValue(index, out FastList? list)) + throw new Exceptions.NotFoundException($"Index of '{index}' is not found in the collector"); + + if (!list.Remove(item)) + return false; + + count--; + return true; + } + + public void RemoveAt(int index) + { + if (IsReadOnly) + throw new System.Data.ReadOnlyException(); + + (TIndex tIndex, int i) = GetAt(index); + items[tIndex].RemoveAt(i); + count--; + } + + public void Clear() + { + if (IsReadOnly) + throw new System.Data.ReadOnlyException(); + + foreach ((TIndex index, FastList list) in items) + list.Clear(); + + count = 0; + } + + public bool Contains(TItem item) + { + foreach ((TIndex index, FastList list) in items) + if (list.Contains(item)) + return true; + return false; + } + + public void CopyTo(TItem[] array, int arrayIndex) + { + int indexCounter = 0; + + foreach ((TIndex index, FastList list) in items) + { + list.CopyTo(array, indexCounter); + indexCounter += list.Count; + } + } + + public IEnumerator GetEnumerator() + { + foreach ((TIndex index, FastList list) in items) + foreach (TItem item in list) + yield return item; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public FastListOrdered(Func getIndexFunc, Comparison sortBy) + { + this.getIndexFunc = getIndexFunc; + this.sortBy = Comparer.Create(sortBy); + items = new(this.sortBy); + } + + public FastListOrdered(Func getIndexFunc, IComparer sortBy) + { + this.getIndexFunc = getIndexFunc; + this.sortBy = sortBy; + items = new(sortBy); + } +}