Unity: FMOD Reference Updater takes 15minutes to scan

Sorry for the late reply. Where can I send a stripped down project? Don’t want to post it publicly. It doesn’t freeze as much, since it’s now very stripped down from what it was, but it still freezes.

Notes:

  • Having especially problems with Spine (Unity Asset, 2D Skeletons), for example removing some of their folders and code does make it scan faster. But still freezes on other stuff
  • Removing Odin (and some other assets) did nothing.
  • Deleting the “Library” folder, to force Unity re-create it when restarting, did work a little (but not really).
  • Renaming prefabs does nothing (tried to remove all weird characters).

No problem! You can upload it to the Uploads tab of your FMOD profile, which only yourself and FMOD staff have access to.

I’ve uploaded a project now, both including the FMOD and Unity project.

It’s just a slice of a larger project (uninstalled some plugins and removed as much Scenes, assets and scripts I could). This was the minimum for it to be slow and constantly freeze on prefabs/scenes. Probably possible on smaller projects, but this is the best I could do.

It especially freezes on the NPC and Player prefabs, and Dialogue System plugin (prefabs like BasicUIToolkitDialogueUI.prefab)

Hope this helps

Thanks very much for the project! I’ve been able to reproduce exactly what you’ve described, and it should serve as a good reference that we can use to look into improving the Event Reference Updater’s scan time and adding additional options for scan specificity.

In our project it takes much more. I started at about 11 am. It took about 1 hour to scan prefabs. Now it is processing scenes. It is 10 pm. It processed only 2 scenes. But we have about 30 scenes. I guess it will take 7-8 days to complete. I’m using MacBook Pro Apple M1 Max.

Thanks for letting us know! While I can’t offer any workarounds at the moment, I can confirm this issue has been scheduled for development.

If possible, could I get you to run this code from earlier in the thread tally the amount of elements that are being scanned? Unity: FMOD Reference Updater takes 15minutes to scan - #3 by Leah_FMOD

Just following up on this @fiftytwo could I get you to run the code I mentioned in my previous reply so I can get the total number of elements in your project?

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

Thanks very much for the detailed info on your fixes! I’ll pass this along to the development team.

I forgot to say that all these fixes are to be made in the EventReferenceUpdater.cs file, which is part of the FMODUnity package.

In your Visual Studio solution explorer, navigate to:
FMODUnityEditor → Assets → Plugins → FMOD → src → Editor → EventReferenceUpdater.cs

The first three parts of the path are of course contingent on where you’ve chosen to import the FMODUnity package. This is the default path.

thank u so much it went from 10 minutes and freezing needing to force quit, to like 10 seconds!!

You’re so welcome! Now it was all worth it :grinning_face_with_smiling_eyes: