Engine modules
Every host subsystem is an EngineModule:
ts
import type { EngineContext, EngineModule } from '@aosengine/core';
/** What other modules and game code get from `engine.get('spin')`. */
interface SpinService {
/** Radians turned so far. */
angle: number;
}
/**
* A module that just turns.
*
* @returns The module, ready to pass to `createEngine`.
*/
export function spin(): EngineModule {
const service: SpinService = { angle: 0 };
return {
id: 'spin',
order: 100,
init: (ctx: EngineContext) => {
console.log(ctx.caps.webgpu);
return service; // published as engine.get('spin')
},
fixedUpdate: (dt) => {
service.angle += dt;
},
dispose: () => {
service.angle = 0;
},
};
}createEngine({ modules: [input(), physics(), audio(), splat()] }) registers them; the engine drives them. A module owns its own resources and releases them in dispose; the engine never reaches inside one.
Order
Modules run sorted by order (default 0), then by registration order. init runs in that order, dispose in reverse. Rough convention:
order | Who |
|---|---|
-100 | input |
0 | physics |
100 | gameplay, the wasm sandbox |
200 | rendering helpers, splats, overlays |
Because init is awaited one module at a time, a module may call ctx.get('physics') for anything registered ahead of it.
Services and declaration merging
A module publishes a service by returning it from init, or by calling ctx.registerService(id, service). engine.get(id) is typed through the EngineServices interface, which core deliberately leaves empty. Each package merges its own entry in:
ts
import type { PhysicsService } from './service.js';
declare module '@aosengine/core' {
interface EngineServices {
physics: PhysicsService;
}
}Importing the package is then enough for engine.get('physics') to be typed — no cast at the call site, and core never has to know the package exists. The same pattern extends EngineEventMap for typed events.
Per-frame hooks
All optional; a module is only called for the hooks it implements.
| Hook | When |
|---|---|
beginFrame() | Before the frame's fixed steps |
fixedUpdate(dt) | Once per fixed step, dt always 1/60 |
update(dt, alpha) | Once per frame, before rendering |
endFrame() | After rendering |
None of them may allocate.
See also
- The engine loop
- Assets and the manifest
- Create an engine
packages/core/README.md— @aosengine/core