mirror of
https://github.com/AssetRipper/AssetRipper.git
synced 2025-12-11 20:15:29 +01:00
371 lines
12 KiB
C#
371 lines
12 KiB
C#
using AssetRipper.AssemblyDumper.Utils;
|
|
using AssetRipper.Numerics;
|
|
using System.Diagnostics;
|
|
|
|
namespace AssetRipper.AssemblyDumper.Passes;
|
|
|
|
internal static partial class Pass007_ExtractSubclasses
|
|
{
|
|
private readonly struct SubclassCandidate
|
|
{
|
|
public readonly UniversalNode? ReleaseNode;
|
|
public readonly UniversalNode? EditorNode;
|
|
public readonly string Name;
|
|
public readonly DiscontinuousRange<UnityVersion> VersionRange;
|
|
public readonly UniversalNode[] NodesToBeAltered;
|
|
|
|
public SubclassCandidate(UniversalNode? releaseNode, UniversalNode? editorNode, Range<UnityVersion> versionRange) : this()
|
|
{
|
|
if (releaseNode is null && editorNode is null)
|
|
{
|
|
throw new Exception("Release and editor can't both be null");
|
|
}
|
|
|
|
ReleaseNode = releaseNode;
|
|
EditorNode = editorNode;
|
|
VersionRange = versionRange;
|
|
Name = releaseNode?.TypeName ?? editorNode?.TypeName ?? throw new Exception("All type names were null");
|
|
if (releaseNode is null)
|
|
{
|
|
Debug.Assert(editorNode is not null);
|
|
NodesToBeAltered = [editorNode];
|
|
}
|
|
else if (editorNode is null)
|
|
{
|
|
NodesToBeAltered = [releaseNode];
|
|
}
|
|
else
|
|
{
|
|
NodesToBeAltered = [releaseNode, editorNode];
|
|
}
|
|
}
|
|
|
|
private SubclassCandidate(UniversalNode? releaseNode, UniversalNode? editorNode, string name, DiscontinuousRange<UnityVersion> versionRange, UniversalNode[] nodesToBeAltered)
|
|
{
|
|
if (releaseNode is null && editorNode is null)
|
|
{
|
|
throw new ArgumentException("Release and editor can't both be null");
|
|
}
|
|
|
|
ReleaseNode = releaseNode;
|
|
EditorNode = editorNode;
|
|
Name = name;
|
|
VersionRange = versionRange;
|
|
NodesToBeAltered = nodesToBeAltered;
|
|
}
|
|
|
|
public bool Contains(SubclassCandidate candidate)
|
|
{
|
|
return Name == candidate.Name &&
|
|
VersionRange.Contains(candidate.VersionRange) &&
|
|
(candidate.ReleaseNode is null || UniversalNodeComparer.Equals(ReleaseNode, candidate.ReleaseNode, true)) &&
|
|
(candidate.EditorNode is null || UniversalNodeComparer.Equals(EditorNode, candidate.EditorNode, true));
|
|
}
|
|
|
|
public bool CanMerge(SubclassCandidate candidate)
|
|
{
|
|
return Name == candidate.Name &&
|
|
UniversalNodeComparer.Equals(ReleaseNode, candidate.ReleaseNode, true) &&
|
|
UniversalNodeComparer.Equals(EditorNode, candidate.EditorNode, true);
|
|
}
|
|
|
|
public bool CanMergeRelaxed(SubclassCandidate candidate)
|
|
{
|
|
return Name == candidate.Name &&
|
|
(candidate.ReleaseNode is null || ReleaseNode is null || UniversalNodeComparer.Equals(ReleaseNode, candidate.ReleaseNode, true)) &&
|
|
(candidate.EditorNode is null || EditorNode is null || UniversalNodeComparer.Equals(EditorNode, candidate.EditorNode, true));
|
|
}
|
|
|
|
public SubclassCandidate Merge(SubclassCandidate candidate)
|
|
{
|
|
UniversalNode[] nodes = new UniversalNode[NodesToBeAltered.Length + candidate.NodesToBeAltered.Length];
|
|
Array.Copy(NodesToBeAltered, nodes, NodesToBeAltered.Length);
|
|
Array.Copy(candidate.NodesToBeAltered, 0, nodes, NodesToBeAltered.Length, candidate.NodesToBeAltered.Length);
|
|
DiscontinuousRange<UnityVersion> range = VersionRange.Union(candidate.VersionRange);
|
|
return new SubclassCandidate(ReleaseNode ?? candidate.ReleaseNode, EditorNode ?? candidate.EditorNode, Name, range, nodes);
|
|
}
|
|
|
|
internal UniversalClass ToUniversalClass()
|
|
{
|
|
return new UniversalClass(ReleaseNode?.ShallowCloneAsRootNode(), EditorNode?.ShallowCloneAsRootNode());
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return $"{Name} ({VersionRange})";
|
|
}
|
|
}
|
|
|
|
private static void AddClassesToSharedStateSubclasses(List<SubclassCandidate> unprocessedList)
|
|
{
|
|
List<SubclassCandidate> consolidatedCandidates = ProcessList(unprocessedList);
|
|
if (consolidatedCandidates.Count == 0)
|
|
{
|
|
throw new Exception("Candidate count can't be zero");
|
|
}
|
|
else if (consolidatedCandidates.Count == 1)
|
|
{
|
|
//Single class, don't change the name
|
|
SubclassCandidate candidate = consolidatedCandidates[0];
|
|
VersionedList<UniversalClass> classList = new();
|
|
SharedState.Instance.SubclassInformation.Add(candidate.Name, classList);
|
|
DiscontinuousRange<UnityVersion> versionRange = candidate.VersionRange;
|
|
classList.Add(versionRange[0].Start, candidate.ToUniversalClass());
|
|
UnityVersion end = versionRange[versionRange.Count - 1].End;
|
|
if (end != UnityVersion.MaxVersion)
|
|
{
|
|
classList.Add(end, null);
|
|
}
|
|
}
|
|
else if (AnyIntersections(consolidatedCandidates))
|
|
{
|
|
Console.WriteLine($"Using conflict naming for {consolidatedCandidates[0].Name}");
|
|
//Use _2 naming
|
|
for (int i = 0; i < consolidatedCandidates.Count; i++)
|
|
{
|
|
SubclassCandidate candidate = consolidatedCandidates[i];
|
|
string typeName = $"{candidate.Name}_{i}";
|
|
VersionedList<UniversalClass> classList = new();
|
|
SharedState.Instance.SubclassInformation.Add(typeName, classList);
|
|
foreach (UniversalNode node in candidate.NodesToBeAltered)
|
|
{
|
|
node.TypeName = typeName;
|
|
}
|
|
DiscontinuousRange<UnityVersion> versionRange = candidate.VersionRange;
|
|
classList.Add(versionRange[0].Start, candidate.ToUniversalClass());
|
|
UnityVersion end = versionRange[versionRange.Count - 1].End;
|
|
if (end != UnityVersion.MaxVersion)
|
|
{
|
|
classList.Add(end, null);
|
|
}
|
|
else if (candidate.Name.StartsWith("PPtr"))
|
|
{
|
|
Console.WriteLine($"{candidate.Name} has unresolved conflicts and has no end version");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//Use _3_4_0f5 naming
|
|
VersionedList<UniversalClass> classList = new();
|
|
SharedState.Instance.SubclassInformation.Add(consolidatedCandidates[0].Name, classList);
|
|
|
|
HashSet<UnityVersion> startSet = new();
|
|
HashSet<UnityVersion> combinedSet = new();
|
|
foreach (SubclassCandidate candidate in consolidatedCandidates)
|
|
{
|
|
foreach (UnityVersionRange range in candidate.VersionRange)
|
|
{
|
|
startSet.Add(range.Start);
|
|
combinedSet.Add(range.Start);
|
|
combinedSet.Add(range.End);
|
|
}
|
|
}
|
|
|
|
List<(UnityVersion, SubclassCandidate?)> orderedVersions = new(combinedSet.Count);
|
|
foreach (UnityVersion version in combinedSet.Order())
|
|
{
|
|
if (startSet.Contains(version))
|
|
{
|
|
SubclassCandidate? candidate = consolidatedCandidates.First(c => c.VersionRange.Contains(version));
|
|
orderedVersions.Add((version, candidate));
|
|
}
|
|
else if (version != UnityVersion.MaxVersion)
|
|
{
|
|
orderedVersions.Add((version, null));
|
|
}
|
|
}
|
|
SubclassCandidate? previousCandidate = default;//Always not null
|
|
for (int i = 0; i < orderedVersions.Count; i++)
|
|
{
|
|
(UnityVersion version, SubclassCandidate? candidate) = orderedVersions[i];
|
|
bool shouldAdd;
|
|
if (i == 0)
|
|
{
|
|
//This is the first candidate.
|
|
Debug.Assert(previousCandidate is null);
|
|
shouldAdd = true;
|
|
previousCandidate = candidate;
|
|
}
|
|
else if (candidate is null)
|
|
{
|
|
Debug.Assert(previousCandidate is not null);
|
|
if (i < orderedVersions.Count - 1)
|
|
{
|
|
//This is null and not the end. Therefore, it's between two not null candidates.
|
|
//If those candidates are the same, we don't add this.
|
|
//The array is used for an efficient reference equality comparison.
|
|
shouldAdd = orderedVersions[i + 1].Item2?.NodesToBeAltered != previousCandidate?.NodesToBeAltered;
|
|
}
|
|
else
|
|
{
|
|
//This is the last version.
|
|
shouldAdd = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//We only add if this candidate is different from the previous one.
|
|
//The array is used for an efficient reference equality comparison.
|
|
Debug.Assert(previousCandidate is not null);
|
|
shouldAdd = candidate?.NodesToBeAltered != previousCandidate?.NodesToBeAltered;
|
|
previousCandidate = candidate;
|
|
}
|
|
if (shouldAdd)
|
|
{
|
|
classList.Add(version, candidate?.ToUniversalClass());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static bool AnyIntersections(List<SubclassCandidate> consolidatedCandidates)
|
|
{
|
|
for (int i = 0; i < consolidatedCandidates.Count; i++)
|
|
{
|
|
for (int j = i + 1; j < consolidatedCandidates.Count; j++)
|
|
{
|
|
if (consolidatedCandidates[i].VersionRange.Intersects(consolidatedCandidates[j].VersionRange))
|
|
{
|
|
Console.WriteLine($"{consolidatedCandidates[i].VersionRange} intersects with {consolidatedCandidates[j].VersionRange}");
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private static List<SubclassCandidate> ProcessList(List<SubclassCandidate> unprocessedList)
|
|
{
|
|
SplitByNullability(unprocessedList, out List<SubclassCandidate> bothList, out List<SubclassCandidate> releaseList, out List<SubclassCandidate> editorList);
|
|
bothList = GetConsolidatedList(bothList);
|
|
MergeIntoBothList(bothList, releaseList);
|
|
MergeIntoBothList(bothList, editorList);
|
|
releaseList = GetConsolidatedList(releaseList);
|
|
editorList = GetConsolidatedList(editorList);
|
|
FinalMergeAttempts(bothList, releaseList, editorList);
|
|
List<SubclassCandidate> unifiedList = new List<SubclassCandidate>(bothList.Count + releaseList.Count + editorList.Count);
|
|
unifiedList.AddRange(bothList);
|
|
unifiedList.AddRange(releaseList);
|
|
unifiedList.AddRange(editorList);
|
|
return unifiedList;
|
|
}
|
|
|
|
private static void SplitByNullability(List<SubclassCandidate> inputList,
|
|
out List<SubclassCandidate> bothList,
|
|
out List<SubclassCandidate> releaseList,
|
|
out List<SubclassCandidate> editorList)
|
|
{
|
|
bothList = inputList.Where(c => c.EditorNode is not null && c.ReleaseNode is not null).ToList();
|
|
releaseList = inputList.Where(c => c.EditorNode is null && c.ReleaseNode is not null).ToList();
|
|
editorList = inputList.Where(c => c.EditorNode is not null && c.ReleaseNode is null).ToList();
|
|
}
|
|
|
|
private static bool TryMergeClasses(List<SubclassCandidate> inputList, out List<SubclassCandidate> outputList)
|
|
{
|
|
outputList = new();
|
|
bool result = false;
|
|
foreach (SubclassCandidate candidate in inputList)
|
|
{
|
|
bool merged = false;
|
|
for (int i = 0; i < outputList.Count; i++)
|
|
{
|
|
if (outputList[i].CanMerge(candidate))
|
|
{
|
|
result = true;
|
|
merged = true;
|
|
outputList[i] = outputList[i].Merge(candidate);
|
|
break;
|
|
}
|
|
}
|
|
if (!merged)
|
|
{
|
|
outputList.Add(candidate);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private static List<SubclassCandidate> GetConsolidatedList(List<SubclassCandidate> inputList)
|
|
{
|
|
List<SubclassCandidate> outputList = inputList;
|
|
bool shouldTryAgain = true;
|
|
while (shouldTryAgain)
|
|
{
|
|
shouldTryAgain = TryMergeClasses(outputList, out List<SubclassCandidate> newOutput);
|
|
outputList = newOutput;
|
|
}
|
|
return outputList;
|
|
}
|
|
|
|
private static void MergeIntoBothList(List<SubclassCandidate> bothList, List<SubclassCandidate> singleList)
|
|
{
|
|
List<SubclassCandidate> leftovers = new();
|
|
foreach (SubclassCandidate singleCandidate in singleList)
|
|
{
|
|
bool successful = false;
|
|
for (int i = 0; i < bothList.Count; i++)
|
|
{
|
|
if (bothList[i].Contains(singleCandidate))
|
|
{
|
|
successful = true;
|
|
bothList[i] = bothList[i].Merge(singleCandidate);
|
|
break;
|
|
}
|
|
}
|
|
if (!successful)
|
|
{
|
|
leftovers.Add(singleCandidate);
|
|
}
|
|
}
|
|
singleList.Clear();
|
|
singleList.Capacity = leftovers.Count;
|
|
singleList.AddRange(leftovers);
|
|
}
|
|
|
|
private static void FinalMergeAttempts(List<SubclassCandidate> bothList, List<SubclassCandidate> releaseList, List<SubclassCandidate> editorList)
|
|
{
|
|
if (bothList.Count == 0)
|
|
{
|
|
if (releaseList.Count == 1 && editorList.Count == 1)
|
|
{
|
|
SubclassCandidate releaseCandidate = releaseList[0];
|
|
SubclassCandidate editorCandidate = editorList[0];
|
|
if (releaseCandidate.Name == editorCandidate.Name
|
|
&& AreCompatible(releaseCandidate.ReleaseNode!, editorCandidate.EditorNode!, true))
|
|
{
|
|
SubclassCandidate mergedCandidate = releaseCandidate.Merge(editorCandidate);
|
|
releaseList.Clear();
|
|
editorList.Clear();
|
|
bothList.Add(mergedCandidate);
|
|
}
|
|
}
|
|
}
|
|
else if (bothList.Count == 1)
|
|
{
|
|
if (releaseList.Count == 1 && editorList.Count == 0)
|
|
{
|
|
SubclassCandidate releaseCandidate = releaseList[0];
|
|
SubclassCandidate bothCandidate = bothList[0];
|
|
if (bothCandidate.CanMergeRelaxed(releaseCandidate))
|
|
{
|
|
SubclassCandidate mergedCandidate = bothCandidate.Merge(releaseCandidate);
|
|
releaseList.Clear();
|
|
bothList[0] = mergedCandidate;
|
|
}
|
|
}
|
|
else if (releaseList.Count == 0 && editorList.Count == 1)
|
|
{
|
|
SubclassCandidate editorCandidate = editorList[0];
|
|
SubclassCandidate bothCandidate = bothList[0];
|
|
if (bothCandidate.CanMergeRelaxed(editorCandidate))
|
|
{
|
|
SubclassCandidate mergedCandidate = bothCandidate.Merge(editorCandidate);
|
|
editorList.Clear();
|
|
bothList[0] = mergedCandidate;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|