SceneManager
Registry and transition authority
Maps SceneKey values to providers, owns the active-scene pointer, binds runtime context, and enforces transition rules.
Engine Module · Runtime Coordination
A lifecycle and routing boundary that owns scene objects, defers transitions to safe frame boundaries, and coordinates input, physics, cameras, rendering, and UI.
01 · Overview
The system separates route decisions from lifecycle execution. Scenes request a destination; SceneManager validates, stores, and applies that request after input or update completes.
SceneManager
Maps SceneKey values to providers, owns the active-scene pointer, binds runtime context, and enforces transition rules.
SceneFactory
Caches scenes by concrete C++ type and supports reuse, reset, or full reconstruction without exposing allocation to callers.
SceneRoute
Carries the target key, type-erased payload, and reload policy. The manager copies it before processing.
SceneRuntimeContext
Provides the renderer, content registry, logical dimensions, font resolver, and engine-assist services before on_enter runs.
02 · Boundaries
Scene owns
Scene delegates
03 · Architecture
Two views show how scenes enter the application and how a runtime request becomes a lifecycle transition.
Engine-owned, testbed, and game scenes share one registry before the initial route starts.
A scene emits intent during input or update; replacement happens only after that phase returns.
04 · Design evolution
Three changes turned scene switching from a convenient manager call into a predictable runtime contract.
The important change was not how a destination is named, but when replacing the active scene is allowed to happen.
SceneManager::switch_to<T>() asked the factory for a typed instance and replaced the active scene immediately. It was compact and sufficient while navigation remained an external manager operation.
The pressure pointOnce scenes initiated navigation from input or update callbacks, immediate replacement could enter exit and destruction work while the old scene was still on the call stack.
A Scene emits a value-owned request. SceneManager copies it into a pending slot and handles it only after on_input or on_update returns.
logic_error.A transition should describe the destination, transferred data, and instance-lifetime policy as one decision.
Switching, resetting, and destroying scenes were separate manager operations. The first request value also exposed target, payload, and reload mode as parallel fields.
SceneRoute keeps those three facts together, so registration, validation, request copying, and lifecycle execution all receive the same route value.
ReuseResetRecreateCached construction and active execution are different lifetimes, so runtime-only dependencies belong at the entry boundary.
The early Scene lifecycle had no unified runtime dependency carrier. The manager and scenes reached renderer, camera, and other services through separate paths, even though a cached scene might be constructed long before it becomes active.
The design ruleRenderer, content registry, logical dimensions, font resolution, and engine-assist services describe the active runtime—not factory construction.
SceneRuntimeContext before on_enter.Lifecycle behavior is covered by the Scene core tests. View scene_core_tests.cpp ↗
05 · Selected source
These snapshots were selected from the current local Moonline working tree. The source links point to their canonical files on GitHub.
Contract
The base class exposes lifecycle hooks while keeping route emission and runtime services behind the protected boundary.
virtual void on_enter(const ScenePayload& payload) = 0;
virtual void on_exit() = 0;
virtual void reset() = 0;
void request_scene_switch(const SceneRoute& route);
[[nodiscard]] const SceneRuntimeContext& runtime_context() const;
Routing
The route keeps destination, payload, and lifetime policy together, while SceneRequest distinguishes switching from quitting.
enum class SceneReloadMode
{
Reuse,
Reset,
Recreate
};
struct SceneRoute
{
SceneKey target = SceneKeys::Invalid;
ScenePayload payload{};
SceneReloadMode reload_mode = SceneReloadMode::Reuse;
};
Transition
The manager rejects ambiguous requests, takes a value copy, and applies exit, reset, context binding, and enter in a controlled order.
const SceneRequest request = _pending_request;
_pending_request = SceneRequest{};
_has_pending_request = false;
ProcessingRequestGuard processing_guard(_is_processing_request);
switch (request.type)
{
case SceneRequestType::Switch:
switch_to_registered_scene(request.route);
break;
}
06 · Limits and next steps
Requests are intentionally limited to normal input and update execution, and only one request may be queued per processing cycle.
Future work can add transition effects or asynchronous scene preparation while preserving the same route and lifecycle boundary.