Our project hung during Event Reference Updater, too. I found out why and fixed it. Here’s the what and how, with a full step-by-step guide at the bottom on how to fix it.
NOTE: I didn’t upload the fully fixed file, as I didn’t want to break any rules or laws, and also this way the fixes are likely to work for many different versions, since they are relatively simple.
Issue 1: Editor hangs indefinitely on certain prefabs
GetGenericUpdateTasks() recurses into sub-object fields without any depth limit. On prefabs with components that hold large or self-referential object graphs (e.g. object pools, manager classes with many collection fields), the recursive walk never finishes within a single EditorApplication.update tick, freezing the editor.
FIX
Add a depth guard at the top of GetGenericUpdateTasks(). Even going as deep as 8 still makes it ultra-fast. You can change this at your leisure. Whatever you set it to, it no longer continues branching forever.
Issue 2: Massive duplicate results and O(n²) cycle detection
An IEnumerable is used to track visited objects, calling parents.Contains(value) for cycle detection, which is incredibly slow! This also only detected direct ancestors in a linear chain, while diamond-shaped references (object A referenced by both B and C) were walked twice. On large graphs this degraded to O(n²).
FIX
Replace the IEnumerable parents parameter with a HashSet for O(1) lookups, created once per top-level call and shared across all recursive calls.
Issue 3: Unmodified prefab instances found in scenes or nested under other prefabs, were reported as duplicate results
GetEmitterUpdateTasks() already correctly skips unoverridden prefab instances by checking PrefabUtility.GetPropertyModifications(), but SearchGameObject() calls GetUpdateTasks(), which routes non-emitter components to GetGenericUpdateTasks() which doesn’t check for that. This means every instance of a prefab placed in a scene or under another prefab is fully scanned, even if it has zero overrides, producing duplicate results for every field already reported on the source prefab asset.
FIX
In SearchGameObject(), skip any component that is part of a prefab instance with no property modifications on that component.
Applying the fixes:
Fixing issue 1
We have to add a depth parameter to the GetGenericUpdateTasks() method, and all the recursive calls within it.
Add the depth parameter at the end of the signature, like this…:
private static IEnumerable<Task> GetGenericUpdateTasks(object target, string subObjectPath = null, IEnumerable<object> parents = null, int depth = 0)
…then add this as the first lines of the method…
if (depth > 8)
yield break;
…then find the recursive calls to it, and add the depth incrementation. There are two foreach loops that call it, at the very bottom of the GetGenericUpdateTasks() method. Respectively, those lines become:
foreach (Task t in GetGenericUpdateTasks(item, FieldPath(subObjectPath, subObjectField.Name, index), parents, depth + 1))
and
foreach (Task t in GetGenericUpdateTasks(value, FieldPath(subObjectPath, subObjectField.Name), parents, depth + 1))
Fixing issue 2
We need to swap out the IEnumerable parents parameter for a HashSet. I chose to call it visitedSet instead, for clarity.
To optimize the set comparisons, we will add an IEqualityComparer.
Add this somewhere within the EventReferenceUpdater class…:
internal sealed class ReferenceEqualityComparer : System.Collections.Generic.IEqualityComparer<object>
{
public static readonly ReferenceEqualityComparer Instance = new ReferenceEqualityComparer();
private ReferenceEqualityComparer() { }
public new bool Equals(object x, object y) => ReferenceEquals(x, y);
public int GetHashCode(object obj) => System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj);
}
…then go to the signature of GetGenericUpdateTasks(), and swap out this…:
, IEnumerable<object> parents = null
…for this…:
, HashSet<object> visitedSet = null
…add these lines at the top of the method, right after the depth yield we added to fix Issue 1…:
if (visitedSet == null)
visitedSet = new HashSet<object>(ReferenceEqualityComparer.Instance);
if (!target.GetType().IsValueType && !visitedSet.Add(target))
yield break;
…scroll down a bit, and delete these lines…:
if (parents == null)
{
parents = Enumerable.Empty<object>();
}
parents = parents.Append(target);
…and lastly replace the remaining references to parents with visitedSet. There should be two parents.Contains() calls and two recursive calls to GetGenericUpdateTasks() (the same ones you added depth + 1 to earlier).
Fixing issue 3
Find the SearchGameObject method, and copy/paste this into the first foreach in it:
// If this component is part of a prefab instance in a scene, skip it unless
// it actually has overrides. The source prefab asset will be scanned separately.
if (PrefabUtility.IsPartOfPrefabInstance(behaviour))
{
UnityEngine.Object source = PrefabUtility.GetCorrespondingObjectFromSource(behaviour);
PropertyModification[] modifications = PrefabUtility.GetPropertyModifications(behaviour);
bool hasOverrides = modifications != null && modifications.Any(m => m.target == source);
if (!hasOverrides)
continue;
}
TADAAAA! It works, and is super-fast! Spend the saved time enjoying life with your family and being good to yourself.
Best regards, Ultroman the Tacoman