Skip to content

Commit

Permalink
Add tests for ObjectPathRemapper
Browse files Browse the repository at this point in the history
  • Loading branch information
bdunderscore committed Nov 18, 2024
1 parent a803497 commit 8c7eda6
Show file tree
Hide file tree
Showing 3 changed files with 206 additions and 16 deletions.
79 changes: 63 additions & 16 deletions Editor/API/AnimatorServices/ObjectPathRemapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,38 @@ public sealed class ObjectPathRemapper
private readonly Dictionary<string, Transform> _pathToObject = new();

private bool _cacheValid;
private readonly Dictionary<string, string> _originalToMappedPath = new();
private Dictionary<string, string?> _originalToMappedPath = new();

internal ObjectPathRemapper(Transform root)
{
_root = root;
RecordObjectTree(root);
}

public void ApplyChanges(AnimationIndex index)
/// <summary>
/// Clears the path remapping cache. This should be called after making changes to the hierarchy,
/// such as moving objects around.
/// </summary>
public void ClearCache()
{
_cacheValid = false;
}

/// <summary>
/// Returns a dictionary mapping from virtual paths (ie - those currently in use in animations) to the corresponding
/// object's current paths.
/// Deleted objects are represented by a null value.
/// </summary>
/// <returns></returns>
public Dictionary<string, string?> GetVirtualToRealPathMap()
{
ClearCache();
UpdateCache();

index.RewritePaths(_originalToMappedPath);
var result = _originalToMappedPath;
_originalToMappedPath = new Dictionary<string, string?>();

return result;
}

private void UpdateCache()
Expand All @@ -57,17 +76,20 @@ private void UpdateCache()

foreach (var kvp in _objectToOriginalPaths)
{
var virtualPath = GetVirtualPathForObject(kvp.Key);

if (virtualPath == null) continue;
var realPath = kvp.Key != null ? RuntimeUtil.RelativePath(_root, kvp.Key) : null;

foreach (var path in kvp.Value)
{
_originalToMappedPath[path] = virtualPath;
if (path == "") continue;
_originalToMappedPath[path] = realPath;
}
}
}

/// <summary>
/// Ensures all objects in this object and its children are recorded in the object path mapper.
/// </summary>
/// <param name="subtree"></param>
public void RecordObjectTree(Transform subtree)
{
GetVirtualPathForObject(subtree);
Expand All @@ -78,17 +100,37 @@ public void RecordObjectTree(Transform subtree)
}
}

/// <summary>
/// Returns the GameObject corresponding to an animation path, if any. This is based on where the object
/// was located at the time it was first discovered, _not_ its current location.
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
public GameObject? GetObjectForPath(string path)
{
var xform = _pathToObject.GetValueOrDefault(path);
return xform ? xform.gameObject : null;
}

/// <summary>
/// Returns a virtual path for the given GameObject. For most objects, this will be their actual path; however,
/// if that path is unusable (e.g. another object was previously at that path), a new path will be generated
/// instead.
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public string? GetVirtualPathForObject(GameObject obj)
{
return GetVirtualPathForObject(obj.transform);
}

/// <summary>
/// Returns a virtual path for the given Transform. For most objects, this will be their actual path; however,
/// if that path is unusable (e.g. another object was previously at that path), a new path will be generated
/// instead.
/// </summary>
/// <param name="t"></param>
/// <returns></returns>
public string? GetVirtualPathForObject(Transform t)
{
if (_objectToOriginalPaths.TryGetValue(t, out var paths))
Expand All @@ -111,18 +153,30 @@ public void RecordObjectTree(Transform subtree)
return path;
}

/// <summary>
/// Replaces all references to `old` with `newObject`.
/// </summary>
/// <param name="old"></param>
/// <param name="newObject"></param>
public void ReplaceObject(GameObject old, GameObject newObject)
{
ReplaceObject(old.transform, newObject.transform);
}

/// <summary>
/// Replaces all references to `old` with `newObject`.
/// </summary>
/// <param name="old"></param>
/// <param name="newObject"></param>
public void ReplaceObject(Transform old, Transform newObject)
{
if (!_objectToOriginalPaths.TryGetValue(old, out var paths)) return;

if (_objectToOriginalPaths.ContainsKey(newObject))
ClearCache();

if (_objectToOriginalPaths.TryGetValue(newObject, out var originalPaths))
{
_objectToOriginalPaths[newObject].AddRange(paths);
originalPaths.AddRange(paths);
}
else
{
Expand All @@ -135,12 +189,5 @@ public void ReplaceObject(Transform old, Transform newObject)
_pathToObject[path] = newObject;
}
}

public string MapPath(string originalPath)
{
UpdateCache();

return _originalToMappedPath.GetValueOrDefault(originalPath, originalPath);
}
}
}
140 changes: 140 additions & 0 deletions UnitTests~/AnimationServices/ObjectPathRemapperTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
using System.Collections.Generic;
using nadena.dev.ndmf.animator;
using NUnit.Framework;

namespace UnitTests.AnimationServices
{
public class ObjectPathRemapperTest : TestBase
{
[Test]
public void TracksRenames()
{
var root = CreateRoot("x");

var c1 = CreateChild(root, "c1");

var remapper = new ObjectPathRemapper(root.transform);

c1.name = "c2";

Assert.AreEqual("c1", remapper.GetVirtualPathForObject(c1));
Assert.AreEqual("c1", remapper.GetVirtualPathForObject(c1.transform));

Assert.That(remapper.GetVirtualToRealPathMap(), Is.EquivalentTo(
new[]
{
new KeyValuePair<string, string>("c1", "c2")
}
));
}

[Test]
public void WhenObjectIsRenamed_AndANewObjectWithTheSameNameAppears_CorrectlyTracked()
{
var root = CreateRoot("x");

var c1 = CreateChild(root, "c1");

var remapper = new ObjectPathRemapper(root.transform);

c1.name = "c2";

var c1x = CreateChild(root, "c1");
var vpath = remapper.GetVirtualPathForObject(c1x);
Assert.AreNotEqual("c1", vpath);
Assert.AreEqual("c1", remapper.GetVirtualPathForObject(c1));

Assert.That(remapper.GetVirtualToRealPathMap(), Is.EquivalentTo(
new[]
{
new KeyValuePair<string, string>("c1", "c2"),
new KeyValuePair<string, string>(vpath, "c1")
}
));
}

[Test]
public void RemembersMultipleHierarchyLevels()
{
var root = CreateRoot("x");
var c1 = CreateChild(root, "c1");
var c2 = CreateChild(c1, "c2");
var c3 = CreateChild(c2, "c3");

var remapper = new ObjectPathRemapper(root.transform);
c1.name = "c1x";
c2.name = "c2x";
c3.name = "c3x";

Assert.AreEqual("c1/c2/c3", remapper.GetVirtualPathForObject(c3));

Assert.That(remapper.GetVirtualToRealPathMap(), Is.EquivalentTo(
new[]
{
new KeyValuePair<string, string>("c1", "c1x"),
new KeyValuePair<string, string>("c1/c2", "c1x/c2x"),
new KeyValuePair<string, string>("c1/c2/c3", "c1x/c2x/c3x")
}
));
}

[Test]
public void Test_RecordObjectTree()
{
var root = CreateRoot("x");

var mapper = new ObjectPathRemapper(root.transform);

var c1 = CreateChild(root, "c1");
var c2 = CreateChild(c1, "c2");

mapper.RecordObjectTree(c1.transform);

c1.name = "x";

Assert.AreEqual("c1", mapper.GetVirtualPathForObject(c1));

Assert.That(mapper.GetVirtualToRealPathMap(), Is.EquivalentTo(
new[]
{
new KeyValuePair<string, string>("c1", "x"),
new KeyValuePair<string, string>("c1/c2", "x/c2")
}
));
}

[Test]
public void Test_GetObjectForPath()
{
var root = CreateRoot("x");
var c1 = CreateChild(root, "c1");

var mapper = new ObjectPathRemapper(root.transform);
c1.name = "xyz";

Assert.AreEqual(c1, mapper.GetObjectForPath("c1"));
}

[Test]
public void Test_ReplaceObject()
{
var root = CreateRoot("x");
var c1 = CreateChild(root, "c1");

var mapper = new ObjectPathRemapper(root.transform);

var c2 = CreateChild(root, "c2");
mapper.ReplaceObject(c1, c2);
UnityEngine.Object.DestroyImmediate(c1);

Assert.AreEqual("c1", mapper.GetVirtualPathForObject(c2));

Assert.That(mapper.GetVirtualToRealPathMap(), Is.EquivalentTo(
new[]
{
new KeyValuePair<string, string>("c1", "c2")
}
));
}
}
}
3 changes: 3 additions & 0 deletions UnitTests~/AnimationServices/ObjectPathRemapperTest.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 8c7eda6

Please sign in to comment.