Table of Contents

Global scope

GlobalScope is Saneject's static runtime registry for globally accessible Component instances.

Saneject is mostly editor-time DI, but some runtime scenarios still need lookup by type. GlobalScope and RuntimeProxy covers that gap:

  • BindGlobal<TComponent>() stores resolved components in a Scope at editor-time and registers them at runtime.
  • RuntimeProxy can resolve through FromGlobalScope() for fast lookup.
  • You can also call GlobalScope directly as a service locatorService locatorObject or API that lets runtime code request dependencies on demand instead of receiving them through injection. In Saneject docs, this usually refers to GlobalScope. when needed.

What problem it solves

Unity serialization cannot serialize direct references between certain boundaries:

  • Scene ↔ other scene
  • Scene ↔ prefab assetPrefab assetReusable prefab definition in the Project window.
  • Prefab assetPrefab assetReusable prefab definition in the Project window. ↔ other prefab assetPrefab assetReusable prefab definition in the Project window.

GlobalScope gives you a shared runtime lookup point for registered components. You declare those components in a normal Scope, then Saneject registers them GlobalScope before regular Awake() methods run.

This keeps the common workflow explicit:

  1. Resolve dependency candidatesDependency candidateObject considered during resolution before qualifiers, filters, and assignment determine whether it becomes the final injected dependency. in the editor through bindingsBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search..
  2. Persist global candidates on the declaring Scope.
  3. Register those components into GlobalScope at runtime startupRuntime startupPhase after entering Play Mode when scopes initialize, global registrations are populated, and runtime proxy references can be swapped to real instances..

Declaring global components with bindings

Use BindGlobal<TConcrete>() inside DeclareBindings():

using Plugins.Saneject.Runtime.Scopes;

public class BootstrapScope : Scope
{
    protected override void DeclareBindings()
    {
        BindGlobal<AudioManager>()
            .FromScopeSelf();
    }
}

BindGlobal<T>():

  • Only accepts Component types.
  • Supports normal From... locator methods and Where... filters.
  • Does not support qualifiers (ToID, ToTarget, ToMember).
  • Does not support runtime proxy bindingRuntime proxy bindingComponent binding configured with FromRuntimeProxy() that injects a proxy asset at editor time and swaps it for a real runtime instance during scope initialization. methods.

See Binding and Scope for more information.

Global lifecycle

When you declare BindGlobal<T>(), Saneject handles it in two phases.

Editor phase:

  1. During injection, Saneject resolves the bindingBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search. like any other component locator.
  2. If multiple candidates are found, the first candidate is selected.
  3. The selected component is serialized into the declaring Scope's global components list.

Runtime phase:

  1. Scope.Awake() runs at execution order -10000.
  2. The scopeScopeMonoBehaviour that declares bindings for a part of your hierarchy. registers serialized global components into GlobalScope.
  3. The same Awake() then swaps runtime proxiesRuntime proxyScriptableObject placeholder asset (RuntimeProxy<TComponent>) injected into interface members at editor time and swapped to the real instance during scope startup. to real instances.
  4. Scope.OnDestroy() unregisters the components that this scopeScopeMonoBehaviour that declares bindings for a part of your hierarchy. owns.

That order matters: global registrationGlobal registrationEntry added to GlobalScope at runtime, keyed by the component's concrete type and owned by the caller that registered it. runs before proxy swapping, so runtime proxiesRuntime proxyScriptableObject placeholder asset (RuntimeProxy<TComponent>) injected into interface members at editor time and swapped to the real instance during scope startup. configured to resolve from GlobalScope can do that during startup.

For full proxy behavior, see Runtime proxy.

Using global scope with runtime proxy

A common pattern is:

  • In one scopeScopeMonoBehaviour that declares bindings for a part of your hierarchy., declare a global component with BindGlobal<TConcrete>().
  • In another scopeScopeMonoBehaviour that declares bindings for a part of your hierarchy. or contextContextSerialization boundary Saneject uses during injection to decide scope traversal and candidate eligibility., bind an interface through FromRuntimeProxy().FromGlobalScope().
using Plugins.Saneject.Runtime.Scopes;

public class BootstrapScope : Scope
{
    protected override void DeclareBindings()
    {
        BindGlobal<GameManager>()
            .FromScopeSelf();
    }
}
using Plugins.Saneject.Runtime.Scopes;

public class HudScope : Scope
{
    protected override void DeclareBindings()
    {
        BindComponent<IGameManager, GameManager>()
            .FromRuntimeProxy()
            .FromGlobalScope();
    }
}

This is usually the intended use of GlobalScope: fast runtime lookup for proxy resolution, not a replacement for normal editor-time injectionEditor-time injectionSaneject workflow that resolves dependencies and writes them into serialized objects in the Unity Editor, before entering Play Mode..

For full proxy behavior, see Runtime proxy.

Using global scope directly as a service locator

If you want to avoid runtime proxiesRuntime proxyScriptableObject placeholder asset (RuntimeProxy<TComponent>) injected into interface members at editor time and swapped to the real instance during scope startup., direct usage is available when you need fast runtime lookup outside the injection pipelineInjection pipelineOrdered stages Saneject runs during injection, from graph build and context filtering through validation, resolution, assignment, and summary logging.:

using Plugins.Saneject.Runtime.Scopes;
using UnityEngine;

public class AudioBootstrap : MonoBehaviour
{
    private void Start()
    {
        if (GlobalScope.TryGetComponent<AudioManager>(out AudioManager audio))
            audio.InitializeMixer();
    }
}

API overview:

  • GlobalScope.GetComponent<T>()
  • GlobalScope.TryGetComponent<T>(out T component)
  • GlobalScope.IsRegistered<T>()
  • GlobalScope.RegisterComponent(Component instance, Object caller)
  • GlobalScope.UnregisterComponent(Component instance, Object caller)
  • GlobalScope.Clear()

Full API here

Use manual RegisterComponent and UnregisterComponent carefully. Most projects should let Scope manage lifecycle through BindGlobal<T>().

Rules and constraints

  • GlobalScope stores Component instances only.
  • Only one registration per concrete component type is allowed at a time.
  • Lookup is exact-type by key. If AudioManager is registered, GetComponent<MonoBehaviour>() does not return it.
  • Generic lookup methods require T : Component, so interface lookups are not supported.
  • Global bindingsBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search. are validated for unique type ownership. Duplicate BindGlobal<SameType>() bindingsBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search. are invalid.
  • Global bindingBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search. candidate selection still obeys context isolationContext isolationProject setting (UseContextIsolation) that controls whether dependency resolution can cross context boundaries. rules during editor resolution.
  • Global bindingsBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search. are not used to resolve [Inject] fields or methods directly. They feed runtime registration, not normal bindingBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search. resolution.
  • Modifying GlobalScope (Register, Unregister, Clear) is runtime-only. Calls outside Play Mode are rejected.
  • Unregister requires ownership: only the same caller object that registered a type can unregister that type.