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}