Skip to content

Commit

Permalink
fix: scene root monitoring sometimes breaks after domain reload (#329)
Browse files Browse the repository at this point in the history
  • Loading branch information
bdunderscore authored Aug 14, 2024
1 parent 4dbd00e commit 702ace6
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 21 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [#321] Fix GetTargetGroup being called on every pipeline invalidation
- [#327] Z-fighting occurs in prefab isolation view
- [#328] Fix issue where preview system is not reinitialized after a scene change
- [#329] Fix issue where scene root monitoring breaks after a domain reload

### Changed

Expand Down
19 changes: 18 additions & 1 deletion Editor/ChangeStream/ListenerSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ private void Delay2Repaint()
{
EditorApplication.delayCall += SceneView.RepaintAll;
}

public void ForceFire()
{
if (_ctx.TryGetTarget(out var ctx) && !ctx.IsInvalidated) ctx.Invalidate();
}
}

internal class ListenerSet<T>
Expand Down Expand Up @@ -134,7 +139,7 @@ public void Prune()
listener = next;
}
}

internal IEnumerable<string> GetListeners()
{
var ptr = _head._next;
Expand All @@ -144,5 +149,17 @@ internal IEnumerable<string> GetListeners()
ptr = ptr._next;
}
}

public void FireAll()
{
for (var listener = _head._next; listener != _head;)
{
var next = listener._next;
listener.ForceFire();
listener = next;
}

_head._next = _head._prev = _head;
}
}
}
65 changes: 57 additions & 8 deletions Editor/ChangeStream/ShadowGameObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;
using Debug = System.Diagnostics.Debug;
using Object = UnityEngine.Object;

#endregion
Expand Down Expand Up @@ -60,7 +61,7 @@ internal class ShadowHierarchy
int lastPruned = Int32.MinValue;

#if NDMF_DEBUG
[MenuItem("Tools/NDM Framework/Debug: Dump shadow hierarchy")]
[MenuItem("Tools/NDM Framework/Debug Tools/Dump shadow hierarchy")]
static void StaticDumpShadowHierarchy()
{
ObjectWatcher.Instance.Hierarchy.DumpShadowHierarchy();
Expand Down Expand Up @@ -95,8 +96,7 @@ void DumpShadowHierarchy()

sb.AppendLine("[End Shadow Hierarchy Dump]");

Debug.Log(sb.ToString());

UnityEngine.Debug.Log(sb.ToString());

void DumpShadowObject(ShadowObject obj)
{
Expand Down Expand Up @@ -146,6 +146,10 @@ void DumpListenerSet<T>(ListenerSet<T> set)

internal IDisposable RegisterRootSetListener(ListenerSet<HierarchyEvent>.Filter filter, ComputeContext ctx)
{
#if NDMF_DEBUG
Debug.WriteLine("[ShadowHierarchy] RegisterRootSetListener()");
#endif

if (ctx.IsInvalidated) return new NullDisposable();

return _rootSetListener.Register(filter, ctx);
Expand All @@ -157,6 +161,10 @@ internal IDisposable RegisterGameObjectListener(
ComputeContext ctx
)
{
#if NDMF_DEBUG
Debug.WriteLine($"[ShadowHierarchy] RegisterGameObjectListener({targetObject.GetInstanceID()})");
#endif

if (targetObject == null || ctx.IsInvalidated) return new NullDisposable();

ShadowGameObject shadowObject = ActivateShadowObject(targetObject);
Expand All @@ -169,6 +177,10 @@ internal IDisposable RegisterObjectListener(UnityObject targetComponent,
ComputeContext ctx
)
{
#if NDMF_DEBUG
Debug.WriteLine($"[ShadowHierarchy] RegisterObjectListener({targetComponent.GetInstanceID()})");
#endif

if (targetComponent == null || ctx.IsInvalidated) return new NullDisposable();

if (!_otherObjects.TryGetValue(targetComponent.GetInstanceID(), out var shadowComponent))
Expand All @@ -195,6 +207,10 @@ public void Dispose()
/// <param name="root"></param>
internal void EnableComponentMonitoring(GameObject root)
{
#if NDMF_DEBUG
Debug.WriteLine($"[ShadowHierarchy] EnableComponentMonitoring({root.GetInstanceID()})");
#endif

var obj = ActivateShadowObject(root);

EnableComponentMonitoring(obj);
Expand All @@ -215,6 +231,10 @@ private void EnableComponentMonitoring(ShadowGameObject obj)

internal void EnablePathMonitoring(GameObject root)
{
#if NDMF_DEBUG
Debug.WriteLine($"[ShadowHierarchy] EnablePathMonitoring({root.GetInstanceID()})");
#endif

var obj = ActivateShadowObject(root);

while (obj != null)
Expand Down Expand Up @@ -265,6 +285,10 @@ private ShadowGameObject ActivateShadowObject(GameObject targetObject)
/// <param name="instanceId"></param>
internal void FireObjectChangeNotification(int instanceId)
{
#if NDMF_DEBUG
Debug.WriteLine($"[ShadowHierarchy] FireObjectChangeNotification({instanceId})");
#endif

if (_gameObjects.TryGetValue(instanceId, out var shadow))
{
shadow._listeners.Fire(HierarchyEvent.ObjectDirty);
Expand Down Expand Up @@ -296,6 +320,10 @@ internal void FireObjectChangeNotification(int instanceId)
/// <param name="instanceId"></param>
internal void FireReparentNotification(int instanceId)
{
#if NDMF_DEBUG
Debug.WriteLine($"[ShadowHierarchy] FireReparentNotification({instanceId})");
#endif

// Always activate on reparent. This is because we might be reparenting _into_ an active hierarchy.
var obj = EditorUtility.InstanceIDToObject(instanceId) as GameObject;
ShadowGameObject shadow;
Expand Down Expand Up @@ -369,6 +397,10 @@ private void FireParentComponentChangeNotifications(ShadowGameObject obj)

internal void FireDestroyNotification(int instanceId)
{
#if NDMF_DEBUG
Debug.WriteLine($"[ShadowHierarchy] FireDestroyNotification({instanceId})");
#endif

if (_gameObjects.TryGetValue(instanceId, out var shadow))
{
FireParentComponentChangeNotifications(shadow.Parent);
Expand All @@ -379,7 +411,8 @@ internal void FireDestroyNotification(int instanceId)
void ForceInvalidateHierarchy(ShadowGameObject obj)
{
#if NDMF_DEBUG
Debug.Log("=== Force invalidate: " + (obj.GameObject?.name ?? "<null>"));
var resolvedName = obj.GameObject == null ? "<null>" : obj.GameObject.name;
Debug.WriteLine($"[ShadowHierarchy] ForceInvalidateHierarchy({obj.InstanceID}:{resolvedName})");
#endif

obj._listeners.Fire(HierarchyEvent.ForceInvalidate);
Expand All @@ -393,6 +426,10 @@ void ForceInvalidateHierarchy(ShadowGameObject obj)

internal void FireReorderNotification(int parentInstanceId)
{
#if NDMF_DEBUG
Debug.WriteLine($"[ShadowHierarchy] FireReorderNotification({parentInstanceId})");
#endif

if (!_gameObjects.TryGetValue(parentInstanceId, out var shadow))
{
return;
Expand All @@ -403,6 +440,10 @@ internal void FireReorderNotification(int parentInstanceId)

internal void FireStructureChangeEvent(int instanceId)
{
#if NDMF_DEBUG
Debug.WriteLine($"[ShadowHierarchy] FireStructureChangeEvent({instanceId})");
#endif

if (!_gameObjects.TryGetValue(instanceId, out var shadow))
{
return;
Expand All @@ -415,26 +456,26 @@ internal void FireStructureChangeEvent(int instanceId)
internal void InvalidateAll()
{
#if NDMF_DEBUG
Debug.Log("=== Invalidate all ===");
Debug.WriteLine("[ShadowHierarchy] InvalidateAll()");
#endif

var oldDict = _gameObjects;
_gameObjects = new Dictionary<int, ShadowGameObject>();

foreach (var shadow in oldDict.Values)
{
shadow._listeners.Fire(HierarchyEvent.ForceInvalidate);
shadow._listeners.FireAll();
}

var oldComponents = _otherObjects;
_otherObjects = new Dictionary<int, ShadowObject>();

foreach (var shadow in oldComponents.Values)
{
shadow._listeners.Fire(HierarchyEvent.ForceInvalidate);
shadow._listeners.FireAll();
}

_rootSetListener.Fire(HierarchyEvent.ForceInvalidate);
_rootSetListener.FireAll();
}

/// <summary>
Expand All @@ -445,6 +486,10 @@ internal void InvalidateAll()
/// <exception cref="NotImplementedException"></exception>
public void InvalidateTree(int instanceId)
{
#if NDMF_DEBUG
Debug.WriteLine($"[ShadowHierarchy] InvalidateTree({instanceId})");
#endif

if (_gameObjects.TryGetValue(instanceId, out var shadow))
{
_gameObjects.Remove(instanceId);
Expand Down Expand Up @@ -479,6 +524,10 @@ public void InvalidateTree(int instanceId)

public void FireGameObjectCreate(int instanceId)
{
#if NDMF_DEBUG
Debug.WriteLine($"[ShadowHierarchy] FireGameObjectCreate({instanceId})");
#endif

var obj = EditorUtility.InstanceIDToObject(instanceId) as GameObject;
if (obj == null) return;

Expand Down
22 changes: 18 additions & 4 deletions Editor/UI/Menus.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using nadena.dev.ndmf.config;
using nadena.dev.ndmf.cs;
using nadena.dev.ndmf.runtime;
using UnityEditor;
using UnityEngine;
using UnityEngine.Profiling;
using UnityObject = UnityEngine.Object;
using Object = UnityEngine.Object;

#endregion

Expand Down Expand Up @@ -63,6 +63,20 @@ private static void OnSettingsChanged()
{
Menu.SetChecked(APPLY_ON_PLAY_MENU_NAME, Config.ApplyOnPlay);
}

#if NDMF_DEBUG
[MenuItem("Tools/NDM Framework/Debug Tools/Domain Reload", false, 101)]
private static void DomainReload()
{
EditorUtility.RequestScriptReload();
}

[MenuItem("Tools/NDM Framework/Debug Tools/Invalidate shadow hierarchy", false, 101)]
private static void InvalidateShadowHierarchy()
{
ObjectWatcher.Instance.Hierarchy.InvalidateAll();
}
#endif

#if UNITY_2022_1_OR_NEWER
[MenuItem("Tools/NDM Framework/Debug Tools/Profile build", false, 101)]
Expand Down Expand Up @@ -116,7 +130,7 @@ private static IEnumerator ProfileBuildCoro(GameObject av)

if (av == null) yield break;

var clone = UnityEngine.Object.Instantiate(av);
var clone = Object.Instantiate(av);

try
{
Expand All @@ -132,7 +146,7 @@ private static IEnumerator ProfileBuildCoro(GameObject av)
m_SetRecordingEnabled.Invoke(profWindow, new object[] { false });
yield return null;

UnityEngine.Object.DestroyImmediate(clone);
Object.DestroyImmediate(clone);
AvatarProcessor.CleanTemporaryAssets();
}
#endif
Expand Down
17 changes: 9 additions & 8 deletions UnitTests~/RQ-EditorTests/ShadowHierarchyTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,9 @@ public void OnInvalidateAll_EverythingIsInvalidated()
{
var shadow = new ShadowHierarchy();

var ctx = new ComputeContext("");
var ctx1 = new ComputeContext("");
var ctx2 = new ComputeContext("");
var ctx3 = new ComputeContext("");

var o1 = c("o1");
var o2 = c("o2");
Expand All @@ -427,17 +429,16 @@ public void OnInvalidateAll_EverythingIsInvalidated()
o3.transform.SetParent(o2.transform);

List<(int, HierarchyEvent)> events = new List<(int, HierarchyEvent)>();
shadow.RegisterGameObjectListener(o1, AddToList(events, 1), ctx);
shadow.RegisterGameObjectListener(o2, AddToList(events, 2), ctx);
shadow.RegisterGameObjectListener(o3, AddToList(events, 3), ctx);
shadow.RegisterGameObjectListener(o1, AddToList(events, 1), ctx1);
shadow.RegisterGameObjectListener(o2, AddToList(events, 2), ctx2);
shadow.RegisterGameObjectListener(o3, AddToList(events, 3), ctx3);

shadow.InvalidateAll();
shadow.FireObjectChangeNotification(o1.GetInstanceID()); // should be ignored

Assert.Contains((1, HierarchyEvent.ForceInvalidate), events);
Assert.Contains((2, HierarchyEvent.ForceInvalidate), events);
Assert.Contains((3, HierarchyEvent.ForceInvalidate), events);
Assert.IsFalse(events.Contains((1, HierarchyEvent.ObjectDirty)));
Assert.IsTrue(ctx1.IsInvalidated);
Assert.IsTrue(ctx2.IsInvalidated);
Assert.IsTrue(ctx3.IsInvalidated);
}

[Test]
Expand Down

0 comments on commit 702ace6

Please sign in to comment.