Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,36 @@ public ContentPath MutateContentPath(ContentPath path)
}

public readonly static ImmutableHashSet<TypeInfo> Types;


private ContentFile(ContentPackage contentPackage, ContentPath path, Md5Hash hash)
{
ContentPackage = contentPackage;
Path = path;
Hash = hash;
}
private class EmptyContentFile : ContentFile
{
public EmptyContentFile() : base(ContentPackage.Empty, ContentPath.Empty, Md5Hash.Blank)
{ }

public override void LoadFile()
{
throw new NotImplementedException();
}

public override void Sort()
{
throw new NotImplementedException();
}

public override void UnloadFile()
{
throw new NotImplementedException();
}
}

public readonly static ContentFile Empty = new EmptyContentFile();
static ContentFile()
{
Types = ReflectionUtils.GetDerivedNonAbstract<ContentFile>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,23 @@ public override string ToString()
? '\n' + stackTrace.CleanupStackTrace()
: string.Empty);
}
private ContentPackage()
{
Path = string.Empty;
Name = string.Empty;
GameVersion = Barotrauma.GameVersion.CurrentVersion;
ModVersion = string.Empty;
Hash = Md5Hash.Blank;
}
private class EmptyContentPackage : ContentPackage
{
public EmptyContentPackage() : base()
{ }
}

public static readonly Version MinimumHashCompatibleVersion = new Version(1, 1, 0, 0);

public static readonly ContentPackage Empty = new EmptyContentPackage();

public const string LocalModsDir = "LocalMods";
public static readonly string WorkshopModsDir = Barotrauma.IO.Path.Combine(
SaveUtil.DefaultSaveFolder,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ public static MapEntityPrefab FindByIdentifier(Identifier identifier)
[Serialize(1f, IsPropertySaveable.Yes), Editable(0.1f, 10f, DecimalCount = 3)]
public float Scale { get; protected set; }

protected MapEntityPrefab(Identifier identifier) : base(null, identifier) { }
protected MapEntityPrefab(Identifier identifier) : base(ContentFile.Empty, identifier) { }

public MapEntityPrefab(ContentXElement element, ContentFile file) : base(file, element) { }

Expand Down
2 changes: 1 addition & 1 deletion Barotrauma/BarotraumaShared/SharedSource/Prefabs/Prefab.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public static void DisallowCallFromConstructor()
public readonly Identifier Identifier;
public readonly ContentFile ContentFile;

public ContentPackage? ContentPackage => ContentFile?.ContentPackage;
public ContentPackage ContentPackage => ContentFile.ContentPackage;
public ContentPath FilePath => ContentFile.Path;

public Prefab(ContentFile file, Identifier identifier)
Expand Down
112 changes: 57 additions & 55 deletions Barotrauma/BarotraumaShared/SharedSource/Prefabs/PrefabCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,90 +109,93 @@ private class InheritanceTreeCollection
{
public class Node
{
public Node(Identifier identifier) { Identifier = identifier; }
public Node(T prefab) { Prefab = prefab; }

public readonly Identifier Identifier;
public readonly T Prefab;
public Node? Parent = null;
public readonly HashSet<Node> Inheritors = new HashSet<Node>();
public void CheckParent(T prefab, IEnumerable<T> list)
{
if (Parent?.Prefab == prefab)
throw new Exception("Inheritance cycle detected: "
+ string.Join(", ", list.Select(n => n.Identifier)));
Parent?.CheckParent(prefab, list.Prepend(Parent.Prefab));
}
}

private readonly PrefabCollection<T> prefabCollection;

public InheritanceTreeCollection(PrefabCollection<T> collection) { prefabCollection = collection; }

public readonly Dictionary<Identifier, Node> IdToNode = new Dictionary<Identifier, Node>();
private readonly Dictionary<Prefab, Node> prefabToNode = new Dictionary<Prefab, Node>();
public readonly HashSet<Node> RootNodes = new HashSet<Node>();

public Node? AddNodeAndInheritors(Identifier id)
public Node? AddNodeAndInheritors(T prefab)
{
if (!prefabCollection.TryGet(id, out T? _, requireInheritanceValid: false)) { return null; }
if (!prefabCollection.TryGet(prefab.Identifier, out T? _, requireInheritanceValid: false)) { return null; }

if (!IdToNode.TryGetValue(id, out var node))
{
node = new Node(id);
RootNodes.Add(node);
IdToNode.Add(id, node);
}
else
if (prefabToNode.TryGetValue(prefab, out var node))
{
//if the node already exists, it already contains
//all inheritors so let's just return this immediately
return node;
}

node = new Node(prefab);
prefabToNode.Add(prefab, node);
RootNodes.Add(node);

if (prefab is IImplementsVariants<T> variant && variant.VariantOf == prefab.Identifier)
{
T? p = prefabCollection.prefabs[prefab.Identifier].GetParentPrefab(prefab);
if (p != null)
{
var newNode = AddNodeAndInheritors(p);
if (newNode is not null)
{
newNode.CheckParent(prefab, prefab.ToEnumerable());

RootNodes.Remove(node);
node.Parent = newNode;
newNode.Inheritors.Add(node);
}
}
}
var enumerator = prefabCollection.GetEnumerator(requireInheritanceValid: false);
while (enumerator.MoveNext())
{
T p = enumerator.Current;
if (p is not IImplementsVariants<T> implementsVariants || implementsVariants.VariantOf != id)
if (p.Identifier == prefab.Identifier || p is not IImplementsVariants<T> implementsVariants || implementsVariants.VariantOf != prefab.Identifier)
{
continue;
}
var inheritorNode = AddNodeAndInheritors(p.Identifier);

var inheritorNode = AddNodeAndInheritors(p);
if (inheritorNode is null) { continue; }
RootNodes.Remove(inheritorNode);
inheritorNode.Parent = node;
inheritorNode.CheckParent(p, p.ToEnumerable());
node.Inheritors.Add(inheritorNode);
}
return node;
}

private static void FindCycles(in Node node, HashSet<Node> uncheckedNodes)
{
HashSet<Node> checkedNodes = new HashSet<Node>();
List<Node> hierarchyPositions = new List<Node>();
Node? currNode = node;
do
{
if (!uncheckedNodes.Contains(currNode)) { break; }
if (checkedNodes.Contains(currNode))
{
int index = hierarchyPositions.IndexOf(currNode);
throw new Exception("Inheritance cycle detected: "
+string.Join(", ", hierarchyPositions.Skip(index).Select(n => n.Identifier)));
}
checkedNodes.Add(currNode);
hierarchyPositions.Add(currNode);
currNode = currNode.Parent;
} while (currNode != null);
uncheckedNodes.RemoveWhere(i => checkedNodes.Contains(i));
return node;
}

public void AddNodesAndInheritors(IEnumerable<Identifier> ids)
=> ids.ForEach(id => AddNodeAndInheritors(id));
public void AddNodesAndInheritors(IEnumerable<T> prefabs)
=> prefabs.ForEach(prefab => AddNodeAndInheritors(prefab));

public void InvokeCallbacks()
{
HashSet<Node> uncheckedNodes = IdToNode.Values.ToHashSet();
IdToNode.Values.ForEach(v => PrefabCollection<T>.InheritanceTreeCollection.FindCycles(v, uncheckedNodes));
void invokeCallbacksForNode(Node node)
{
if (!prefabCollection.TryGet(node.Identifier, out var p, requireInheritanceValid: false) ||
p is not IImplementsVariants<T> prefab) { return; }
if (!prefab.VariantOf.IsEmpty && prefabCollection.TryGet(prefab.VariantOf, out T? parent, requireInheritanceValid: false))
{
prefab.InheritFrom(parent);
prefab.ParentPrefab = parent;
if (node.Prefab is not IImplementsVariants<T> prefab) { return; }
if (!prefab.VariantOf.IsEmpty)
{
T? parent = node.Parent?.Prefab;
if (parent != null || prefabCollection.TryGet(prefab.VariantOf, out parent, requireInheritanceValid: false))
{
prefab.InheritFrom(parent);
prefab.ParentPrefab = parent;
}
}
node.Inheritors.ForEach(invokeCallbacksForNode);
}
Expand All @@ -208,23 +211,22 @@ prefab is not IImplementsVariants<T> implementsVariants ||
(implementsVariants.VariantOf.IsEmpty || (implementsVariants.ParentPrefab != null && IsInheritanceValid(implementsVariants.ParentPrefab)));
}

private void HandleInheritance(Identifier prefabIdentifier)
=> HandleInheritance(prefabIdentifier.ToEnumerable());
private void HandleInheritance(T prefab)
=> HandleInheritance(prefab.ToEnumerable());

private void HandleInheritance(IEnumerable<Identifier> identifiers)
private void HandleInheritance(IEnumerable<T> prefabs)
{
if (!implementsVariants) { return; }
foreach (var id in identifiers)
foreach (var prefab in prefabs)
{
if (!TryGet(id, out T? prefab, requireInheritanceValid: false)) { continue; }
if (prefab is IImplementsVariants<T> implementsVariants && !implementsVariants.VariantOf.IsEmpty)
{
//reset parent prefab, it'll get set in InvokeCallbacks if the inheritance is valid
implementsVariants.ParentPrefab = null;
}
}
InheritanceTreeCollection inheritanceTreeCollection = new InheritanceTreeCollection(this);
inheritanceTreeCollection.AddNodesAndInheritors(identifiers);
inheritanceTreeCollection.AddNodesAndInheritors(prefabs);
inheritanceTreeCollection.InvokeCallbacks();
}

Expand Down Expand Up @@ -409,7 +411,7 @@ public void Add(T prefab, bool isOverride)
if (!prefabs.TryAdd(prefab.Identifier, selector)) { throw new Exception($"Failed to add selector for \"{prefab.Identifier}\""); }
}
OnAdd?.Invoke(prefab, isOverride);
HandleInheritance(prefab.Identifier);
HandleInheritance(prefab);
}

/// <summary>
Expand All @@ -428,7 +430,7 @@ public void Remove(T prefab)
{
prefabs.TryRemove(prefab.Identifier, out _);
}
HandleInheritance(prefab.Identifier);
HandleInheritance(prefab);
}

/// <summary>
Expand Down Expand Up @@ -489,7 +491,7 @@ public void SortAll()
}
topMostOverrideFile = overrideFiles.Any() ? overrideFiles.First(f1 => overrideFiles.All(f2 => f1.ContentPackage.Index >= f2.ContentPackage.Index)) : null;
OnSort?.Invoke();
HandleInheritance(this.Select(p => p.Identifier));
HandleInheritance(prefabs.Values.Where(x => !x.IsEmpty).Select(x => x.ActivePrefab!));

var enumerator = GetEnumerator(requireInheritanceValid: false);
while (enumerator.MoveNext())
Expand Down
26 changes: 24 additions & 2 deletions Barotrauma/BarotraumaShared/SharedSource/Prefabs/PrefabSelector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,23 @@ public T? ActivePrefab
}
}

public T? GetParentPrefab(T prefab)
{
using (new ReadLock(rwl))
{
T? previous = null;
int index = overrides.IndexOf(prefab) + 1;
if (index == 0) { throw new Exception("Only for override inheritance!"); }
if (index < overrides.Count)
{
previous = overrides[index];
}
else if (basePrefabInternal != prefab)
previous = basePrefabInternal;
return previous;
}
}

public void Add(T prefab, bool isOverride)
{
using (new WriteLock(rwl)) { AddInternal(prefab, isOverride); }
Expand Down Expand Up @@ -102,7 +119,12 @@ private void AddInternal(T prefab, bool isOverride)
{
if (isOverride)
{
if (overrides.Contains(prefab)) { throw new InvalidOperationException($"Duplicate prefab in PrefabSelector ({typeof(T)}, {prefab.Identifier}, {prefab.ContentFile.ContentPackage.Name})"); }
if (overrides.Contains(prefab)) { throw new InvalidOperationException($"Duplicate prefab in PrefabSelector ({typeof(T)}, {prefab.Identifier}, {prefab.ContentPackage.Name})"); }
//disallow double overloading in one package in case of inheritance to avoid problems with ancestor determination
if (prefab is IImplementsVariants<T> variant && variant.VariantOf == prefab.Identifier && overrides.Any(x => x.ContentPackage == prefab.ContentPackage))
{
throw new InvalidOperationException($"Double override prefab in one content package PrefabSelector ({typeof(T)}, {prefab.Identifier}, {prefab.ContentPackage.Name})");
}
overrides.Add(prefab);
}
else
Expand Down Expand Up @@ -139,7 +161,7 @@ private void RemoveInternal(T prefab)

private void SortInternal()
{
overrides.Sort((p1, p2) => (p1.ContentPackage?.Index ?? int.MaxValue) - (p2.ContentPackage?.Index ?? int.MaxValue));
overrides.Sort((p1, p2) => p1.ContentPackage.Index - p2.ContentPackage.Index);
}

private bool isEmptyInternal => basePrefabInternal is null && overrides.Count == 0;
Expand Down