diff --git a/Source/AssetRipper.Assets.Tests/AssetEqualityTests.cs b/Source/AssetRipper.Assets.Tests/AssetEqualityTests.cs new file mode 100644 index 000000000..bbb55a8e8 --- /dev/null +++ b/Source/AssetRipper.Assets.Tests/AssetEqualityTests.cs @@ -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(ClassIDType.GameObject, new UnityVersion(2017)); + IGameObject gameObject2 = AssetCreator.Create(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(ClassIDType.GameObject); + ITransform transform = collection.CreateAsset(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(ClassIDType.GameObject); + ITransform transform = collection.CreateAsset(ClassIDType.Transform); + transform.LocalPosition_C4.SetValues(x, y, z); + gameObject.AddComponent(ClassIDType.Transform, transform); + transform.GameObject_C4P = gameObject; + return gameObject; + } + } +} diff --git a/Source/AssetRipper.Assets.Tests/AssetRipper.Assets.Tests.csproj b/Source/AssetRipper.Assets.Tests/AssetRipper.Assets.Tests.csproj index c0eebe298..e408e977b 100644 --- a/Source/AssetRipper.Assets.Tests/AssetRipper.Assets.Tests.csproj +++ b/Source/AssetRipper.Assets.Tests/AssetRipper.Assets.Tests.csproj @@ -7,7 +7,6 @@ - @@ -23,6 +22,7 @@ + diff --git a/Source/AssetRipper.Assets/Cloning/AssetEqualityComparer.cs b/Source/AssetRipper.Assets/Cloning/AssetEqualityComparer.cs index 788427fa1..5b89b0305 100644 --- a/Source/AssetRipper.Assets/Cloning/AssetEqualityComparer.cs +++ b/Source/AssetRipper.Assets/Cloning/AssetEqualityComparer.cs @@ -1,11 +1,12 @@ using AssetRipper.Assets.Metadata; +using System.Diagnostics; namespace AssetRipper.Assets.Cloning { public sealed class AssetEqualityComparer : IEqualityComparer { - private static readonly Dictionary compareCache = new(); - private static readonly Dictionary> dependentEqualityPairs = new(); + private readonly Dictionary compareCache = new(); + private readonly Dictionary> 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) diff --git a/Source/AssetRipper.Tests/AssetCreator.cs b/Source/AssetRipper.SourceGenerated.Extensions/AssetCreator.cs similarity index 51% rename from Source/AssetRipper.Tests/AssetCreator.cs rename to Source/AssetRipper.SourceGenerated.Extensions/AssetCreator.cs index 74700aa11..5ad9581f1 100644 --- a/Source/AssetRipper.Tests/AssetCreator.cs +++ b/Source/AssetRipper.SourceGenerated.Extensions/AssetCreator.cs @@ -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 +/// +/// A helper class for creating assets, generally for unit testing. +/// +public static class AssetCreator { + public static T CreateAsset(this ProcessedAssetCollection collection, ClassIDType classID) where T : IUnityObjectBase + { + return collection.CreateAsset((int)classID, (assetInfo) => + { + return (T)AssetFactory.Create(assetInfo); + }); + } + public static T Create(ClassIDType classID, UnityVersion version, Func factory) where T : IUnityObjectBase { - return MakeCollection(version).CreateAsset((int)classID, factory); + return CreateCollection(version).CreateAsset((int)classID, factory); } public static T Create(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 /// /// The type of asset to create. /// A new asset. - public static T CreateUnsafe() 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 /// /// The type of asset to create. /// A new asset. - 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); diff --git a/Source/AssetRipper.Tests/Traversal/DefaultJsonWalkerTests.cs b/Source/AssetRipper.Tests/Traversal/DefaultJsonWalkerTests.cs index c2c0a5f3d..511fba4bf 100644 --- a/Source/AssetRipper.Tests/Traversal/DefaultJsonWalkerTests.cs +++ b/Source/AssetRipper.Tests/Traversal/DefaultJsonWalkerTests.cs @@ -1,5 +1,6 @@ using AssetRipper.Assets; using AssetRipper.Export.PrimaryContent; +using AssetRipper.SourceGenerated.Extensions; using System.Text.Json.Nodes; namespace AssetRipper.Tests.Traversal; diff --git a/Source/AssetRipper.Tests/Traversal/DefaultYamlWalkerTests.cs b/Source/AssetRipper.Tests/Traversal/DefaultYamlWalkerTests.cs index 9dfae19b3..9685aaaee 100644 --- a/Source/AssetRipper.Tests/Traversal/DefaultYamlWalkerTests.cs +++ b/Source/AssetRipper.Tests/Traversal/DefaultYamlWalkerTests.cs @@ -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; diff --git a/Source/AssetRipper.Tests/Traversal/YamlWalkerTests.cs b/Source/AssetRipper.Tests/Traversal/YamlWalkerTests.cs index 08b59a968..b09a5a64c 100644 --- a/Source/AssetRipper.Tests/Traversal/YamlWalkerTests.cs +++ b/Source/AssetRipper.Tests/Traversal/YamlWalkerTests.cs @@ -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;