Context
Rocket currently does a large part of engine bootstrapping inside the Rocket constructor:
- create config
- create service/plugin containers
- create the
Engine facade
- set world scale
- register default services through
EngineInit
- initialize all service/plugin instances
- mark the engine initialized
That works, but it makes lifecycle ordering hard to reason about. User code registers the application and stacks after construction, while some engine services have already been initialized. launch() then performs loading, scene activation, callback wiring, and loop startup.
The external setup should stay simple:
const rocket = new Rocket(config);
rocket.application(new Application());
rocket.stack('world', stack => {
stack.addScene(new WorldScene());
});
rocket.launch();
But internally, launch() should finalize and start the engine instead of the constructor doing almost everything.
Goal
Turn bootstrapping into an explicit internal lifecycle while preserving the friendly Rocket facade and current game-engine setup style.
Proposed Lifecycle
created
configured
registered
initialized
loading
running
stopped
- optionally
destroyed / disposed
Proposed Internal Shape
Split the current EngineInit responsibilities into provider-like modules or descriptors, for example:
CoreProvider
RenderingProvider
SceneProvider
InputProvider
AssetProvider
AudioProvider
EntityProvider
ParticleProvider
Each provider can register explicit service descriptors such as:
{
id: EngineParts.TIMER,
factory: engine => new Timer(engine.config.fps, engine.config.showPerformanceMonitor),
init: true,
optional: false,
}
The bootstrapper can then:
- register all core definitions
- apply user overrides/services/plugins
- validate required services and dependencies
- instantiate services in a predictable order
- initialize services once
- load resources
- activate first scenes
- start the timer loop
First Robustness Wins
- Add lifecycle state to
Rocket or an internal bootstrapper.
- Stop initializing all services in the constructor.
- Move final initialization into
launch() / start().
- Make
launch() return the start() promise so callers can await boot failures.
- Make optional engine parts dependency-aware. For example, disabling
eventBus should clearly affect input, scenes, particles, or fail with a clear error.
- Keep
Rocket as the friendly facade and Engine as the object passed to applications, scenes, services, and plugins.
Dependencies / Suggested Order
This should probably come after:
Those two changes make the lifecycle refactor smaller and less ambiguous.
Non-Goals
- Do not redesign the public Rocket setup API.
- Do not rewrite scenes, rendering, physics, input, or ECS behavior.
- Do not convert to TypeScript in this ticket.
- Do not introduce a general-purpose DI framework.
- Do not make Rocket expose more public API just to support the refactor.
Acceptance Criteria
- Construction no longer performs final service initialization.
- Boot phases are explicit and named in code.
- Services are initialized once and in predictable order.
- User services/plugins registered before
launch() participate in normal initialization.
- Scene activation happens after services and resources are ready.
launch() returns a promise representing boot/start success or failure.
- Existing demo behavior remains unchanged.
- The refactor is covered by at least lightweight lifecycle tests or focused example coverage if no test setup exists yet.
Context
Rocket currently does a large part of engine bootstrapping inside the
Rocketconstructor:EnginefacadeEngineInitThat works, but it makes lifecycle ordering hard to reason about. User code registers the application and stacks after construction, while some engine services have already been initialized.
launch()then performs loading, scene activation, callback wiring, and loop startup.The external setup should stay simple:
But internally,
launch()should finalize and start the engine instead of the constructor doing almost everything.Goal
Turn bootstrapping into an explicit internal lifecycle while preserving the friendly Rocket facade and current game-engine setup style.
Proposed Lifecycle
createdconfiguredregisteredinitializedloadingrunningstoppeddestroyed/disposedProposed Internal Shape
Split the current
EngineInitresponsibilities into provider-like modules or descriptors, for example:CoreProviderRenderingProviderSceneProviderInputProviderAssetProviderAudioProviderEntityProviderParticleProviderEach provider can register explicit service descriptors such as:
The bootstrapper can then:
First Robustness Wins
Rocketor an internal bootstrapper.launch()/start().launch()return thestart()promise so callers can await boot failures.eventBusshould clearly affect input, scenes, particles, or fail with a clear error.Rocketas the friendly facade andEngineas the object passed to applications, scenes, services, and plugins.Dependencies / Suggested Order
This should probably come after:
Those two changes make the lifecycle refactor smaller and less ambiguous.
Non-Goals
Acceptance Criteria
launch()participate in normal initialization.launch()returns a promise representing boot/start success or failure.