Fix Asset Deduplication bug

* Resolves #1404
This commit is contained in:
ds5678 2024-07-29 22:33:11 -07:00
parent 2e4ce635d0
commit c5753f3aed
7 changed files with 109 additions and 17 deletions

View File

@ -0,0 +1,60 @@
using AssetRipper.Assets.Cloning;
using AssetRipper.Assets.Collections;
using AssetRipper.Primitives;
using AssetRipper.SourceGenerated;
using AssetRipper.SourceGenerated.Classes.ClassID_1;
using AssetRipper.SourceGenerated.Classes.ClassID_4;
using AssetRipper.SourceGenerated.Extensions;
namespace AssetRipper.Assets.Tests;
internal class AssetEqualityTests
{
[Test]
public void DefaultGameObjectEqualityTest()
{
IGameObject gameObject1 = AssetCreator.Create<IGameObject>(ClassIDType.GameObject, new UnityVersion(2017));
IGameObject gameObject2 = AssetCreator.Create<IGameObject>(ClassIDType.GameObject, new UnityVersion(2017));
AssetEqualityComparer comparer = new();
Assert.That(comparer.Equals(gameObject1, gameObject2));
}
[Test]
public void GameObjectWithTransformEqualityTest()
{
IGameObject gameObject1 = CreateGameObject();
IGameObject gameObject2 = CreateGameObject();
AssetEqualityComparer comparer = new();
Assert.That(comparer.Equals(gameObject1, gameObject2));
static IGameObject CreateGameObject()
{
ProcessedAssetCollection collection = AssetCreator.CreateCollection(new UnityVersion(2017));
IGameObject gameObject = collection.CreateAsset<IGameObject>(ClassIDType.GameObject);
ITransform transform = collection.CreateAsset<ITransform>(ClassIDType.Transform);
gameObject.AddComponent(ClassIDType.Transform, transform);
transform.GameObject_C4P = gameObject;
return gameObject;
}
}
[Test]
public void GameObjectWithTransformInequalityTest()
{
IGameObject gameObject1 = CreateGameObject(0, 0, 0);
IGameObject gameObject2 = CreateGameObject(1, 1, 1);
AssetEqualityComparer comparer = new();
Assert.That(comparer.Equals(gameObject1, gameObject2), Is.False);
static IGameObject CreateGameObject(float x, float y, float z)
{
ProcessedAssetCollection collection = AssetCreator.CreateCollection(new UnityVersion(2017));
IGameObject gameObject = collection.CreateAsset<IGameObject>(ClassIDType.GameObject);
ITransform transform = collection.CreateAsset<ITransform>(ClassIDType.Transform);
transform.LocalPosition_C4.SetValues(x, y, z);
gameObject.AddComponent(ClassIDType.Transform, transform);
transform.GameObject_C4P = gameObject;
return gameObject;
}
}
}

View File

@ -7,7 +7,6 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AssetRipper.SourceGenerated" Version="1.1.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageReference Include="NUnit" Version="4.1.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
@ -23,6 +22,7 @@
<ItemGroup>
<ProjectReference Include="..\AssetRipper.Assets\AssetRipper.Assets.csproj" />
<ProjectReference Include="..\AssetRipper.SourceGenerated.Extensions\AssetRipper.SourceGenerated.Extensions.csproj" />
</ItemGroup>
</Project>

View File

@ -1,11 +1,12 @@
using AssetRipper.Assets.Metadata;
using System.Diagnostics;
namespace AssetRipper.Assets.Cloning
{
public sealed class AssetEqualityComparer : IEqualityComparer<IUnityObjectBase>
{
private static readonly Dictionary<UnorderedPair, bool> compareCache = new();
private static readonly Dictionary<UnorderedPair, List<UnorderedPair>> dependentEqualityPairs = new();
private readonly Dictionary<UnorderedPair, bool> compareCache = new();
private readonly Dictionary<UnorderedPair, List<UnorderedPair>> dependentEqualityPairs = new();
public IUnityObjectBase CallingObject { get; private set; } = default!;
public IUnityObjectBase OtherObject { get; private set; } = default!;
@ -60,6 +61,7 @@ namespace AssetRipper.Assets.Cloning
DoComparison(x, y);
EvaluateDependentEqualityComparisons();
Debug.Assert(dependentEqualityPairs.Count == 0, "Dependent equality pairs should have been resolved");
return compareCache[(x, y)];
}
@ -85,10 +87,17 @@ namespace AssetRipper.Assets.Cloning
UnorderedPair valuePair = list[i];
if (compareCache.TryGetValue(valuePair, out bool value))
{
compareCache[keyPair] = value;
dependentEqualityPairs.Remove(keyPair);
hasChanged = true;
break;
if (value)
{
list.RemoveAt(i);
}
else
{
compareCache[keyPair] = false;
dependentEqualityPairs.Remove(keyPair);
break;
}
}
else if (!dependentEqualityPairs.ContainsKey(valuePair))
{
@ -107,6 +116,15 @@ namespace AssetRipper.Assets.Cloning
}
}
} while (hasChanged);
if (dependentEqualityPairs.Count > 0)
{
foreach ((UnorderedPair keyPair, _) in dependentEqualityPairs)
{
compareCache[keyPair] = true;
}
dependentEqualityPairs.Clear();
}
}
private void DoComparison(IUnityObjectBase x, IUnityObjectBase y)

View File

@ -2,22 +2,31 @@
using AssetRipper.Assets.Bundles;
using AssetRipper.Assets.Collections;
using AssetRipper.Assets.Metadata;
using AssetRipper.Primitives;
using AssetRipper.SourceGenerated;
using System.Reflection;
namespace AssetRipper.Tests;
namespace AssetRipper.SourceGenerated.Extensions;
internal static class AssetCreator
/// <summary>
/// A helper class for creating assets, generally for unit testing.
/// </summary>
public static class AssetCreator
{
public static T CreateAsset<T>(this ProcessedAssetCollection collection, ClassIDType classID) where T : IUnityObjectBase
{
return collection.CreateAsset((int)classID, (assetInfo) =>
{
return (T)AssetFactory.Create(assetInfo);
});
}
public static T Create<T>(ClassIDType classID, UnityVersion version, Func<AssetInfo, T> factory) where T : IUnityObjectBase
{
return MakeCollection(version).CreateAsset<T>((int)classID, factory);
return CreateCollection(version).CreateAsset((int)classID, factory);
}
public static T Create<T>(ClassIDType classID, UnityVersion version) where T : IUnityObjectBase
{
return MakeCollection(version).CreateAsset((int)classID, (assetInfo) =>
return CreateCollection(version).CreateAsset((int)classID, (assetInfo) =>
{
return (T)AssetFactory.Create(assetInfo);
});
@ -31,7 +40,7 @@ internal static class AssetCreator
/// </remarks>
/// <typeparam name="T">The type of asset to create.</typeparam>
/// <returns>A new asset.</returns>
public static T CreateUnsafe<T>() where T : UnityObjectBase
public static T CreateUnsafe<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] T>() where T : UnityObjectBase
{
return Create(default, default, (assetInfo) =>
{
@ -48,15 +57,17 @@ internal static class AssetCreator
/// </remarks>
/// <param name="type">The type of asset to create.</param>
/// <returns>A new asset.</returns>
public static UnityObjectBase CreateUnsafe(Type type)
[RequiresDynamicCode("")]
public static UnityObjectBase CreateUnsafe([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] Type type)
{
#pragma warning disable IL2111 // Method with parameters or return value with `DynamicallyAccessedMembersAttribute` is accessed via reflection. Trimmer can't guarantee availability of the requirements of the method.
return (UnityObjectBase?)typeof(AssetCreator).GetMethod(nameof(CreateUnsafe), 1, BindingFlags.Public | BindingFlags.Static, null, [], null)
!.MakeGenericMethod(type)
.Invoke(null, null)
?? throw new NullReferenceException();
.Invoke(null, null) ?? throw new NullReferenceException();
#pragma warning restore IL2111 // Method with parameters or return value with `DynamicallyAccessedMembersAttribute` is accessed via reflection. Trimmer can't guarantee availability of the requirements of the method.
}
private static ProcessedAssetCollection MakeCollection(UnityVersion version)
public static ProcessedAssetCollection CreateCollection(UnityVersion version)
{
GameBundle gameBundle = new();
ProcessedAssetCollection collection = gameBundle.AddNewProcessedCollection(nameof(AssetCreator), version);

View File

@ -1,5 +1,6 @@
using AssetRipper.Assets;
using AssetRipper.Export.PrimaryContent;
using AssetRipper.SourceGenerated.Extensions;
using System.Text.Json.Nodes;
namespace AssetRipper.Tests.Traversal;

View File

@ -2,6 +2,7 @@
using AssetRipper.Assets.Metadata;
using AssetRipper.Export.UnityProjects;
using AssetRipper.SourceGenerated.Classes.ClassID_114;
using AssetRipper.SourceGenerated.Extensions;
using AssetRipper.SourceGenerated.Subclasses.StaticBatchInfo;
using AssetRipper.Yaml;
using System.Globalization;

View File

@ -2,6 +2,7 @@
using AssetRipper.SourceGenerated.Classes.ClassID_1;
using AssetRipper.SourceGenerated.Classes.ClassID_114;
using AssetRipper.SourceGenerated.Classes.ClassID_142;
using AssetRipper.SourceGenerated.Extensions;
namespace AssetRipper.Tests.Traversal;