ecspresso 0.10.2 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/README.md +73 -17
  2. package/dist/asset-manager.d.ts +15 -15
  3. package/dist/asset-types.d.ts +16 -14
  4. package/dist/bundle.d.ts +66 -16
  5. package/dist/bundles/audio.d.ts +293 -0
  6. package/dist/bundles/{utils/bounds.d.ts → bounds.d.ts} +9 -7
  7. package/dist/bundles/camera.d.ts +89 -0
  8. package/dist/bundles/collision.d.ts +289 -0
  9. package/dist/bundles/diagnostics.d.ts +48 -0
  10. package/dist/bundles/{utils/input.d.ts → input.d.ts} +16 -17
  11. package/dist/bundles/physics2D.d.ts +159 -0
  12. package/dist/bundles/renderers/renderer2D.d.ts +65 -24
  13. package/dist/bundles/spatial-index.d.ts +57 -0
  14. package/dist/bundles/state-machine.d.ts +298 -0
  15. package/dist/bundles/{utils/timers.d.ts → timers.d.ts} +9 -8
  16. package/dist/bundles/{utils/transform.d.ts → transform.d.ts} +10 -10
  17. package/dist/bundles/tween.d.ts +197 -0
  18. package/dist/command-buffer.d.ts +20 -20
  19. package/dist/ecspresso-builder.d.ts +165 -0
  20. package/dist/ecspresso.d.ts +157 -178
  21. package/dist/entity-manager.d.ts +76 -40
  22. package/dist/event-bus.d.ts +6 -1
  23. package/dist/index.d.ts +1 -9
  24. package/dist/reactive-query-manager.d.ts +14 -3
  25. package/dist/resource-manager.d.ts +35 -19
  26. package/dist/screen-manager.d.ts +4 -4
  27. package/dist/screen-types.d.ts +12 -11
  28. package/dist/src/bundles/audio.js +4 -0
  29. package/dist/src/bundles/audio.js.map +10 -0
  30. package/dist/src/bundles/bounds.js +4 -0
  31. package/dist/src/bundles/bounds.js.map +10 -0
  32. package/dist/src/bundles/camera.js +4 -0
  33. package/dist/src/bundles/camera.js.map +10 -0
  34. package/dist/src/bundles/collision.js +4 -0
  35. package/dist/src/bundles/collision.js.map +11 -0
  36. package/dist/src/bundles/diagnostics.js +5 -0
  37. package/dist/src/bundles/diagnostics.js.map +10 -0
  38. package/dist/src/bundles/input.js +4 -0
  39. package/dist/src/bundles/input.js.map +10 -0
  40. package/dist/src/bundles/physics2D.js +4 -0
  41. package/dist/src/bundles/physics2D.js.map +11 -0
  42. package/dist/src/bundles/renderers/renderer2D.js +4 -0
  43. package/dist/src/bundles/renderers/renderer2D.js.map +10 -0
  44. package/dist/src/bundles/spatial-index.js +4 -0
  45. package/dist/src/bundles/spatial-index.js.map +11 -0
  46. package/dist/src/bundles/state-machine.js +4 -0
  47. package/dist/src/bundles/state-machine.js.map +10 -0
  48. package/dist/src/bundles/timers.js +4 -0
  49. package/dist/src/bundles/timers.js.map +10 -0
  50. package/dist/src/bundles/transform.js +4 -0
  51. package/dist/src/bundles/transform.js.map +10 -0
  52. package/dist/src/bundles/tween.js +4 -0
  53. package/dist/src/bundles/tween.js.map +11 -0
  54. package/dist/src/index.js +4 -0
  55. package/dist/src/index.js.map +25 -0
  56. package/dist/system-builder.d.ts +36 -42
  57. package/dist/type-utils.d.ts +52 -3
  58. package/dist/types.d.ts +10 -19
  59. package/dist/utils/check-required-cycle.d.ts +12 -0
  60. package/dist/utils/easing.d.ts +71 -0
  61. package/dist/utils/math.d.ts +67 -0
  62. package/dist/utils/narrowphase.d.ts +63 -0
  63. package/dist/utils/spatial-hash.d.ts +53 -0
  64. package/package.json +50 -20
  65. package/dist/bundles/renderers/renderer2D.js +0 -4
  66. package/dist/bundles/renderers/renderer2D.js.map +0 -10
  67. package/dist/bundles/utils/bounds.js +0 -4
  68. package/dist/bundles/utils/bounds.js.map +0 -10
  69. package/dist/bundles/utils/collision.d.ts +0 -204
  70. package/dist/bundles/utils/collision.js +0 -4
  71. package/dist/bundles/utils/collision.js.map +0 -10
  72. package/dist/bundles/utils/input.js +0 -4
  73. package/dist/bundles/utils/input.js.map +0 -10
  74. package/dist/bundles/utils/movement.d.ts +0 -86
  75. package/dist/bundles/utils/movement.js +0 -4
  76. package/dist/bundles/utils/movement.js.map +0 -10
  77. package/dist/bundles/utils/timers.js +0 -4
  78. package/dist/bundles/utils/timers.js.map +0 -10
  79. package/dist/bundles/utils/transform.js +0 -4
  80. package/dist/bundles/utils/transform.js.map +0 -10
  81. package/dist/index.js +0 -4
  82. package/dist/index.js.map +0 -22
@@ -1,29 +1,30 @@
1
1
  import EntityManager from "./entity-manager";
2
2
  import EventBus from "./event-bus";
3
+ import { type ResourceFactoryWithDeps } from "./resource-manager";
3
4
  import AssetManager from "./asset-manager";
4
5
  import ScreenManager from "./screen-manager";
5
6
  import { type ReactiveQueryDefinition } from "./reactive-query-manager";
6
7
  import CommandBuffer from "./command-buffer";
7
8
  import type { System, SystemPhase, FilteredEntity, Entity, RemoveEntityOptions, HierarchyEntry, HierarchyIteratorOptions } from "./types";
8
9
  import type Bundle from "./bundle";
9
- import type { BundlesAreCompatible } from "./type-utils";
10
- import type { AssetHandle, AssetConfigurator } from "./asset-types";
11
- import type { ScreenDefinition, ScreenConfigurator } from "./screen-types";
10
+ import type { AssetHandle } from "./asset-types";
11
+ import type { ScreenDefinition } from "./screen-types";
12
+ import { ECSpressoBuilder } from "./ecspresso-builder";
12
13
  /**
13
14
  * Interface declaration for ECSpresso constructor to ensure type augmentation works properly.
14
15
  * This merges with the class declaration below.
15
16
  */
16
- export default interface ECSpresso<ComponentTypes extends Record<string, any> = {}, EventTypes extends Record<string, any> = {}, ResourceTypes extends Record<string, any> = {}, AssetTypes extends Record<string, unknown> = {}, ScreenStates extends Record<string, ScreenDefinition<any, any>> = {}> {
17
+ export default interface ECSpresso<ComponentTypes extends Record<string, any> = {}, EventTypes extends Record<string, any> = {}, ResourceTypes extends Record<string, any> = {}, AssetTypes extends Record<string, unknown> = {}, ScreenStates extends Record<string, ScreenDefinition<any, any>> = {}, Labels extends string = string, Groups extends string = string, AssetGroupNames extends string = string, ReactiveQueryNames extends string = string> {
17
18
  /**
18
19
  * Default constructor
19
20
  */
20
- new (): ECSpresso<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates>;
21
+ new (): ECSpresso<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates, Labels, Groups, AssetGroupNames, ReactiveQueryNames>;
21
22
  }
22
23
  /**
23
24
  * ECSpresso is the central ECS framework class that connects all features.
24
25
  * It handles creation and management of entities, components, and systems, and provides lifecycle hooks.
25
26
  */
26
- export default class ECSpresso<ComponentTypes extends Record<string, any> = {}, EventTypes extends Record<string, any> = {}, ResourceTypes extends Record<string, any> = {}, AssetTypes extends Record<string, unknown> = {}, ScreenStates extends Record<string, ScreenDefinition<any, any>> = {}> {
27
+ export default class ECSpresso<ComponentTypes extends Record<string, any> = {}, EventTypes extends Record<string, any> = {}, ResourceTypes extends Record<string, any> = {}, AssetTypes extends Record<string, unknown> = {}, ScreenStates extends Record<string, ScreenDefinition<any, any>> = {}, Labels extends string = string, Groups extends string = string, AssetGroupNames extends string = string, ReactiveQueryNames extends string = string> {
27
28
  /** Library version*/
28
29
  static readonly VERSION: string;
29
30
  /** Access/modify stored components and entities*/
@@ -64,36 +65,57 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
64
65
  private _interpolationAlpha;
65
66
  /** Maximum fixed update steps per frame (spiral-of-death protection) */
66
67
  private _maxFixedSteps;
68
+ /** Registry of required component relationships: trigger -> [{component, factory}] */
69
+ private _requiredComponents;
70
+ /** Pending bundle assets awaiting manager creation at build time */
71
+ private _pendingBundleAssets;
72
+ /** Pending bundle screens awaiting manager creation at build time */
73
+ private _pendingBundleScreens;
74
+ /** Whether diagnostics timing collection is enabled */
75
+ private _diagnosticsEnabled;
76
+ /** Per-system timing in ms, populated when diagnostics enabled */
77
+ private _systemTimings;
78
+ /** Per-phase timing in ms, populated when diagnostics enabled */
79
+ private _phaseTimings;
67
80
  /**
68
81
  * Creates a new ECSpresso instance.
69
82
  */
70
83
  constructor();
71
84
  /**
72
- * Sets up component lifecycle hooks for reactive query tracking
85
+ * Subscribes to EntityManager lifecycle hooks for change detection,
86
+ * required component auto-addition, and reactive query tracking.
73
87
  * @private
74
88
  */
75
- private _setupReactiveQueryHooks;
89
+ private _subscribeLifecycleHooks;
76
90
  /**
77
91
  * Creates a new ECSpresso builder for type-safe bundle installation.
78
92
  * This is the preferred way to create an ECSpresso instance with bundles.
93
+ * Types are inferred from the builder chain — use `.withBundle()`,
94
+ * `.withComponentTypes<T>()`, `.withEventTypes<T>()`, and `.withResource()`
95
+ * to accumulate types without manual aggregate interfaces.
79
96
  *
80
97
  * @returns A builder instance for fluent method chaining
81
98
  *
82
99
  * @example
83
100
  * ```typescript
84
- * const ecs = ECSpresso.create<BaseComponents, BaseEvents, BaseResources>()
85
- * .withBundle(bundle1)
86
- * .withBundle(bundle2)
101
+ * const ecs = ECSpresso.create()
102
+ * .withBundle(createRenderer2DBundle({ ... }))
103
+ * .withBundle(createPhysics2DBundle())
104
+ * .withComponentTypes<{ player: true; enemy: { type: string } }>()
105
+ * .withEventTypes<{ gameStart: true }>()
106
+ * .withResource('score', { value: 0 })
87
107
  * .build();
108
+ *
109
+ * type ECS = typeof ecs;
88
110
  * ```
89
111
  */
90
- static create<C extends Record<string, any> = {}, E extends Record<string, any> = {}, R extends Record<string, any> = {}, A extends Record<string, unknown> = {}, S extends Record<string, ScreenDefinition<any, any>> = {}>(): ECSpressoBuilder<C, E, R, A, S>;
112
+ static create<C extends Record<string, any> = {}, E extends Record<string, any> = {}, R extends Record<string, any> = {}, A extends Record<string, unknown> = {}, S extends Record<string, ScreenDefinition<any, any>> = {}>(): ECSpressoBuilder<C, E, R, A, S, never, never, never, never>;
91
113
  /**
92
114
  * Adds a system directly to this ECSpresso instance
93
115
  * @param label Unique name to identify the system
94
116
  * @returns A SystemBuilder instance for method chaining
95
117
  */
96
- addSystem(label: string): import("./system-builder").SystemBuilderWithEcspresso<ComponentTypes, EventTypes, ResourceTypes, {}>;
118
+ addSystem(label: string): import("./system-builder").SystemBuilderWithEcspresso<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates, {}, string, never>;
97
119
  /**
98
120
  * Update all systems across execution phases.
99
121
  * Phases run in order: preUpdate -> fixedUpdate -> update -> postUpdate -> render.
@@ -106,6 +128,11 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
106
128
  * @private
107
129
  */
108
130
  private _executePhase;
131
+ /**
132
+ * Execute a non-fixed phase with optional timing, then play back the command buffer.
133
+ * @private
134
+ */
135
+ private _runPhase;
109
136
  /**
110
137
  * Initialize all resources and systems
111
138
  * This method:
@@ -141,14 +168,14 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
141
168
  * @param priority The new priority value (higher values execute first)
142
169
  * @returns true if the system was found and updated, false otherwise
143
170
  */
144
- updateSystemPriority(label: string, priority: number): boolean;
171
+ updateSystemPriority(label: Labels, priority: number): boolean;
145
172
  /**
146
173
  * Move a system to a different execution phase at runtime.
147
174
  * @param label The unique label of the system to move
148
175
  * @param phase The target phase
149
176
  * @returns true if the system was found and updated, false otherwise
150
177
  */
151
- updateSystemPhase(label: string, phase: SystemPhase): boolean;
178
+ updateSystemPhase(label: Labels, phase: SystemPhase): boolean;
152
179
  /**
153
180
  * The interpolation alpha between fixed update steps.
154
181
  * Ranges from 0 to <1, representing how far into the next
@@ -164,31 +191,31 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
164
191
  * Disable a system group. Systems in this group will be skipped during update().
165
192
  * @param groupName The name of the group to disable
166
193
  */
167
- disableSystemGroup(groupName: string): void;
194
+ disableSystemGroup(groupName: Groups): void;
168
195
  /**
169
196
  * Enable a system group. Systems in this group will run during update().
170
197
  * @param groupName The name of the group to enable
171
198
  */
172
- enableSystemGroup(groupName: string): void;
199
+ enableSystemGroup(groupName: Groups): void;
173
200
  /**
174
201
  * Check if a system group is enabled.
175
202
  * @param groupName The name of the group to check
176
203
  * @returns true if the group is enabled (or doesn't exist), false if disabled
177
204
  */
178
- isSystemGroupEnabled(groupName: string): boolean;
205
+ isSystemGroupEnabled(groupName: Groups): boolean;
179
206
  /**
180
207
  * Get all system labels that belong to a specific group.
181
208
  * @param groupName The name of the group
182
209
  * @returns Array of system labels in the group
183
210
  */
184
- getSystemsInGroup(groupName: string): string[];
211
+ getSystemsInGroup(groupName: Groups): string[];
185
212
  /**
186
213
  * Remove a system by its label
187
214
  * Calls the system's onDetach method with this ECSpresso instance if defined
188
215
  * @param label The unique label of the system to remove
189
216
  * @returns true if the system was found and removed, false otherwise
190
217
  */
191
- removeSystem(label: string): boolean;
218
+ removeSystem(label: Labels): boolean;
192
219
  /**
193
220
  * Internal method to register a system with this ECSpresso instance
194
221
  * @internal Used by SystemBuilder - replaces direct private property access
@@ -199,17 +226,36 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
199
226
  */
200
227
  hasResource<K extends keyof ResourceTypes>(key: K): boolean;
201
228
  /**
202
- * Get a resource if it exists, or undefined if not
203
- */
229
+ * Get a resource by key. Throws if the resource is not found.
230
+ * @param key The resource key
231
+ * @returns The resource value
232
+ * @throws Error if resource not found
233
+ * @see tryGetResource — the non-throwing alternative that returns undefined
234
+ */
204
235
  getResource<K extends keyof ResourceTypes>(key: K): ResourceTypes[K];
236
+ /**
237
+ * Try to get a resource by key. Returns undefined if the resource is not found.
238
+ * Inspired by Bevy's `World::get_resource::<T>()` which returns `Option<&T>`.
239
+ *
240
+ * Two overloads:
241
+ * 1. Known key — full type safety from `ResourceTypes`
242
+ * 2. String key with explicit type param — for cross-bundle optional dependencies
243
+ *
244
+ * @example
245
+ * ```typescript
246
+ * // Known key (type inferred from ResourceTypes)
247
+ * const score = ecs.tryGetResource('score'); // ScoreResource | undefined
248
+ *
249
+ * // Cross-bundle optional dependency (caller specifies expected type)
250
+ * const si = ecs.tryGetResource<SpatialIndex>('spatialIndex') ?? null;
251
+ * ```
252
+ */
253
+ tryGetResource<K extends keyof ResourceTypes>(key: K): ResourceTypes[K] | undefined;
254
+ tryGetResource<T>(key: unknown extends T ? never : string): T | undefined;
205
255
  /**
206
256
  * Add a resource to the ECS instance
207
257
  */
208
- addResource<K extends keyof ResourceTypes>(key: K, resource: ResourceTypes[K] | ((ecs: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>) => ResourceTypes[K] | Promise<ResourceTypes[K]>) | {
209
- dependsOn?: readonly string[];
210
- factory: (ecs: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>) => ResourceTypes[K] | Promise<ResourceTypes[K]>;
211
- onDispose?: (resource: ResourceTypes[K], ecs?: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>) => void | Promise<void>;
212
- }): this;
258
+ addResource<K extends keyof ResourceTypes>(key: K, resource: ResourceTypes[K] | ((ecs: ECSpresso<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates>) => ResourceTypes[K] | Promise<ResourceTypes[K]>) | ResourceFactoryWithDeps<ResourceTypes[K], ECSpresso<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates>, keyof ResourceTypes & string>): this;
213
259
  /**
214
260
  * Remove a resource from the ECS instance (without calling onDispose)
215
261
  * @param key The resource key to remove
@@ -258,7 +304,7 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
258
304
  */
259
305
  spawn<T extends {
260
306
  [K in keyof ComponentTypes]?: ComponentTypes[K];
261
- }>(components: T & Record<Exclude<keyof T, keyof ComponentTypes>, never>): FilteredEntity<ComponentTypes, keyof T & keyof ComponentTypes, never>;
307
+ }>(components: T & Record<Exclude<keyof T, keyof ComponentTypes>, never>): FilteredEntity<ComponentTypes, keyof T & keyof ComponentTypes>;
262
308
  /**
263
309
  * Get all entities with specific components
264
310
  */
@@ -289,89 +335,89 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
289
335
  removeEntity(entityOrId: number | Entity<ComponentTypes>, options?: RemoveEntityOptions): boolean;
290
336
  /**
291
337
  * Create an entity as a child of another entity with initial components
292
- * @param parentId The parent entity ID
338
+ * @param parentOrId The parent entity or entity ID
293
339
  * @param components Initial components to add
294
340
  * @returns The created child entity
295
341
  */
296
342
  spawnChild<T extends {
297
343
  [K in keyof ComponentTypes]?: ComponentTypes[K];
298
- }>(parentId: number, components: T & Record<Exclude<keyof T, keyof ComponentTypes>, never>): FilteredEntity<ComponentTypes, keyof T & keyof ComponentTypes, never>;
344
+ }>(parentOrId: number | Entity<ComponentTypes>, components: T & Record<Exclude<keyof T, keyof ComponentTypes>, never>): FilteredEntity<ComponentTypes, keyof T & keyof ComponentTypes>;
299
345
  /**
300
346
  * Set the parent of an entity
301
- * @param childId The entity to set as a child
302
- * @param parentId The entity to set as the parent
347
+ * @param childOrId The entity or entity ID to set as a child
348
+ * @param parentOrId The entity or entity ID to set as the parent
303
349
  */
304
- setParent(childId: number, parentId: number): this;
350
+ setParent(childOrId: number | Entity<ComponentTypes>, parentOrId: number | Entity<ComponentTypes>): this;
305
351
  /**
306
352
  * Remove the parent relationship for an entity (orphan it)
307
- * @param childId The entity to orphan
353
+ * @param childOrId The entity or entity ID to orphan
308
354
  * @returns true if a parent was removed, false if entity had no parent
309
355
  */
310
- removeParent(childId: number): boolean;
356
+ removeParent(childOrId: number | Entity<ComponentTypes>): boolean;
311
357
  /**
312
358
  * Get the parent of an entity
313
- * @param entityId The entity to get the parent of
359
+ * @param entityOrId The entity or entity ID to get the parent of
314
360
  * @returns The parent entity ID, or null if no parent
315
361
  */
316
- getParent(entityId: number): number | null;
362
+ getParent(entityOrId: number | Entity<ComponentTypes>): number | null;
317
363
  /**
318
364
  * Get all children of an entity in insertion order
319
- * @param parentId The parent entity
365
+ * @param parentOrId The parent entity or entity ID
320
366
  * @returns Readonly array of child entity IDs
321
367
  */
322
- getChildren(parentId: number): readonly number[];
368
+ getChildren(parentOrId: number | Entity<ComponentTypes>): readonly number[];
323
369
  /**
324
370
  * Get a child at a specific index
325
- * @param parentId The parent entity
371
+ * @param parentOrId The parent entity or entity ID
326
372
  * @param index The index of the child
327
373
  * @returns The child entity ID, or null if index is out of bounds
328
374
  */
329
- getChildAt(parentId: number, index: number): number | null;
375
+ getChildAt(parentOrId: number | Entity<ComponentTypes>, index: number): number | null;
330
376
  /**
331
377
  * Get the index of a child within its parent's children list
332
- * @param parentId The parent entity
333
- * @param childId The child entity to find
378
+ * @param parentOrId The parent entity or entity ID
379
+ * @param childOrId The child entity or entity ID to find
334
380
  * @returns The index of the child, or -1 if not found
335
381
  */
336
- getChildIndex(parentId: number, childId: number): number;
382
+ getChildIndex(parentOrId: number | Entity<ComponentTypes>, childOrId: number | Entity<ComponentTypes>): number;
337
383
  /**
338
384
  * Get all ancestors of an entity in order [parent, grandparent, ...]
339
- * @param entityId The entity to get ancestors of
385
+ * @param entityOrId The entity or entity ID to get ancestors of
340
386
  * @returns Readonly array of ancestor entity IDs
341
387
  */
342
- getAncestors(entityId: number): readonly number[];
388
+ getAncestors(entityOrId: number | Entity<ComponentTypes>): readonly number[];
343
389
  /**
344
390
  * Get all descendants of an entity in depth-first order
345
- * @param entityId The entity to get descendants of
391
+ * @param entityOrId The entity or entity ID to get descendants of
346
392
  * @returns Readonly array of descendant entity IDs
347
393
  */
348
- getDescendants(entityId: number): readonly number[];
394
+ getDescendants(entityOrId: number | Entity<ComponentTypes>): readonly number[];
349
395
  /**
350
396
  * Get the root ancestor of an entity (topmost parent), or self if no parent
351
- * @param entityId The entity to get the root of
397
+ * @param entityOrId The entity or entity ID to get the root of
352
398
  * @returns The root entity ID
353
399
  */
354
- getRoot(entityId: number): number;
400
+ getRoot(entityOrId: number | Entity<ComponentTypes>): number;
355
401
  /**
356
402
  * Get siblings of an entity (other children of the same parent)
357
- * @param entityId The entity to get siblings of
403
+ * @param entityOrId The entity or entity ID to get siblings of
358
404
  * @returns Readonly array of sibling entity IDs
359
405
  */
360
- getSiblings(entityId: number): readonly number[];
406
+ getSiblings(entityOrId: number | Entity<ComponentTypes>): readonly number[];
361
407
  /**
362
408
  * Check if an entity is a descendant of another entity
363
- * @param entityId The potential descendant
364
- * @param ancestorId The potential ancestor
365
- * @returns true if entityId is a descendant of ancestorId
409
+ * @param entityOrId The potential descendant (entity or ID)
410
+ * @param ancestorOrId The potential ancestor (entity or ID)
411
+ * @returns true if entityOrId is a descendant of ancestorOrId
366
412
  */
367
- isDescendantOf(entityId: number, ancestorId: number): boolean;
413
+ isDescendantOf(entityOrId: number | Entity<ComponentTypes>, ancestorOrId: number | Entity<ComponentTypes>): boolean;
368
414
  /**
369
415
  * Check if an entity is an ancestor of another entity
370
- * @param entityId The potential ancestor
371
- * @param descendantId The potential descendant
372
- * @returns true if entityId is an ancestor of descendantId
416
+ * @param entityOrId The potential ancestor (entity or ID)
417
+ * @param descendantOrId The potential descendant (entity or ID)
418
+ * @returns true if entityOrId is an ancestor of descendantOrId
373
419
  */
374
- isAncestorOf(entityId: number, descendantId: number): boolean;
420
+ isAncestorOf(entityOrId: number | Entity<ComponentTypes>, descendantOrId: number | Entity<ComponentTypes>): boolean;
375
421
  /**
376
422
  * Get all root entities (entities that have children but no parent)
377
423
  * @returns Readonly array of root entity IDs
@@ -413,7 +459,7 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
413
459
  * ecs.commands.spawn({ position: { x: 0, y: 0 } });
414
460
  * ```
415
461
  */
416
- get commands(): CommandBuffer<ComponentTypes, EventTypes, ResourceTypes>;
462
+ get commands(): CommandBuffer<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates>;
417
463
  /**
418
464
  * The current tick number, incremented at the end of each update()
419
465
  */
@@ -425,14 +471,47 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
425
471
  * Manual change detection should compare: getChangeSeq(...) > changeThreshold
426
472
  */
427
473
  get changeThreshold(): number;
474
+ /**
475
+ * Toggle diagnostics timing collection. When enabled, system and phase
476
+ * timings are recorded each frame. When disabled, timing maps are cleared
477
+ * and no overhead is incurred.
478
+ */
479
+ enableDiagnostics(enabled: boolean): void;
480
+ get diagnosticsEnabled(): boolean;
481
+ get systemTimings(): ReadonlyMap<string, number>;
482
+ get phaseTimings(): Readonly<Record<SystemPhase, number>>;
483
+ get entityCount(): number;
428
484
  /**
429
485
  * Mark a component as changed on an entity.
430
486
  * Each call increments a global monotonic sequence; systems with changed
431
487
  * queries will see the mark exactly once (on their next execution).
432
- * @param entityId The entity ID
488
+ * @param entityOrId The entity or entity ID
433
489
  * @param componentName The component that was changed
434
490
  */
435
- markChanged<K extends keyof ComponentTypes>(entityId: number, componentName: K): void;
491
+ markChanged<K extends keyof ComponentTypes>(entityOrId: number | Entity<ComponentTypes>, componentName: K): void;
492
+ /**
493
+ * Register a dispose callback for a component type.
494
+ * Called when a component is removed (explicit removal, entity destruction, or replacement).
495
+ * Later registrations replace earlier ones for the same component type.
496
+ * @param componentName The component type to register disposal for
497
+ * @param callback Function receiving the component value being disposed
498
+ */
499
+ registerDispose<K extends keyof ComponentTypes>(componentName: K, callback: (value: ComponentTypes[K]) => void): void;
500
+ /**
501
+ * Register a required component relationship.
502
+ * When an entity gains `trigger`, the `required` component is auto-added
503
+ * (using `factory` for the default value) if not already present.
504
+ * Enforced at insertion time (spawn/addComponent) only — removal is unrestricted.
505
+ * @param trigger The component whose presence triggers auto-addition
506
+ * @param required The component to auto-add
507
+ * @param factory Function that creates the default value for the required component
508
+ */
509
+ registerRequired<Trigger extends keyof ComponentTypes, Required extends keyof ComponentTypes>(trigger: Trigger, required: Required, factory: (triggerValue: ComponentTypes[Trigger]) => ComponentTypes[Required]): void;
510
+ /**
511
+ * Check for circular dependencies in the required components graph.
512
+ * @throws Error if adding trigger→newRequired would create a cycle
513
+ */
514
+ private _checkRequiredCycle;
436
515
  /**
437
516
  * Register a callback when a specific component is added to any entity
438
517
  * @param componentName The component key
@@ -452,13 +531,13 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
452
531
  * @param name Unique name for the query
453
532
  * @param definition Query definition with with/without arrays and onEnter/onExit callbacks
454
533
  */
455
- addReactiveQuery<WithComponents extends keyof ComponentTypes, WithoutComponents extends keyof ComponentTypes = never, OptionalComponents extends keyof ComponentTypes = never>(name: string, definition: ReactiveQueryDefinition<ComponentTypes, WithComponents, WithoutComponents, OptionalComponents>): void;
534
+ addReactiveQuery<WithComponents extends keyof ComponentTypes, WithoutComponents extends keyof ComponentTypes = never, OptionalComponents extends keyof ComponentTypes = never>(name: ReactiveQueryNames, definition: ReactiveQueryDefinition<ComponentTypes, WithComponents, WithoutComponents, OptionalComponents>): void;
456
535
  /**
457
536
  * Remove a reactive query by name.
458
537
  * @param name Name of the query to remove
459
538
  * @returns true if the query existed and was removed, false otherwise
460
539
  */
461
- removeReactiveQuery(name: string): boolean;
540
+ removeReactiveQuery(name: ReactiveQueryNames): boolean;
462
541
  /**
463
542
  * Subscribe to an event (convenience wrapper for eventBus.subscribe)
464
543
  * @param eventType The event type to subscribe to
@@ -478,7 +557,8 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
478
557
  * @param callback The hook to call after all systems have processed
479
558
  * @returns An unsubscribe function to remove the hook
480
559
  */
481
- onPostUpdate(callback: (ecs: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>, deltaTime: number) => void): () => void;
560
+ onPostUpdate(callback: (ecs: ECSpresso<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates>, deltaTime: number) => void): () => void;
561
+ private requireAssetManager;
482
562
  /**
483
563
  * Get a loaded asset by key. Throws if not loaded.
484
564
  */
@@ -502,15 +582,16 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
502
582
  /**
503
583
  * Load all assets in a group
504
584
  */
505
- loadAssetGroup(groupName: string): Promise<void>;
585
+ loadAssetGroup(groupName: AssetGroupNames): Promise<void>;
506
586
  /**
507
587
  * Check if all assets in a group are loaded
508
588
  */
509
- isAssetGroupLoaded(groupName: string): boolean;
589
+ isAssetGroupLoaded(groupName: AssetGroupNames): boolean;
510
590
  /**
511
591
  * Get the loading progress of a group (0-1)
512
592
  */
513
- getAssetGroupProgress(groupName: string): number;
593
+ getAssetGroupProgress(groupName: AssetGroupNames): number;
594
+ private requireScreenManager;
514
595
  /**
515
596
  * Transition to a new screen, clearing the stack
516
597
  */
@@ -560,15 +641,19 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
560
641
  */
561
642
  getScreenStackDepth(): number;
562
643
  /**
563
- * Internal method to set the asset manager
644
+ * Internal method to set the asset manager and drain pending bundle assets
564
645
  * @internal Used by ECSpressoBuilder
565
646
  */
566
647
  _setAssetManager(manager: AssetManager<AssetTypes>): void;
567
648
  /**
568
- * Internal method to set the screen manager
649
+ * Internal method to set the screen manager and drain pending bundle screens
569
650
  * @internal Used by ECSpressoBuilder
570
651
  */
571
652
  _setScreenManager(manager: ScreenManager<ScreenStates>): void;
653
+ /** @internal */
654
+ _hasPendingBundleAssets(): boolean;
655
+ /** @internal */
656
+ _hasPendingBundleScreens(): boolean;
572
657
  /**
573
658
  * Internal method to set the fixed timestep interval
574
659
  * @internal Used by ECSpressoBuilder
@@ -579,111 +664,5 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
579
664
  * Called by the ECSpressoBuilder during the build process.
580
665
  * The type safety is guaranteed by the builder's type system.
581
666
  */
582
- _installBundle<C extends Record<string, any>, E extends Record<string, any>, R extends Record<string, any>, A extends Record<string, unknown> = {}, S extends Record<string, ScreenDefinition<any, any>> = {}>(bundle: Bundle<C, E, R, A, S>): this;
583
- }
584
- /**
585
- * Resource factory with optional dependencies and disposal callback
586
- */
587
- type ResourceFactoryWithDeps<T> = {
588
- dependsOn?: readonly string[];
589
- factory: (context?: any) => T | Promise<T>;
590
- onDispose?: (resource: T, context?: any) => void | Promise<void>;
591
- };
592
- /**
593
- * Builder class for ECSpresso that provides fluent type-safe bundle installation.
594
- * Handles type checking during build process to ensure type safety.
595
- */
596
- export declare class ECSpressoBuilder<C extends Record<string, any> = {}, E extends Record<string, any> = {}, R extends Record<string, any> = {}, A extends Record<string, unknown> = {}, S extends Record<string, ScreenDefinition<any, any>> = {}> {
597
- /** The ECSpresso instance being built*/
598
- private ecspresso;
599
- /** Asset configurator for collecting asset definitions */
600
- private assetConfigurator;
601
- /** Screen configurator for collecting screen definitions */
602
- private screenConfigurator;
603
- /** Pending resources to add during build */
604
- private pendingResources;
605
- /** Fixed timestep interval (null means use default 1/60) */
606
- private _fixedDt;
607
- constructor();
608
- /**
609
- * Add the first bundle when starting with empty types.
610
- * This overload allows any bundle to be added to an empty ECSpresso instance.
611
- */
612
- withBundle<BC extends Record<string, any>, BE extends Record<string, any>, BR extends Record<string, any>>(this: ECSpressoBuilder<{}, {}, {}, A, S>, bundle: Bundle<BC, BE, BR>): ECSpressoBuilder<BC, BE, BR, A, S>;
613
- /**
614
- * Add a subsequent bundle with type checking.
615
- * This overload enforces bundle type compatibility.
616
- */
617
- withBundle<BC extends Record<string, any>, BE extends Record<string, any>, BR extends Record<string, any>>(bundle: BundlesAreCompatible<C, BC, E, BE, R, BR> extends true ? Bundle<BC, BE, BR> : never): ECSpressoBuilder<C & BC, E & BE, R & BR, A, S>;
618
- /**
619
- * Add a resource during ECSpresso construction
620
- * @param key The resource key
621
- * @param resource The resource value, factory function, or factory with dependencies/disposal
622
- * @returns This builder with updated resource types
623
- *
624
- * @example
625
- * ```typescript
626
- * ECSpresso.create<Components, Events, Resources>()
627
- * .withResource('config', { debug: true })
628
- * .withResource('counter', () => 42)
629
- * .withResource('derived', {
630
- * dependsOn: ['base'],
631
- * factory: (ecs) => ecs.getResource('base') * 2,
632
- * onDispose: (value) => console.log('Disposed:', value)
633
- * })
634
- * .build();
635
- * ```
636
- */
637
- withResource<K extends string, V>(key: K, resource: V | ((context?: any) => V | Promise<V>) | ResourceFactoryWithDeps<V>): ECSpressoBuilder<C, E, R & Record<K, V>, A, S>;
638
- /**
639
- * Configure assets for this ECSpresso instance
640
- * @param configurator Function that receives an AssetConfigurator and returns it after adding assets
641
- * @returns This builder with updated asset types
642
- *
643
- * @example
644
- * ```typescript
645
- * ECSpresso.create<Components, Events, Resources>()
646
- * .withAssets(assets => assets
647
- * .add('playerSprite', () => loadTexture('player.png'))
648
- * .addGroup('level1', {
649
- * background: () => loadTexture('level1-bg.png'),
650
- * music: () => loadAudio('level1.mp3'),
651
- * })
652
- * )
653
- * .build();
654
- * ```
655
- */
656
- withAssets<NewA extends Record<string, unknown>>(configurator: (assets: AssetConfigurator<{}>) => AssetConfigurator<NewA>): ECSpressoBuilder<C, E, R, A & NewA, S>;
657
- /**
658
- * Configure screens for this ECSpresso instance
659
- * @param configurator Function that receives a ScreenConfigurator and returns it after adding screens
660
- * @returns This builder with updated screen types
661
- *
662
- * @example
663
- * ```typescript
664
- * ECSpresso.create<Components, Events, Resources>()
665
- * .withScreens(screens => screens
666
- * .add('loading', {
667
- * initialState: () => ({ progress: 0 }),
668
- * })
669
- * .add('gameplay', {
670
- * initialState: ({ level }) => ({ score: 0, level }),
671
- * requiredAssetGroups: ['level1'],
672
- * })
673
- * )
674
- * .build();
675
- * ```
676
- */
677
- withScreens<NewS extends Record<string, ScreenDefinition<any, any>>>(configurator: (screens: ScreenConfigurator<{}>) => ScreenConfigurator<NewS>): ECSpressoBuilder<C, E, R, A, S & NewS>;
678
- /**
679
- * Configure the fixed timestep interval for the fixedUpdate phase.
680
- * @param dt The fixed timestep in seconds (e.g., 1/60 for 60Hz physics)
681
- * @returns This builder for method chaining
682
- */
683
- withFixedTimestep(dt: number): this;
684
- /**
685
- * Complete the build process and return the built ECSpresso instance
686
- */
687
- build(): ECSpresso<C, E, R, A, S>;
667
+ _installBundle<C extends Record<string, any>, E extends Record<string, any>, R extends Record<string, any>, A extends Record<string, unknown> = {}, S extends Record<string, ScreenDefinition<any, any>> = {}>(bundle: Bundle<C, E, R, A, S, any, any, any, any>): this;
688
668
  }
689
- export {};