Addressable Scene

Load and unload additive scenes from the Addressables catalog, with per-key reference counting.

Stream extra scenes in and out on top of your current level — reference counted, so shared content never unloads early.

InterfaceIAddressableSceneService
Off switchNullAddressableSceneService
AssemblyCommonGameSystem.AddressableScene (optional — compiles out if the Addressables package is removed)
StartupRegistered automatically at boot (one of the 23 services) — no setup needed

What it does

This service loads scenes from your Addressables catalog on top of the scenes you already have open (additive loading — the new scene joins the current ones instead of replacing them), and unloads them when you are done. Addressables is Unity's asset-delivery system: content is published to a catalog and loaded by a string key at runtime.

Each scene key is reference counted: loading a key that is already live shares the existing scene and raises its count, and the scene only truly unloads when the count returns to zero. In-flight loads are shared too — two overlapping loads of the same key await one operation.

Runtime failures do not throw. A missing key or a failed load logs a warning and returns a result with Succeeded = false, so your game keeps running.

The service runs one operation at a time, and its IsLoading gate is independent from the Scene Flow service (ISceneService) — the two never block each other.

It ships in the optional CommonGameSystem.AddressableScene assembly. If you remove the Addressables package, that assembly switches itself off and the rest of the framework keeps compiling.

Quick start

using CommonGameSystem.Core;
using UnityEngine;

public class MySceneStreamer : MonoBehaviour
{
    private IAddressableSceneService _scenes;

    private void Awake() => _scenes = ServiceLocator.Resolve<IAddressableSceneService>();

    public async void EnterArea()
    {
        SceneLoadResult result = await _scenes.LoadAdditiveAsync("DlcArea_01");
        if (result.Succeeded) { /* scene is live */ }
    }

    // later, when you're done with it:
    public async void LeaveArea()
    {
        await _scenes.UnloadAdditiveAsync("DlcArea_01");   // count goes down; unloads at 0
    }
}

Bootstrap registers this service automatically at startup. Resolve and cache it once in Awake(), as shown.

API reference

IAddressableSceneService

MemberNotes
Task<SceneLoadResult> LoadAdditiveAsync(string key, CancellationToken ct = default)Load a catalog scene additively. Loading a key that is already live shares the existing scene: the count goes up and you receive the shared result. Starting a load while another operation is running throws an InvalidOperationException whose message starts with [AddrScene]. A missing key or a failed load never throws — it logs a warning and returns Succeeded = false. Only programming errors throw: a null or whitespace key, a disposed service, or a call from a worker thread.
Task UnloadAdditiveAsync(string key, CancellationToken ct = default)Lower the reference count for a loaded key. The scene unloads only when the count reaches zero; there is no automatic eviction. Unloading a key that is not loaded never throws — it logs a warning and returns a completed task.
IReadOnlyList<string> ActiveSceneList { get; }The distinct scene keys currently loaded, in load order. A read-only snapshot; reference counts are not exposed. Empty when the service is switched off.
bool IsLoading { get; }Whether this service has an operation in flight. It is separate from ISceneService.IsLoading — to ask "is any scene work running?", check both.

SceneLoadResult

The same result type the Scene Flow service uses:

MemberNotes
string SceneNameThe scene key the result belongs to.
bool SucceededBranch on this to tell success from a logged failure.
bool CanceledUnused here — always false.
Exception ErrorThe underlying failure, if any.
TimeSpan TotalDurationHow long the operation took.

Events

Published on the Event Bus — see the pitfalls below for the pairing guarantee:

EventFires when
AddrSceneLoadStarted(string Key)A real load began (the key went from not loaded to loading).
AddrSceneLoadCompleted(string Key, SceneLoadResult Result)The load finished without cancellation (success or logged failure).
AddrSceneLoadCanceled(string Key, Exception Cause)An in-flight load was canceled through its CancellationToken.

AddressableSceneOptions — constructor settings

MemberNotes
bool WarnOnMissingKey / bool WarnOnUnloadOfUnloadedHow chatty the warnings are. The Default preset warns (easier to diagnose); the Release preset stays silent.
int InitialKeyCapacityStarting capacity of the internal scene table, clamped to 0–1024.

Examples

using CommonGameSystem.Core;
using UnityEngine;

public class DlcLoader : MonoBehaviour
{
    private IAddressableSceneService _scenes;
    private const string DlcKey = "DlcArea_01";

    private void Awake() => _scenes = ServiceLocator.Resolve<IAddressableSceneService>();

    public async void EnterDlc()
    {
        // Loading twice is safe: a second call while live just raises the count.
        SceneLoadResult result = await _scenes.LoadAdditiveAsync(DlcKey);
        if (!result.Succeeded)
        {
            Debug.LogWarning($"DLC scene didn't load: {result.Error}");
            return;
        }
        Debug.Log($"Loaded in {result.TotalDuration.TotalMilliseconds:F0} ms. " +
                  $"Active: {string.Join(", ", _scenes.ActiveSceneList)}");
    }

    public async void LeaveDlc() => await _scenes.UnloadAdditiveAsync(DlcKey); // unloads at count 0
}

Turning it off

ServiceLocator.Replace<IAddressableSceneService>(new NullAddressableSceneService());

This suppresses all additive loads. LoadAdditiveAsync returns a completed result with Succeeded = true so callers keep running, UnloadAdditiveAsync does nothing, ActiveSceneList is empty, and no events fire. Useful for headless tests, or for builds shipped without the catalog content. One warning is logged when the replacement is constructed, so the swap is never silent. Programming errors still throw — a null key still raises ArgumentNullException — and a requested cancellation is still honored.

Common pitfalls

  • Reference counting means paired calls. Two LoadAdditiveAsync("X") calls put the count at 2. The scene survives the first UnloadAdditiveAsync("X") and only unloads on the second. Pair every load with exactly one unload, or you will leave a scene alive.
  • The gate is independent from Scene Flow. IsLoading here is this service's own lock. It does not block, and is not blocked by, a Scene Flow operation. Only two overlapping operations on this service throw the [AddrScene] InvalidOperationException.
  • Every start is closed exactly once. Shared loads and refused calls publish no events. So each AddrSceneLoadStarted is matched by exactly one AddrSceneLoadCompleted or one AddrSceneLoadCanceled — never both, never neither.
  • A normal scene transition destroys additive scenes. If your game performs a full (non-additive) scene change, Unity unloads every additive scene — including the ones this service still tracks. Reload what you need after such a transition.
  • Scene Flow — the base scene loader (ISceneService), including its own additive loading from Unity's build list
  • Asset Provider — the sibling service: Addressables asset loading with the same reference-counting approach
  • Event Bus — subscribe to the AddrSceneLoad* events