Editor is taking 20 seconds to enter playmode in an empty scene

Does anyone have any advice on how to reduce the time it takes to enter play mode in the editor?

Even when playing a scene that is completely empty, the editor seems to take about 20 seconds to enter play mode after pressing play. This is really hurting our team's productivity. I've checked just about every thread about this subject but couldn't find any solutions that fixed the problem.

According to the Profiler window, unity seems to be spending the majority of it's time in EnterPlaymode>ReloadAssemblies>SerializeBackups, but I have no idea how to reduce the time spent in SerializeBackups. Deep profiling didn't yield any additional information either.

3381500--265451--ProfilerReloadAssemblies.png

We are currently using Unity 2017.3.0f3. If anyone has any advice on what we should try, please let us know.

1 Like

Usually SerializeBackups/DeserializeBackups time is coming from plugins and custom windows which are opened in the Editor.
Entering Playmode involves domain reload (to use player-like scripts) and editor scripts has to survive this.
Thus we serialize all alive Editor objects (Windows, statics, etc.) and restore them after domain reload.
You can try to use Deep Profiler to see which plugins, classes (windows) are being saved and restored. And detailed related objects view can give a clue about some scripts as well.

1 Like

@alexeyzakharov All right after running the Memory Profiler in "Detailed" mode, it seems like even when I open a new empty scene, all the objects from the previous scene are still hanging around in memory. So this means every time I open a scene the memory usage gets higher and higher. If I'm understanding you correctly, unity will attempt to serialize and deserialize all these objects even though they aren't used in the current scene, and that's why the time to enter play mode gets is so high even in an entirely empty scene. As a first step to fixing this, is there a way to force unity to clear out all these unused objects from memory? I wrote this script to try to unload unused assets, but it doesn't seem to do the trick. The objects are still stubbornly in memory. The Memory Profiler doesn't seem to give me any hint as to why these are still in memory. Everything in the "Referenced By" column are objects that were in the previous scene.

[InitializeOnLoad]
public static class MemoryManager
{
    static MemoryManager()
    {
        EditorSceneManager.sceneOpened += OnSceneOpened;
    }

    static void OnSceneOpened(Scene scene, OpenSceneMode mode)
    {
        GarbageCollect();
    }

    [MenuItem("Tools/Garbage Collect")]
    static void GarbageCollect()
    {
        EditorUtility.UnloadUnusedAssetsImmediate();
        GC.Collect();
    }
}

@alexeyzakharov Also I'm trying to use the deep profiler but I'm not sure how to interpret the results. The "Other" category is the thing that seems to be taking the most amount of memory (almost 1 gb in my case). The highest memory usage under Other is ManagedHeap.ReservedUnusedSize and System.ExecutableAndDlls

3382502--265567--Other.png

I think I’m getting a better idea of what’s happening. The MemoryManager script above IS working, but the reason the garbage collector isn’t collecting the objects from the old scene is because there are ghost references to those objects hanging around.

So I believe the problem is because I made 2 incorrect assumptions:

  1. I assumed unity automatically unloads references when you open a new scene: I thought that when you open a scene that has some objects in it, and then open a brand new scene, Unity would automatically unload all unused references and garbage collect to get rid of those now unused objects from the previous scene. Unity definitely does not do that. Is this a bug? I can work around it by including the MemoryManager script I wrote above in my project, but cleaning up unused references from old scenes seems like something Unity should be doing by default when opening a new scene.

  2. I assumed that when an object is destroyed, it releases its references to other objects so those objects can be garbage collected: For example, I thought that if a MonoBehaviour has a Sprite field in it and that sprite field is assigned a value in the inspector, when that MonoBehaviour is destroyed, that assigned Sprite will be considered no longer referenced and will be garbage collected. This is not the case. Even when destroying the MonoBehaviour a ghost reference will still point to that Sprite and the Sprite will never be garbage collected. It’ll just persist and continue to take up memory until you close unity. The solution is to set the Sprite field to null in the MonoBehaviour’s OnDestroy method.

So if you write your MonoBehaviour like this, any sprite you assign will hang around in memory forever and never be garbage collected:

public class ReferenceHolder : MonoBehaviour
{
    public Sprite m_SpriteValue;
}

So you always need to write your MonoBehaviour like this, so that all object fields are set to null OnDestroy

public class ReferenceHolder : MonoBehaviour
{
    public Sprite m_SpriteValue;

    void OnDestroy()
    {
        m_SpriteValue = null;
    }
}

Luckily, all MonoBehaviours in our project inherit from one MonoBehaviour class. So instead of making this change to all our classes, we can simply use reflection to automatically null out any nullable field OnDestroy:

public class BaseMonoBehaviour : MonoBehaviour
{
    protected virtual void OnDestroy()
    {
       foreach (FieldInfo field in GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
        {
            Type fieldType = field.FieldType;
            if (!fieldType.IsPrimitive)
            {
                field.SetValue(this, null);
            }
        }
    }
}

Hopefully this information helps someone out there. I’ll start cleaning up references using this method and see if that reduces the time spent in SerializeBackups and DeserializeBackups when entering play mode. If it does (or if it doesn’t), I’ll continue posting in this thread.

3 Likes

Minor update: In addition to nulling out fields I had to clear out array, list, and dictionary fields as well so those would also be picked up by the garbage collector

void OnDestroy()
{
    foreach (FieldInfo field in GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
    {
        Type fieldType = field.FieldType;

        if (typeof(IList).IsAssignableFrom(fieldType))
        {
            IList list = field.GetValue(this) as IList;
            if (list != null)
            {
                list.Clear();
            }
        }

        if (typeof(IDictionary).IsAssignableFrom(fieldType))
        {
            IDictionary dictionary = field.GetValue(this) as IDictionary;
            if (dictionary != null)
            {
                dictionary.Clear();
            }
        }

        if (!fieldType.IsPrimitive)
        {
            field.SetValue(this, null);
        }
    }
}
4 Likes

Ok so following the steps above I've managed to get the enter play mode time down from 17 seconds to 6 seconds. There are still a few assets that are stubbornly sticking around even after calling EditorUtility.UnloadUnusedAssetsImmediate and GC.Collect. I'll see if I can pinpoint what is holding onto these references.

I believe it may have something to do with the OnValidate function. Weirdly enough, I had a MonoBehaviour attached to a game object. That MonoBehaviour had a reference to a RigidBody2D also attached to the same game object, and on the OnValidate function of the MonoBehaviour I was setting some fields on the RigidBody2D. For some odd reason this caused the entire game object and all its asset references to not be garbage collected. No idea why. Possibly a unity bug? In any case, I just made sure to not set any fields on the rigidbody2d during OnValidate and that fixed the issue.

I'm guessing there may be more unity bugs like this that are tripping up the garbage collector. I'll see if I can track them down and get the EnterPlayMode time down even further.

3 Likes

Thank you for all this information, just jumping into PlayMode has almost turned into a coffee task for me. I’ll try to experiment with this myself, unfortunately I don’t have one base class I inherit from so the changes would be a bit more involved.

@lynxelia Thanks for the information. I will make changes when i can myself. Never realized this could / would happen. As it turns out I moved my project to a 6Gb Ramdrive to increase the speed. (As even a SD is to slow)
It seems I can make it even faster if I implement your suggestions i bet. Thanks

I’m glad people seem to be finding this helpful! I’m sure there are many others that have this problem since our team has found many forum posts about the issue but didn’t find any good answers in them. These steps seem to be working for us, so hopefully it will work for you too. I’ll continue posting findings here.

Here's a few more tips:

  1. !EditorApplication.isPlayingOrWillChangePlaymode: Surround code you don't want to be executed while entering play mode with this entering play mode check
#if UNITY_EDITOR
            if (!EditorApplication.isPlayingOrWillChangePlaymode)
            {
                // time intensive code you don't want executed when entering play mode
            }
#endif
  1. OnValidate: OnValidate will be called on every object in your scene while entering play mode, so make sure you don't have anything too time intensive in your OnValidate methods. Calls to GetComponent, GetComponentsInChildren, GameObject.Find, FindObjectOfType, etc, in OnValidate will greatly increase the time it takes to enter playmode. If you do have time intensive code in your OnValidate methods, surround the code with the "entering play mode check" in tip #1.

  2. InitializeOnLoad: Search your project for any classes marked with the attribute [InitializeOnLoad]. Any classes marked with this attribute will be run when unity starts up AND every time you enter play mode. The unity documentation mentions the former but does not mention the latter. If you do have classes marked with [InitializeOnLoad], make sure you surround anything in their constructor with the "entering play mode check" in tip #1. Keep in mind, even if you don't remember adding this attribute anywhere to your code, this is a common attribute that is used in lot of asset store packages and unfortunately the creators of these packages don't realize that this code will be executed every time you enter play mode. In our case, we included a script in our project that automatically scans for empty folders in the project and deletes them every time unity is opened. (We included this script so we didn't have the meta hell caused by git and unity whenever someone moves files around and deletes/renames folders.) What we didn't realize was that this empty folder check wasn't only happening on starting up unity but also every time we entered play mode, which increased the play mode enter time significantly.

5 Likes

@alexeyzakharov Any comment on the fact that unity does not unload and garbage collect objects from the previous scene when opening a new scene? Is this expected behaviour for unity or should I report this as a bug? We split our game across several scenes, so without that MemoryManager script I wrote above, the EnterPlayMode times would really start to stack up every time we switch to a different scene to edit it.

@lynxelia Thank you a lot for the detailed investigation!

We have looked at the serialization code with @lukaszunity and it seems that your findings are correct - we do serialize managed counterpart of destroyed UnityEngine.Object.
We have discussed different possibilities how avoid this and came to conclusion that we can ignore destroyed objects during serialization. That would cause acceptable minor behavior change - scripts won’t be able to use managed part of destroyed object during domain reload. Which makes total sense, because Destroy is equivalent to Dispose and means that object is in invalid state and can’t be used anymore.

It would be very helpful if you can file a bug and provide a repro project so we can validate potential solution.

Also we’ll add markers to InitializeOnLoad and OnValidate, so the activity there can be visible without deep profiling.

3 Likes

@alexeyzakharov Wonderful! So If I’m understanding correctly, once the change is made to ignore managed references in destroyed objects during serialization, I will no longer need to clear and null out all the fields in my MonoBehaviours OnDestroy, right? Out of curiosity, how long has this bug been around? Was it something that was introduced in a fairly recent version of unity? I have another project on an older version of unity (Unity 5.2.2) and am wondering if I need to port the workaround to that project as well (since we aren’t planning on updating the version of unity for that project).

How about unity not calling EditorUtility.UnloadUnusedAssetsImmediate and GC.Collect when opening a scene? That’s a separate bug right? Or is that expected behaviour? Because without doing that, the old references from the previous scene always stick around to the next scene you open.

I’ll try to find time this weekend to create a small repo project for both these issues. Thanks for looking into it!

1 Like

Ok we encountered one more bug with garbage collection. We figured out how to avoid the issue, but I’m not too familiar with what’s going on under the hood of unity to understand why it’s happening.

As we suspected, OnValidate seems to mess with garbage collection if you set a field on any other component during OnValidate.

For example:

[RequireComponent(typeof(Rigidbody2D))]
public class Example : MonoBehaviour
{
    void OnValidate()
    {
        GetComponent<Rigidbody2D>().bodyType = RigidbodyType2D.Dynamic;
    }
}

Attaching a MonoBehaviour like this to a GameObject will cause that GameObject and all its managed references to not be garbage collected no matter what.

This was the issue that was causing those stubborn asset references to stick around for us even after clearing out and nulling all their fields and calling EditorUtility.UnloadUnusedAssetsImmediate and GC.Collect.

Luckily we have another workaround for this. We mainly use OnValidate to validate properties whenever a dev changes those properties in the inspector. We don’t actually want this validation to occur any other time (e.g. when the asset is loaded or when entering play mode). So as a work around, we rolled our own “OnValidateProperty” method. This method is guaranteed to only execute when a property is changed in the inspector, no where else. Moving all our code from OnValidate into this method prevents all those weird issues that trip up the garbage collector.

To use this workaround, instead of inheriting from MonoBehaviour, you need to make sure all your MonoBehaviours inherit from a BaseMonoBehaviour class with the following OnValidateProperty method (though for cleanliness I am ommiting it below, remember to also include the OnDestroy code I mentioned before in this BaseMonoBehaviour class):

[CanEditMultipleObjects, CustomEditor(typeof(BaseMonoBehaviour), true)]
public class BaseMonoBehaviourEditor : Editor
{
    public override void OnInspectorGUI()
    {
        serializedObject.Update();

        SerializedProperty iterator = serializedObject.GetIterator();
        bool enterChildren = true;
        while (iterator.NextVisible(enterChildren))
        {
            enterChildren = false;

            OnPropertyGUI(property);
        }

        serializedObject.ApplyModifiedProperties();
    }

    public void OnPropertyGUI(SerializedProperty property)
    {
        OnPropertyGUI(property, new GUIContent(property.displayName, property.tooltip));
    }

    public void OnPropertyGUI(SerializedProperty property, GUIContent label)
    {
        BaseMonoBehaviour t = target as BaseMonoBehaviour;

        EditorGUI.BeginChangeCheck();

        DrawProperty(property, label);

        if (EditorGUI.EndChangeCheck())
        {
            property.serializedObject.ApplyModifiedProperties();
            property.serializedObject.Update();
            t.OnValidateProperty(property.name);
        }
    }

    public void OnPropertyGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        BaseMonoBehaviour t = target as BaseMonoBehaviour;

        EditorGUI.BeginChangeCheck();

        DrawProperty(position, property, label);

        if (EditorGUI.EndChangeCheck())
        {
            property.serializedObject.ApplyModifiedProperties();
            property.serializedObject.Update();
            t.OnValidateProperty(property.name);
        }
    }
  
    public virtual Rect GetPropertyControlRect(SerializedProperty property, GUIContent label, params GUILayoutOption[] options)
    {
        return EditorGUILayout.GetControlRect(!string.IsNullOrEmpty(label.text), EditorGUI.GetPropertyHeight(property), options);
    }

    protected virtual void DrawProperty(SerializedProperty property, GUIContent label, params GUILayoutOption[] options)
    {
        Rect position = GetPropertyControlRect(property, label, options);
        DrawProperty(position, property, label);
    }

    protected virtual void DrawProperty(Rect position, SerializedProperty property, GUIContent label)
    {
        EditorGUI.PropertyField(position, property, label);
    }
}

This way in your class you can easily validate properties without worrying about it tripping up the garbage collector or taking up scene load time.

[RequireComponent(typeof(Rigidbody2D))]
public class Example : BaseMonoBehaviour
{
    public bool m_CanMove;
 
    public override bool OnValidateProperty(string propertyName)
    {
        if (propertyName == "m_CanMove")
        {
            if (!m_CanMove)
            {
                GetComponent<Rigidbody2D>().bodyType = RigidbodyType2D.Static;
                return true;
            }
        }
 
        return base.OnValidateProperty(propertyName);
    }
}

After doing this, we have completely clean scene loads! No more ghost references hanging around and taking up valuable memory. Woo!

4 Likes

I have a post above that lays out a full solution for the final garbage collector bug we encountered (the OnValidate bug). But the post says "This message is awaiting moderator approval, and is invisible to normal visitors." I'm not sure how to contact a moderator to get it approved. But if anyone is encountering the same garbage collection issue with OnValidate that we did and wants the full solution we used, I'm working on creating a full writeup of all these issues and their various workarounds. I'll post a link to that writeup here in a bit.

All right full write up (including the solution for the OnValidate bug) is done!

2 Likes

Has the bug-report been added to the public issue tracker already? I can’t seem to find it, but would like to keep an eye on it.

@Peter77 I've sent 3 bug reports and repo cases for steps 2, 3, and 4 of that write-up I did. I don't believe they've been added to the public issue tracker yet, but I'll post here once they are.

2 Likes

Yes, we want to make it so you shouldn’t modify scripts to cleanup zombie references.

I’m pretty sure it was always like that (though I didn’t look too much back in time)

We do call UnloadUnusedAssets when we open scene in the Editor or use LoadScene (nonadditive) API. That happens after the scene is opened. But we don’t do it when opening scene backup while entering playmode. That probably requires more discussion as doing it after scene is opened means additional performance impact for entering playmode. It might reduce enter playmode time when called before serialization in a way that preserves assets used by a scene alive, so we don’t load back them during scene loading.

Thanks for that! Please put them here once you have ids.