using System; using System.Collections; using System.Collections.Generic; namespace Unity.VisualScripting { // The advantage of this class is not to provide list accessors // for merged lists (which would be confusing), but rather that unlike // merged collection, it can provide a zero-allocation enumerator. // OPTIM note: Dictionary<,>.Values allocated memory the first time, so avoid it if possible public class MergedList : IMergedCollection { public MergedList() { lists = new Dictionary>(); } protected readonly Dictionary> lists; public int Count { get { int count = 0; foreach (var listByType in lists) { count += listByType.Value.Count; } return count; } } public bool IsReadOnly => false; public virtual void Include(IList list) where TI : T { lists.Add(typeof(TI), new VariantList(list)); } public bool Includes() where TI : T { return Includes(typeof(TI)); } public bool Includes(Type elementType) { return GetListForType(elementType, false) != null; } public IList ForType() where TI : T { return ((VariantList)GetListForType(typeof(TI))).implementation; } protected IList GetListForItem(T item) { Ensure.That(nameof(item)).IsNotNull(item); return GetListForType(item.GetType()); } protected IList GetListForType(Type type, bool throwOnFail = true) { if (lists.ContainsKey(type)) { return lists[type]; } foreach (var listByType in lists) { if (listByType.Key.IsAssignableFrom(type)) { return listByType.Value; } } if (throwOnFail) { throw new InvalidOperationException($"No sub-collection available for type '{type}'."); } else { return null; } } public bool Contains(T item) { return GetListForItem(item).Contains(item); } public virtual void Add(T item) { GetListForItem(item).Add(item); } public virtual void Clear() { foreach (var listByType in lists) { listByType.Value.Clear(); } } public virtual bool Remove(T item) { return GetListForItem(item).Remove(item); } public void CopyTo(T[] array, int arrayIndex) { if (array == null) { throw new ArgumentNullException(nameof(array)); } if (arrayIndex < 0) { throw new ArgumentOutOfRangeException(nameof(arrayIndex)); } if (array.Length - arrayIndex < Count) { throw new ArgumentException(); } var i = 0; foreach (var listByType in lists) { var list = listByType.Value; list.CopyTo(array, arrayIndex + i); i += list.Count; } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public Enumerator GetEnumerator() { return new Enumerator(this); } public struct Enumerator : IEnumerator { private Dictionary>.Enumerator listsEnumerator; private T currentItem; private IList currentList; private int indexInCurrentList; private bool exceeded; public Enumerator(MergedList merged) : this() { listsEnumerator = merged.lists.GetEnumerator(); } public void Dispose() { } public bool MoveNext() { // We just started, so we're not in a list yet if (currentList == null) { // Try to find the first list if (listsEnumerator.MoveNext()) { // There is at least a list, start with this one currentList = listsEnumerator.Current.Value; if (currentList == null) { throw new InvalidOperationException("Merged sub list is null."); } } else { // There is no list at all, stop currentItem = default(T); exceeded = true; return false; } } // Check if we're within the current list if (indexInCurrentList < currentList.Count) { // We are, return this element and move to the next currentItem = currentList[indexInCurrentList]; indexInCurrentList++; return true; } // We're beyond the current list, but there may be more, // and because there may be many empty lists, we need to check // them all until we find an element, not just the next one while (listsEnumerator.MoveNext()) { currentList = listsEnumerator.Current.Value; indexInCurrentList = 0; if (currentList == null) { throw new InvalidOperationException("Merged sub list is null."); } if (indexInCurrentList < currentList.Count) { currentItem = currentList[indexInCurrentList]; indexInCurrentList++; return true; } } // We're beyond all lists, stop currentItem = default(T); exceeded = true; return false; } public T Current => currentItem; Object IEnumerator.Current { get { if (exceeded) { throw new InvalidOperationException(); } return Current; } } void IEnumerator.Reset() { throw new InvalidOperationException(); } } } }