Search Unity

Configurable Enter Play Mode

Discussion in '2019.3 Beta' started by alexeyzakharov, Oct 29, 2019.

  1. alexeyzakharov


    Unity Technologies

    Jul 2, 2014
    TL;DR; Try out the experimental Configurable Play Mode option in Editor ProjectSettings to improve iteration time when you don't mind scripts not being fully reset.

    Play Mode is one of the key concepts in game development with Unity. The ability to iterate fast in the Editor is a great advantage which allows developers to prototype faster, fail faster, and create great games more easily. The time it takes to enter PlayMode is a vital metric when it comes to fast iterations. With an empty project it typically takes 1.5-2.5 seconds. However, with the project evolving, time to enter Play Mode might grow significantly to 10-25 seconds, crippling productivity.
    We profiled and analyzed Enter Play Mode to understand where the time is spent. As a result, we would like to offer options to skip the most time consuming parts during Enter Play Mode and let you decide whether accuracy or speed is preferred during development.

    How much time can be saved
    Domain Reload and Scene Reload are the main contributors to the cost to Enter Play Mode - typically their cumulative impact is around 80-90% of Enter Playmode time.
    Domain Reload is a generic way to achieve a clean state of game and Editor C# code (besides its main role of reloading a new scripting code). Similarly Scene Reload is a generic way to get a clean state of GameObjects and associated MonoBehaviors/ScriptableObjects. Generic approaches are simple and allow not to think about behavior differences, but incur huge time costs in some cases and reduce productivity.
    As you can see below, the effect of skipping Domain Reload and Scene Reload is very promising.

    It should be noted that the outcome depends on the project and might vary depending on Scene complexity and used packages and AssetStore plugins.

    How to enable
    You can find the options in the Editor Project Setting.

    ProjectSettings → Editor → Enter Play Mode Settings

    The same settings are configurable through the EditorSettings scripting API that we have added to the UnityEditor namespace:
    1. bool EditorSettings.enterPlayModeOptionsEnabled;
    2. EnterPlayModeOptions EditorSettings.enterPlayModeOptions;
    EnterPlaymodeOptions allows you to disable Domain and/or Scene Reload.

    What exactly is skipped
    From a high-level perspective, Enter Play Mode consists of the following main stages:
    1. [Backup current Scenes]. Allows us to restore state during Exit Play Mode. Is optional - only happens when Scene is dirty.
    2. Reset scripting state (reload scripting domain, aka “Domain Reload”).
    3. Resets Scene state (Scene Reload).
    4. Update scene (2 times - without rendering and with rendering).
    Combination of Domain Reload and Scene Reload is the main job of Enter Play Mode state. It resets the game world and simulates the startup behavior as the game would run in the player.
    To help you understand those options, I would like to provide a scripting events diagram for Enter Play Mode and highlight differences.

    What are the side effects
    Important differences due to skipped Domain Reload
    The main side effect is that the scripting state is not globally reinitialized and it is up to you to decide what has to be reset.
    1. Fields marked with [NonSerialized] attribute keep their values - as script serialization step is skipped completely and scripts remain the same, all data is carried to the Play Mode.
    2. Static variables keep their values - because Domain Reload does not happen types stay initialized.
      • Static events keep their subscribers.
    3. There is no extra OnDisable/OnEnable calls for ExecuteInEditMode scripts.
    Static variables can be reinitialized with the help of the RuntimeInitializeOnLoad (RuntimeInitializeOnLoadMethod) attribute.
    Code (CSharp):
    1. using UnityEngine;
    2. public class StaticCounterExampleFixed : MonoBehaviour
    3. {
    4.     static int counter;
    6.     [RuntimeInitializeOnLoadMethod]
    7.     static void Init()
    8.     {
    9.             counter = 0;
    10.     }
    11. }
    Important differences due to skipping Scene Reload
    Avoiding Scene Reload is much more complex to implement and align with the normal loading path, but should have minimal side effects. However, due to its tight connection to Domain Reload, there are a couple of important differences:
    1. ScriptableObject, MonoBehaviour fields which are not serialized into the build ([NonSerialized], private or internal) keep their values - as existing objects are not recreated and constructors are not called.
      Watch out for null private and internal fields of array/List type - they are converted to an empty array/List object during Domain Reload and stay non null for game scripts.
    2. ExecuteInEditMode/ExecuteAlways scripts are not destroyed/awaken - no OnDestroy/Awake calls for those.
      Watch out for Awake/OnEnable methods which check EditorApplication.isPlaying property - Awake is not called and OnEnable is called only when EditorApplication.isPlaying is already true on Play Mode change.
    Nonserialized fields for game scripts should not be an issue, as game scripts are not active in Edit Mode, however, the ones marked with ExecuteInEditMode/ExecuteAlways might change themselves or touch fields of other game scripts. This can be mitigated by initializing affected fields in an OnEnable callback.

    Known issues
    1. - Fixed.
    2. - Can’t be fixed due to being a Domain Reload issue.
    3. - To be fixed.
    Call for feedback
    We believe that if your project is currently slow to enter Play Mode, this feature will speed things up significantly. However, we understand that the side effects of the feature might be difficult to handle and we’re looking forward to hearing what you think, which issues you encounter in you project/ package/AssetStore plugin and how we can make life easier for you!

    Big thanks to @sinitreo, @chrisk, @Peter77, @Baste, @Hyp-X and others for providing a valuable feedback.
    Last edited: Nov 5, 2019
  2. SugoiDev


    Mar 27, 2013
    Thank you for this feature. In my opinion, this had one of the largest positive impacts on our workflow so far (and we've been using Unity since the 3.x days).
    Our playmode cycles have never been this fast and a lot less time is spent creating/maintaining a poor-man's pseudo-playmode state for edit-time. We can just enter playmode now!

    I don't disable scene-reloading, as that one caused trouble for us. But I'm already plenty happy with not having to reload the domain.

    It would be awesome to see script recompilation cycles without having domain reloads, but this one is probably not feasible without .NET core.
  3. tertle


    Jan 25, 2011
    After selecting "Enter Play Mode Settings" and hitting play for the first time, Unity just crashed to desktop straight away.

    Stacktrace seems to point to an ECS / jobs issue.

    Is this meant to be supported yet? If it is I'll have a look at debugging it over the weekend to figure out if it's something I'm doing or something the package is doing. First look it seems to be crashing in one of my GameObjectConversion systems.


    After a clean restart, re-enabling the feature i can't seem to replicate the crash. Everything seems to work well. That I'm going to have to tweak one small section of code to be compliant.
    Last edited: Nov 1, 2019
  4. chrisk


    Jan 23, 2009
    Once released, Fast Enter-to-play should be the default mode going forward. I'm guessing within a year also? Once experienced the fast mode, there is no way you want to go back. In my opinion, it should've been the default mode from the beginning.

    Well, it's better late than never and everyone should start thinking about the new ways of initializing the game. Anyway, big thanks for the initiative and I'm happy that it's a big step forward.

    However, people will not optin unless 2019.3 releases (I find that too many developers say they will not touch beta Unity and I still think that you should've released it on 2019.2 as a preview if you really wanted feedbacks earlier) and don't despair for the lack of feedback. I'll try to push it once 2019.3 release and I'm sure you will have plenty of feedback soon enough. 2019.3 still on-track for mid-Nov?

    I want to point out that we still have to get rid of AppDomain reload for the compilation; it will allow insanely fast compilation as well. Make the fast iteration complete.

    As you already know it can be done by managing Assembly load/unload ONLY the changed assemblies through AssemblyLoadContext. I hope there is a team who is looking into this already.

    One of the big problems AppDomain reload is that not only it takes a really long time to unload/reload whole Assemblies regardless it's changed or not, but Unity Editor will freeze if you are using native DLL with p/invoke, thus we can't use many native frameworks out there.

    Please keep the good work.

  5. tertle


    Jan 25, 2011
    Been using this for a few days. Great feature, but at the moment sadly very unstable at least for me.
    I crash about 1 in 3 times when I make a code change.

    Based off stack trace it appears to be the Burst asynchronous compiler.
    alexeyzakharov likes this.
  6. alexeyzakharov


    Unity Technologies

    Jul 2, 2014
    Could you please file a bugreport - we'll ensure there is no crash.
  7. alexeyzakharov


    Unity Technologies

    Jul 2, 2014
    Yes, com.unity.entities is supported since 0.2.0-preview.
  8. M_R


    Apr 15, 2015
    nice post (especially the diagram with enter play mode lifecycle. should be put in the docs along with exit play mode and script compilation counterparts).

    @alexeyzakharov could you also share some best practices about how to properly support this feature in code? (e.g. when to initialize serialized state vs non-serialized state vs static state etc...)
    alexeyzakharov likes this.
  9. alexeyzakharov


    Unity Technologies

    Jul 2, 2014
    Thanks! Yes,we'll add the diagram to a manual section here

    When updating internal projects to use the feature I kept those statements in mind:
    1. Game initialization is driven by Awake/OnEnable and shutdown by OnDestroy/OnDisable - this is already a best practice for Unity games.
    2. Editor scripts use OnEnable to alter Editmode/Playmode behavior.
    3. Avoid using global static variables shared between editor and game scripts.
    4. Prefer singletons which are initialized with Awake/RuntimeInitializeOnLoad to having a many scattered static variables.
    5. Do heavy loading in Awake or in a lazy init way.
    6. Add tests to ensure Editor functionality survives domain reload and playmode changes.
    The procedure I've used is quite simple:
    1. Enable the mode.
    2. Enter Play Mode 2-3 time consecutively.
    3. Check that there are no errors in the console.
    4. Check that the game functions properly - requires help from the game team.
    5. If there are errors or different behavior - enable scene reload.
    6. Investigate which static variables might be carried to the next game session - move those to RuntimeInitializeOnLoad helper method.
    I didn't have issues with private fields serialization, but if you experienced those mark private array/lists game script references game script doesn't care about with [NotSerialized] as @Hyp-X mentioned here. (although, I still think we might be able to fix this).
    Generally, skipping the domain reload is a huge win in iteration time and requires minimal code modifications (none in some cases if you were already following general MonoBehaviour lifecycly patterns) and ongoing support. Skipping scene reload saves extra time, but also requires extra fixes which are more workarounds unfortunately, and this is where the most of the work is to get the feature out of the experimental.
  10. laeusnb


    Jun 10, 2014
    This is by far the biggest potential problem in an established codebase. Is there any possible way you guys could provide diagnostics that would help detect the problems in a mature codebase, without having to run through a game's myriad pathways to ensure some tiny static didn't sneak its way through? At least getting some detection from Unity's side would help us find these things more quickly and in a methodical way rather than hoping we have enough coverage.
  11. tertle


    Jan 25, 2011
    I upgraded from burst 1.2.0p8 to 1.2.0p9 and disabled domain reloading again and have not yet been able to crash it in about 30min of use so far. Even trying hard doing some dodgy things.

    The burst changelog for p9 somewhat related to the last exception in the stacktrace I was getting so I'm hoping it's all sorted. I'll continue testing and see how it goes.
    alexeyzakharov likes this.
  12. Lorash


    May 6, 2019
    It would be nice if first-party Unity packages actually supported this, like the new input system. (case 1192017)
  13. Ethan_VisualVocal


    Mar 23, 2016
  14. Arkade


    Oct 11, 2012
    FYI: URP's DebugManager can start giving an NRE sometimes when using this feature. To reiterate, it doesn't happen all the time. However once it starts happening, it happens every time one tries to enter play mode.

    In case anyone else ends-up here, you can get it working again by disabling the "Enter Play Mode" feature, starting your game (it'll work fine) then stop and re-enable the feature. HTH

    Skimming the code, I find myself wondering if it's after I do a non-debug build...? I'll keep that in mind. (DebugManager's Lazy-initialized constructor has a check for `Debug.isDebugBuild` which I'd hazard is leaving lying around an instance that /is/ constructed but has no members. (not checked.))

    Code (CSharp):
    1. NullReferenceException: Object reference not set to an instance of an object
    2.   at UnityEngine.Rendering.DebugManager.UpdateActions () [0x0001b] in D:\Users\User\Dev\Unity3D\LevelDesignTest2019\Library\PackageCache\com.unity.render-pipelines.core@7.1.6\Runtime\Debugging\DebugManager.Actions.cs:165
    3.   at UnityEngine.Rendering.DebugUpdater.Update () [0x00001] in D:\Users\User\Dev\Unity3D\LevelDesignTest2019\Library\PackageCache\com.unity.render-pipelines.core@7.1.6\Runtime\Debugging\DebugUpdater.cs:18
    4. (Filename: Library/PackageCache/com.unity.render-pipelines.core@7.1.6/Runtime/Debugging/DebugManager.Actions.cs Line: 165)