4 using System.Collections;
5 using System.Collections.Concurrent;
6 using System.Collections.Generic;
7 using System.Diagnostics.CodeAnalysis;
9 using System.Security.Cryptography;
20 var interfaces = typeof(T).GetInterfaces();
28 Action<T, bool>? onAdd,
31 Action<ContentFile>? onAddOverrideFile,
32 Action<ContentFile>? onRemoveOverrideFile) : this()
37 OnAddOverrideFile = onAddOverrideFile;
38 OnRemoveOverrideFile = onRemoveOverrideFile;
53 private readonly Action<T, bool>? OnAdd =
null;
60 private readonly Action<T>? OnRemove =
null;
65 private readonly Action? OnSort =
null;
70 private readonly Action<ContentFile>? OnAddOverrideFile =
null;
75 private readonly Action<ContentFile>? OnRemoveOverrideFile =
null;
87 #if DEBUG && MODBREAKER
88 private readonly CursedDictionary<Identifier, PrefabSelector<T>> prefabs =
new CursedDictionary<Identifier, PrefabSelector<T>>();
90 private readonly ConcurrentDictionary<Identifier, PrefabSelector<T>> prefabs =
new ConcurrentDictionary<Identifier, PrefabSelector<T>>();
97 private readonly HashSet<ContentFile> overrideFiles =
new HashSet<ContentFile>();
100 private readonly
bool implementsVariants;
102 private bool IsPrefabOverriddenByFile(T prefab)
104 return topMostOverrideFile !=
null &&
108 private class InheritanceTreeCollection
116 public readonly HashSet<Node>
Inheritors =
new HashSet<Node>();
121 public InheritanceTreeCollection(
PrefabCollection<T> collection) { prefabCollection = collection; }
123 public readonly Dictionary<Identifier, Node> IdToNode =
new Dictionary<Identifier, Node>();
124 public readonly HashSet<Node> RootNodes =
new HashSet<Node>();
126 public Node? AddNodeAndInheritors(Identifier
id)
128 if (!prefabCollection.TryGet(
id, out T? _, requireInheritanceValid:
false)) {
return null; }
130 if (!IdToNode.TryGetValue(
id, out var node))
134 IdToNode.Add(
id, node);
143 var enumerator = prefabCollection.GetEnumerator(requireInheritanceValid:
false);
144 while (enumerator.MoveNext())
146 T p = enumerator.Current;
147 if (p is not IImplementsVariants<T> implementsVariants || implementsVariants.VariantOf !=
id)
151 var inheritorNode = AddNodeAndInheritors(p.Identifier);
152 if (inheritorNode is
null) {
continue; }
153 RootNodes.Remove(inheritorNode);
154 inheritorNode.Parent = node;
155 node.Inheritors.Add(inheritorNode);
160 private static void FindCycles(in Node node, HashSet<Node> uncheckedNodes)
162 HashSet<Node> checkedNodes =
new HashSet<Node>();
163 List<Node> hierarchyPositions =
new List<Node>();
164 Node? currNode = node;
167 if (!uncheckedNodes.Contains(currNode)) {
break; }
168 if (checkedNodes.Contains(currNode))
170 int index = hierarchyPositions.IndexOf(currNode);
171 throw new Exception(
"Inheritance cycle detected: "
172 +
string.Join(
", ", hierarchyPositions.Skip(index).Select(n => n.Identifier)));
174 checkedNodes.Add(currNode);
175 hierarchyPositions.Add(currNode);
176 currNode = currNode.Parent;
177 }
while (currNode !=
null);
178 uncheckedNodes.RemoveWhere(i => checkedNodes.Contains(i));
181 public void AddNodesAndInheritors(IEnumerable<Identifier> ids)
182 => ids.ForEach(
id => AddNodeAndInheritors(
id));
184 public void InvokeCallbacks()
186 HashSet<Node> uncheckedNodes = IdToNode.Values.ToHashSet();
187 IdToNode.Values.ForEach(v => PrefabCollection<T>.InheritanceTreeCollection.FindCycles(v, uncheckedNodes));
188 void invokeCallbacksForNode(Node node)
190 if (!prefabCollection.TryGet(node.Identifier, out var p, requireInheritanceValid:
false) ||
191 p is not IImplementsVariants<T> prefab) {
return; }
192 if (!prefab.VariantOf.IsEmpty && prefabCollection.TryGet(prefab.VariantOf, out T? parent, requireInheritanceValid:
false))
194 prefab.InheritFrom(parent);
195 prefab.ParentPrefab = parent;
197 node.Inheritors.ForEach(invokeCallbacksForNode);
199 RootNodes.ForEach(invokeCallbacksForNode);
203 private static bool IsInheritanceValid(T? prefab)
205 if (prefab ==
null) {
return false; }
207 prefab is not IImplementsVariants<T> implementsVariants ||
208 (implementsVariants.VariantOf.IsEmpty || (implementsVariants.ParentPrefab !=
null && IsInheritanceValid(implementsVariants.ParentPrefab)));
211 private void HandleInheritance(Identifier prefabIdentifier)
212 => HandleInheritance(prefabIdentifier.ToEnumerable());
214 private void HandleInheritance(IEnumerable<Identifier> identifiers)
216 if (!implementsVariants) {
return; }
217 foreach (var
id in identifiers)
219 if (!
TryGet(
id, out T? prefab, requireInheritanceValid:
false)) {
continue; }
220 if (prefab is IImplementsVariants<T> implementsVariants && !implementsVariants.VariantOf.IsEmpty)
223 implementsVariants.ParentPrefab =
null;
226 InheritanceTreeCollection inheritanceTreeCollection =
new InheritanceTreeCollection(
this);
227 inheritanceTreeCollection.AddNodesAndInheritors(identifiers);
228 inheritanceTreeCollection.InvokeCallbacks();
234 public IEnumerable<KeyValuePair<Identifier, PrefabSelector<T>>>
AllPrefabs
238 foreach (var kvp
in prefabs)
240 var prefab = kvp.Value.ActivePrefab;
241 if (!IsInheritanceValid(prefab)) {
continue; }
252 public T
this[Identifier identifier]
257 var prefab = prefabs[identifier].ActivePrefab;
258 if (prefab !=
null && !IsPrefabOverriddenByFile(prefab) &&
259 IsInheritanceValid(prefab))
263 throw new IndexOutOfRangeException($
"Prefab of identifier \"{identifier}\" cannot be returned because it was overridden by \"{topMostOverrideFile!.Path}\"");
267 public T
this[
string identifier]
275 return this[identifier.ToIdentifier()];
285 public bool TryGet(Identifier identifier, [NotNullWhen(
true)] out T? result)
287 return TryGet(identifier, out result, requireInheritanceValid:
true);
290 private bool TryGet(Identifier identifier, [NotNullWhen(
true)] out T? result,
bool requireInheritanceValid)
293 if (prefabs.TryGetValue(identifier, out
PrefabSelector<T>? selector) && selector.ActivePrefab !=
null)
295 result = selector!.ActivePrefab;
296 return !requireInheritanceValid || IsInheritanceValid(result);
305 public bool TryGet(
string identifier, out T? result)
306 =>
TryGet(identifier.ToIdentifier(), out result);
308 public IEnumerable<Identifier>
Keys => prefabs.Keys;
316 public T?
Find(Predicate<T> predicate)
319 foreach (var kpv
in prefabs)
321 if (kpv.Value.ActivePrefab is T p && predicate(p))
337 return TryGet(identifier, out _);
340 public bool ContainsKey(
string k) => prefabs.ContainsKey(k.ToIdentifier());
352 return prefabs[prefab.Identifier].IsOverride(prefab);
366 public void Add(T prefab,
bool isOverride)
369 if (prefab.Identifier.IsEmpty)
371 throw new ArgumentException($
"Prefab has no identifier!");
374 bool selectorExists = prefabs.TryGetValue(prefab.Identifier, out
PrefabSelector<T>? selector);
381 if (!selector.IsEmpty)
387 using (MD5 md5 = MD5.Create())
389 prefabWithUintIdentifier.UintIdentifier = ToolBoxCore.IdentifierToUint32Hash(prefab.Identifier, md5);
394 p.Identifier != prefab.Identifier
396 && otherPrefab.
UintIdentifier == prefabWithUintIdentifier.UintIdentifier);
397 for (T? collision = findCollision(); collision !=
null; collision = findCollision())
399 DebugConsole.ThrowError($
"Hashing collision when generating uint identifiers for {typeof(T).Name}: {prefab.Identifier} has the same UintIdentifier as {collision.Identifier} ({prefabWithUintIdentifier.UintIdentifier})");
400 prefabWithUintIdentifier.UintIdentifier++;
405 selector.Add(prefab, isOverride);
409 if (!prefabs.TryAdd(prefab.Identifier, selector)) {
throw new Exception($
"Failed to add selector for \"{prefab.Identifier}\""); }
411 OnAdd?.Invoke(prefab, isOverride);
412 HandleInheritance(prefab.Identifier);
422 OnRemove?.Invoke(prefab);
424 if (!prefabs[prefab.Identifier].Contains(prefab)) {
return; }
425 prefabs[prefab.Identifier].Remove(prefab);
427 if (prefabs[prefab.Identifier].IsEmpty)
429 prefabs.TryRemove(prefab.Identifier, out _);
431 HandleInheritance(prefab.Identifier);
440 HashSet<Identifier> clearedIdentifiers =
new HashSet<Identifier>();
441 foreach (var kpv
in prefabs)
443 kpv.Value.RemoveByFile(file, OnRemove);
444 if (kpv.Value.IsEmpty) { clearedIdentifiers.Add(kpv.Key); }
447 foreach (var identifier
in clearedIdentifiers)
449 prefabs.TryRemove(identifier, out _);
460 if (!overrideFiles.Contains(file))
462 overrideFiles.Add(file);
464 OnAddOverrideFile?.Invoke(file);
473 if (overrideFiles.Contains(file))
475 overrideFiles.Remove(file);
477 OnRemoveOverrideFile?.Invoke(file);
486 foreach (var kvp
in prefabs)
490 topMostOverrideFile = overrideFiles.Any() ? overrideFiles.First(f1 => overrideFiles.All(f2 => f1.ContentPackage.Index >= f2.ContentPackage.Index)) :
null;
492 HandleInheritance(this.
Select(p => p.Identifier));
494 var enumerator =
GetEnumerator(requireInheritanceValid:
false);
495 while (enumerator.MoveNext())
497 T p = enumerator.Current;
500 DebugConsole.ThrowError(
501 $
"Error in content package \"{p.ContentFile.ContentPackage.Name}\": " +
502 $
"could not find the prefab \"{implementsVariants.VariantOf}\" the prefab \"{p.Identifier}\" is configured as a variant of.");
517 private IEnumerator<T>
GetEnumerator(
bool requireInheritanceValid)
520 foreach (var kvp
in prefabs)
522 var prefab = kvp.Value.ActivePrefab;
523 if (prefab ==
null || IsPrefabOverriddenByFile(prefab)) {
continue; }
524 if (requireInheritanceValid && !IsInheritanceValid(prefab)) {
continue; }
533 IEnumerator IEnumerable.GetEnumerator()
Base class for content file types, which are loaded from filelist.xml via reflection....
readonly ContentPackage ContentPackage
readonly HashSet< Node > Inheritors
readonly Identifier Identifier
Node(Identifier identifier)
void Add(T prefab, bool isOverride)
Add a prefab to the collection. If not marked as an override, fail if a prefab with the same identifi...
PrefabCollection(Action< T, bool >? onAdd, Action< T >? onRemove, Action? onSort, Action< ContentFile >? onAddOverrideFile, Action< ContentFile >? onRemoveOverrideFile)
Constructor with OnAdd and OnRemove callbacks provided.
IEnumerable< KeyValuePair< Identifier, PrefabSelector< T > > > AllPrefabs
AllPrefabs exposes all prefabs instead of just the active ones.
T? Find(Predicate< T > predicate)
Finds the first active prefab that returns true given the predicate, or null if no such prefab is fou...
void SortAll()
Sorts all prefabs in the collection based on the content package load order.
IEnumerator< T > GetEnumerator()
GetEnumerator implementation to enable foreach
bool ContainsKey(string k)
PrefabCollection(Action? onSort)
Constructor with only the OnSort callback provided.
void AddOverrideFile(ContentFile file)
Adds an override file to the collection.
bool IsOverride(T prefab)
Determines whether a prefab is implemented as an override or not.
IEnumerable< Identifier > Keys
void RemoveOverrideFile(ContentFile file)
Removes an override file from the collection.
void Remove(T prefab)
Removes a prefab from the collection.
bool TryGet(Identifier identifier, [NotNullWhen(true)] out T? result)
Returns true if a prefab with the identifier exists, false otherwise.
PrefabCollection()
Default constructor.
bool ContainsKey(Identifier identifier)
Returns true if a prefab with the given identifier exists, false otherwise.
bool TryGet(string identifier, out T? result)
void RemoveByFile(ContentFile file)
Removes all prefabs that were loaded from a certain file.
static void DisallowCallFromConstructor()
Prefab that has a property serves as a deterministic hash of a prefab's identifier....