A game about forced loneliness, made by TACStudios
1using System; 2using System.Collections; 3using System.Collections.Generic; 4 5namespace Unity.VisualScripting 6{ 7 public class MergedKeyedCollection<TKey, TItem> : IMergedCollection<TItem> 8 { 9 public MergedKeyedCollection() : base() 10 { 11 collections = new Dictionary<Type, IKeyedCollection<TKey, TItem>>(); 12 collectionsLookup = new Dictionary<Type, IKeyedCollection<TKey, TItem>>(); 13 } 14 15 protected readonly Dictionary<Type, IKeyedCollection<TKey, TItem>> collections; 16 17 // Used for performance optimization when finding the right collection for a type 18 protected readonly Dictionary<Type, IKeyedCollection<TKey, TItem>> collectionsLookup; 19 20 public TItem this[TKey key] 21 { 22 get 23 { 24 if (key == null) 25 { 26 throw new ArgumentNullException(nameof(key)); 27 } 28 29 foreach (var collectionByType in collections) 30 { 31 if (collectionByType.Value.Contains(key)) 32 { 33 return collectionByType.Value[key]; 34 } 35 } 36 37 throw new KeyNotFoundException(); 38 } 39 } 40 41 public int Count 42 { 43 get 44 { 45 int count = 0; 46 47 foreach (var collectionByType in collections) 48 { 49 count += collectionByType.Value.Count; 50 } 51 52 return count; 53 } 54 } 55 56 public bool IsReadOnly => false; 57 58 public bool Includes<TSubItem>() where TSubItem : TItem 59 { 60 return Includes(typeof(TSubItem)); 61 } 62 63 public bool Includes(Type elementType) 64 { 65 return GetCollectionForType(elementType, false) != null; 66 } 67 68 public IKeyedCollection<TKey, TSubItem> ForType<TSubItem>() where TSubItem : TItem 69 { 70 return ((VariantKeyedCollection<TItem, TSubItem, TKey>)GetCollectionForType(typeof(TSubItem))).implementation; 71 } 72 73 public virtual void Include<TSubItem>(IKeyedCollection<TKey, TSubItem> collection) where TSubItem : TItem 74 { 75 var type = typeof(TSubItem); 76 var variantCollection = new VariantKeyedCollection<TItem, TSubItem, TKey>(collection); 77 collections.Add(type, variantCollection); 78 collectionsLookup.Add(type, variantCollection); 79 } 80 81 protected IKeyedCollection<TKey, TItem> GetCollectionForItem(TItem item) 82 { 83 Ensure.That(nameof(item)).IsNotNull(item); 84 85 return GetCollectionForType(item.GetType()); 86 } 87 88 protected IKeyedCollection<TKey, TItem> GetCollectionForType(Type type, bool throwOnFail = true) 89 { 90 Ensure.That(nameof(type)).IsNotNull(type); 91 92 if (collectionsLookup.TryGetValue(type, out var collection)) 93 { 94 return collection; 95 } 96 97 foreach (var collectionByType in collections) 98 { 99 if (collectionByType.Key.IsAssignableFrom(type)) 100 { 101 collection = collectionByType.Value; 102 collectionsLookup.Add(type, collection); 103 return collection; 104 } 105 } 106 107 if (throwOnFail) 108 { 109 throw new InvalidOperationException($"No sub-collection available for type '{type}'."); 110 } 111 else 112 { 113 return null; 114 } 115 } 116 117 protected IKeyedCollection<TKey, TItem> GetCollectionForKey(TKey key, bool throwOnFail = true) 118 { 119 // Optim: avoid boxing here. 120 // Ensure.That(nameof(key)).IsNotNull(key); 121 122 foreach (var collectionsByType in collections) 123 { 124 if (collectionsByType.Value.Contains(key)) 125 { 126 return collectionsByType.Value; 127 } 128 } 129 130 if (throwOnFail) 131 { 132 throw new InvalidOperationException($"No sub-collection available for key '{key}'."); 133 } 134 else 135 { 136 return null; 137 } 138 } 139 140 public bool TryGetValue(TKey key, out TItem value) 141 { 142 var collection = GetCollectionForKey(key, false); 143 144 value = default(TItem); 145 146 return collection != null && collection.TryGetValue(key, out value); 147 } 148 149 public virtual void Add(TItem item) 150 { 151 GetCollectionForItem(item).Add(item); 152 } 153 154 public void Clear() 155 { 156 foreach (var collection in collections.Values) 157 { 158 collection.Clear(); 159 } 160 } 161 162 public bool Contains(TItem item) 163 { 164 return GetCollectionForItem(item).Contains(item); 165 } 166 167 public bool Remove(TItem item) 168 { 169 return GetCollectionForItem(item).Remove(item); 170 } 171 172 public void CopyTo(TItem[] array, int arrayIndex) 173 { 174 if (array == null) 175 { 176 throw new ArgumentNullException(nameof(array)); 177 } 178 179 if (arrayIndex < 0) 180 { 181 throw new ArgumentOutOfRangeException(nameof(arrayIndex)); 182 } 183 184 if (array.Length - arrayIndex < Count) 185 { 186 throw new ArgumentException(); 187 } 188 189 var i = 0; 190 191 foreach (var collection in collections.Values) 192 { 193 collection.CopyTo(array, arrayIndex + i); 194 i += collection.Count; 195 } 196 } 197 198 public bool Contains(TKey key) 199 { 200 return GetCollectionForKey(key, false) != null; 201 } 202 203 public bool Remove(TKey key) 204 { 205 return GetCollectionForKey(key).Remove(key); 206 } 207 208 IEnumerator IEnumerable.GetEnumerator() 209 { 210 return GetEnumerator(); 211 } 212 213 IEnumerator<TItem> IEnumerable<TItem>.GetEnumerator() 214 { 215 return GetEnumerator(); 216 } 217 218 public Enumerator GetEnumerator() 219 { 220 return new Enumerator(this); 221 } 222 223 public struct Enumerator : IEnumerator<TItem> 224 { 225 private Dictionary<Type, IKeyedCollection<TKey, TItem>>.Enumerator collectionsEnumerator; 226 private TItem currentItem; 227 private IKeyedCollection<TKey, TItem> currentCollection; 228 private int indexInCurrentCollection; 229 private bool exceeded; 230 231 public Enumerator(MergedKeyedCollection<TKey, TItem> merged) : this() 232 { 233 collectionsEnumerator = merged.collections.GetEnumerator(); 234 } 235 236 public void Dispose() { } 237 238 public bool MoveNext() 239 { 240 // We just started, so we're not in a collection yet 241 if (currentCollection == null) 242 { 243 // Try to find the first collection 244 if (collectionsEnumerator.MoveNext()) 245 { 246 // There is at least a collection, start with this one 247 currentCollection = collectionsEnumerator.Current.Value; 248 249 if (currentCollection == null) 250 { 251 throw new InvalidOperationException("Merged sub collection is null."); 252 } 253 } 254 else 255 { 256 // There is no collection at all, stop 257 currentItem = default(TItem); 258 exceeded = true; 259 return false; 260 } 261 } 262 263 // Check if we're within the current collection 264 if (indexInCurrentCollection < currentCollection.Count) 265 { 266 // We are, return this element and move to the next 267 currentItem = currentCollection[indexInCurrentCollection]; 268 indexInCurrentCollection++; 269 return true; 270 } 271 272 // We're beyond the current collection, but there may be more, 273 // and because there may be many empty collections, we need to check 274 // them all until we find an element, not just the next one 275 while (collectionsEnumerator.MoveNext()) 276 { 277 currentCollection = collectionsEnumerator.Current.Value; 278 indexInCurrentCollection = 0; 279 280 if (currentCollection == null) 281 { 282 throw new InvalidOperationException("Merged sub collection is null."); 283 } 284 285 if (indexInCurrentCollection < currentCollection.Count) 286 { 287 currentItem = currentCollection[indexInCurrentCollection]; 288 indexInCurrentCollection++; 289 return true; 290 } 291 } 292 293 // We're beyond all collections, stop 294 currentItem = default(TItem); 295 exceeded = true; 296 return false; 297 } 298 299 public TItem Current => currentItem; 300 301 Object IEnumerator.Current 302 { 303 get 304 { 305 if (exceeded) 306 { 307 throw new InvalidOperationException(); 308 } 309 310 return Current; 311 } 312 } 313 314 void IEnumerator.Reset() 315 { 316 throw new InvalidOperationException(); 317 } 318 } 319 } 320}