Skip to content

Make Rocket bootstrapping an explicit lifecycle #3

@judus

Description

@judus

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:

  1. register all core definitions
  2. apply user overrides/services/plugins
  3. validate required services and dependencies
  4. instantiate services in a predictable order
  5. initialize services once
  6. load resources
  7. activate first scenes
  8. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions