Skip to content

Fast Enter Play Mode and Domain Reload

LoL Engine is designed to be safe with Unity's Configurable Enter Play Mode when Reload Domain is disabled — the default for new projects starting in Unity 6.6. You can turn on Fast Enter Play Mode for quicker iteration and the engine still starts each Play session from a clean state. You do not need to do anything; this page explains how it works and what to follow if you add your own static state.


Why this matters

Entering Play mode normally rebuilds the scripting (C#) domain, which resets every static field to its initial value and clears every static event's subscribers. Fast Enter Play Mode skips that domain reload to enter Play faster — so without care, static state would survive between Play sessions in the editor: a service registry still holding last session's services, a static event still holding handlers attached to destroyed objects, a singleton field pointing at a destroyed GameObject, an "initialized" flag stuck true.

This affects the in-editor iterate loop only. A built player always launches as a fresh process, so static state always starts clean in a build regardless of this setting.


What LoL Engine already handles (you do not need to do this)

Every piece of static state the engine owns is reset on each Play-enter, using the mechanism Unity provides for exactly this purpose — a method marked [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)], which runs at the start of every Play session whether or not the domain was reloaded. Each system resets itself independently and idempotently (the engine's distributed-reset pattern), covering for example:

  • the logger (sinks, history, file sink, cached main-thread id);
  • the ServiceLocator (instance and registrations);
  • the event bus subscriber tables and the EventRegister / EventStartListening plumbing;
  • the ModelDb content registry;
  • localization LocString formatters;
  • the TestMode gate;
  • the engine-initialization registry used by ServiceAwaiter / WaitForServices;
  • the built-in singleton base classes (Singleton<T>, PersistentSingleton<T>, RegulatorSingleton<T>) and ImprovedGameInitializer.

So enabling Fast Enter Play Mode is safe with the engine: on the second and later Play sessions, services re-initialize, event subscriptions start empty, and singletons resolve to the live instance — exactly as they would after a full domain reload.


If you write your own static state

The same rule applies to your game code: any static mutable field, static event, or custom singleton you add must reset on Play-enter, or it will hold stale values when Reload Domain is off. Two options:

1. Reset it explicitly (non-generic types):

using UnityEngine;

public static class MyGameState
{
    public static int Score;
    public static event System.Action OnScoreChanged;

    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
    private static void ResetStatics()
    {
        Score = 0;
        OnScoreChanged = null; // drop subscribers from the previous Play session
    }
}

Note: [RuntimeInitializeOnLoadMethod] does not fire for an open generic type (Foo<T>), because Unity has no concrete T to call it on. For a generic singleton, clear the static field from an instance OnDestroy() instead — which is how the engine's RegulatorSingleton<T> does it.

2. Derive from an engine singleton base class (Singleton<T>, PersistentSingleton<T>, or RegulatorSingleton<T>) — they already null their static instance on destroy, so your singleton is Fast-Enter-Play-Mode-safe for free.


Verifying

To confirm your project (engine + your code) is clean under this setting:

  1. Edit → Project Settings → Editor → Enter Play Mode Settings: enable Enter Play Mode Options and uncheck Reload Domain (this is the Unity 6.6 default).
  2. Press Play, then Stop, then Play again — at least twice.
  3. Confirm the second run behaves like the first: services re-initialize, no duplicated event handlers firing on destroyed objects, no MissingReferenceException or ObjectDisposedException from leftover references, and no "already initialized" warnings.

If your own code misbehaves only on the second-plus Play, an unreset static is almost always the cause — apply one of the patterns above.


See Also