Dependency Injection
EngineContext
Section titled “EngineContext”EngineContext is the typed dependency injection container at the heart of
YAGE. Plugins register services during install(), and the rest of the engine
resolves them by key.
import { EngineContext, ServiceKey } from "@yagejs/core";ServiceKey
Section titled “ServiceKey”Every service is identified by a ServiceKey<T>. The type parameter ensures
that resolve() returns the correct type without casts:
import { ServiceKey } from "@yagejs/core";
// Define a key with a descriptive labelconst AudioMixerKey = new ServiceKey<AudioMixer>("AudioMixer");Never use plain strings for service resolution — always define a ServiceKey.
Registering and Resolving
Section titled “Registering and Resolving”// In a plugin's install():function install(context: EngineContext) { const mixer = new AudioMixer(); context.register(AudioMixerKey, mixer);}
// Resolve (throws if not registered)const mixer = context.resolve(AudioMixerKey);
// Try resolve (returns undefined if not registered)const mixer = context.tryResolve(AudioMixerKey);
// Check existenceif (context.has(AudioMixerKey)) { // ...}context.resolve() throws a clear error listing the missing key name.
Prefer it when the service is required. Use context.tryResolve() for optional
services.
Well-Known Keys
Section titled “Well-Known Keys”YAGE’s core and official plugins register these services:
| Key | Type | Package |
|---|---|---|
EngineKey | Engine | @yagejs/core |
EventBusKey | EventBus | @yagejs/core |
SceneManagerKey | SceneManager | @yagejs/core |
LoggerKey | Logger | @yagejs/core |
QueryCacheKey | QueryCache | @yagejs/core |
ErrorBoundaryKey | ErrorBoundary | @yagejs/core |
GameLoopKey | GameLoop | @yagejs/core |
InspectorKey | Inspector | @yagejs/core |
SystemSchedulerKey | SystemScheduler | @yagejs/core |
ProcessSystemKey | ProcessSystem | @yagejs/core |
AssetManagerKey | AssetManager | @yagejs/core |
Accessing Services from Components
Section titled “Accessing Services from Components”Inside a component, use this.use(key). The result is cached after the first
call so repeated access is free:
import { Component } from "@yagejs/core";import { InputManagerKey } from "@yagejs/input";import { PhysicsWorldKey } from "@yagejs/physics";
class PlayerController extends Component { update(dt: number) { const input = this.use(InputManagerKey); const physics = this.use(PhysicsWorldKey);
if (input.isPressed("jump")) { const body = this.sibling(RigidBody); body.setVelocityY(-600); } }}Some keys like PhysicsWorldKey and SceneRenderTreeKey are per-scene —
each scene gets its own instance. this.use() resolves the correct one
automatically; you don’t need to do anything different.
Accessing Services from Systems
Section titled “Accessing Services from Systems”Systems receive the EngineContext in onRegister. Store references there or
call this.use(key) during update:
import { System, Phase, EngineContext } from "@yagejs/core";
class DebugOverlaySystem extends System { readonly phase = Phase.Render; readonly priority = 9999;
private logger!: Logger;
onRegister(context: EngineContext) { this.logger = context.resolve(LoggerKey); }
update(dt: number) { const stats = this.use(GameLoopKey).stats; this.logger.debug(`FPS: ${stats.fps}`); }}Custom Services
Section titled “Custom Services”Register your own services for shared game state:
import { ServiceKey, Plugin, EngineContext } from "@yagejs/core";
interface Inventory { items: string[]; add(item: string): void; has(item: string): boolean;}
export const InventoryKey = new ServiceKey<Inventory>("Inventory");
export function inventoryPlugin(): Plugin { return { name: "inventory", install(context: EngineContext) { context.register(InventoryKey, { items: [], add(item) { this.items.push(item); }, has(item) { return this.items.includes(item); }, }); }, };}Any component or system can then this.use(InventoryKey) to access the shared
inventory.
Scene-Scoped Services
Section titled “Scene-Scoped Services”context.register services live for the engine’s lifetime. For state that
belongs to a single scene, call scene.registerScoped(key, value) — plugins
typically do this from their beforeEnter hook, but game code can use it
directly to attach scene-local services:
import { Scene, ServiceKey } from "@yagejs/core";
const LevelClockKey = new ServiceKey<{ elapsed: number }>("LevelClock");
class LevelScene extends Scene { override onEnter() { this.registerScoped(LevelClockKey, { elapsed: 0 }); }}Components resolve it the same way as any other service (this.use(LevelClockKey)).
Every key registered this way is automatically unregistered when the scene
exits (after onExit and plugin afterExit hooks run), so the next scene
starts with a clean slate and there’s no cross-scene leakage.