Search Unity

Bug Editor 2021.2: Prefab multiselect performance issue and how to fix it

Discussion in 'Editor & General Support' started by OndrejP, Mar 25, 2022.

  1. OndrejP

    OndrejP

    Joined:
    Jul 19, 2017
    Posts:
    304
    When I select 84 prefabs in editor, it freezes for ~7 seconds.
    I was wondering why and started digging deeper.

    See attached Profiler screenshot, it's caused by
    Resources.FindObjectsOfTypeAll
    , fair enough.
    Why is it called for each prefab, is it correct?

    It turns out it can be optimized as easy as moving a line of code higher in the file.

    Take a look at line 35, it can be easily moved out of the loop, so instead of 84 calls it would do one call.
    Not entirely sure why it's needed in the first place to count some importers and call
    FixCacheCount
    , but hey, 83 ms is low enough to not care (7s / 84 = 83ms).

    I'm seriously considering writing simple ResourcesAPI wrapper, which would cache last returned result for the rest of the frame or something like that, this way I could work around that issue myself.

    So Unity, please fix this quickly, there's literally written how.
    This is the code I'd expect some junior developer to write and here it's used millions times a day - every time anyone clicks on prefab in ProjectWindow.

    AssetImporterEditor.InternalSetTargets:
    Code (CSharp):
    1. internal sealed override void InternalSetTargets(Object[] t)
    2. {
    3.     base.InternalSetTargets(t);
    4.     if (m_CopySaved)
    5.     {
    6.         if (extraDataType != null && m_ExtraDataTargets != null)
    7.         {
    8.             m_ExtraDataTargets = extraDataSerializedObject.targetObjects;
    9.         }
    10.         ReloadTargets(AssetWasUpdated());
    11.         return;
    12.     }
    13.     CheckExtraDataArray();
    14.     List<int> list = new List<int>(t.Length);
    15.     int i = 0;
    16.     while (i < t.Length)
    17.     {
    18.         int instanceID = t[i].GetInstanceID();
    19.         list.Add(instanceID);
    20.         Object @object = CreateOrReloadInspectorCopy(instanceID);
    21.         if (m_ExtraDataTargets != null)
    22.         {
    23.             if (@object != null)
    24.             {
    25.                 m_ExtraDataTargets[i] = @object;
    26.             }
    27.             else
    28.             {
    29.                 m_ExtraDataTargets[i] = ScriptableObject.CreateInstance(extraDataType);
    30.                 m_ExtraDataTargets[i].hideFlags = (HideFlags.DontSaveInEditor | HideFlags.DontUnloadUnusedAsset);
    31.                 InitializeExtraDataInstance(m_ExtraDataTargets[i], i);
    32.                 SaveUserData(instanceID, m_ExtraDataTargets[i]);
    33.             }
    34.         }
    35.         IEnumerable<AssetImporterEditor> source = Resources.FindObjectsOfTypeAll(GetType()).Cast<AssetImporterEditor>();
    36.         int num = source.Count((AssetImporterEditor e) => !Unsupported.IsDestroyScriptableObject(e) && e.targets.Contains(t[i]));
    37.         if (s_UnreleasedInstances != null)
    38.         {
    39.             num += s_UnreleasedInstances.Count((int id) => id == instanceID);
    40.         }
    41.         int inspectorCopyCount = GetInspectorCopyCount(instanceID);
    42.         if (num != inspectorCopyCount)
    43.         {
    44.             if (!CanEditorSurviveAssemblyReload())
    45.             {
    46.                 Debug.LogError($"The previous instance of {GetType()} was not un-loaded properly. The script has to be declared in a file with the same name.");
    47.             }
    48.             else
    49.             {
    50.                 Debug.LogError($"The previous instance of {GetType()} has not been disposed correctly. Make sure you are calling base.OnDisable() in your AssetImporterEditor implementation.");
    51.             }
    52.             FixCacheCount(instanceID, num);
    53.         }
    54.         int num2 = ++i;
    55.     }
    56.     // Some code omitted
    57. }
    58.  
     

    Attached Files:

    Last edited: Mar 25, 2022
  2. OndrejP

    OndrejP

    Joined:
    Jul 19, 2017
    Posts:
    304
    This is the workaround I'm currently using, attached Profiler screenshot
    To be on the safe side, cache is only used for
    AssetImporterEditor
    and subclasses

    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using System.Text;
    5. using System.Threading.Tasks;
    6. using UnityEngine;
    7.  
    8. #if UNITY_EDITOR
    9. public class EditorCachingResourceLoader : ResourcesAPI
    10. {
    11.     static Type m_cacheType;
    12.     static UnityEngine.Object[] m_cache;
    13.  
    14.     [UnityEditor.InitializeOnLoadMethod]
    15.     static void Init()
    16.     {
    17.         overrideAPI = new EditorCachingResourceLoader();
    18.         UnityEditor.EditorApplication.update += OnEditorUpdate;
    19.     }
    20.  
    21.     protected override UnityEngine.Object[] FindObjectsOfTypeAll(Type systemTypeInstance)
    22.     {
    23.         // There's issue when selecting multiple prefabs, it's very slow because this method gets called for each object with same result
    24.         if (m_cacheType == systemTypeInstance)
    25.         {
    26.             return m_cache;
    27.         }
    28.        
    29.         var result = base.FindObjectsOfTypeAll(systemTypeInstance);
    30.         if (typeof(UnityEditor.AssetImporters.AssetImporterEditor).IsAssignableFrom(systemTypeInstance))
    31.         {
    32.             m_cache = result;
    33.             m_cacheType = systemTypeInstance;
    34.         }
    35.         return result;
    36.     }
    37.  
    38.     private static void OnEditorUpdate()
    39.     {
    40.         m_cache = null;
    41.         m_cacheType = null;
    42.     }
    43. }
    44. #endif
    45.  
     

    Attached Files: