ecspresso 0.4.3 → 0.6.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.
@@ -1,23 +1,28 @@
1
1
  import EntityManager from "./entity-manager";
2
2
  import EventBus from "./event-bus";
3
- import type { System, FilteredEntity, Entity } from "./types";
3
+ import AssetManager from "./asset-manager";
4
+ import ScreenManager from "./screen-manager";
5
+ import { type ReactiveQueryDefinition } from "./reactive-query-manager";
6
+ import type { System, FilteredEntity, Entity, RemoveEntityOptions, HierarchyEntry, HierarchyIteratorOptions } from "./types";
4
7
  import type Bundle from "./bundle";
5
8
  import type { BundlesAreCompatible } from "./type-utils";
9
+ import type { AssetHandle, AssetConfigurator } from "./asset-types";
10
+ import type { ScreenDefinition, ScreenConfigurator } from "./screen-types";
6
11
  /**
7
12
  * Interface declaration for ECSpresso constructor to ensure type augmentation works properly.
8
13
  * This merges with the class declaration below.
9
14
  */
10
- export default interface ECSpresso<ComponentTypes extends Record<string, any> = {}, EventTypes extends Record<string, any> = {}, ResourceTypes extends Record<string, any> = {}> {
15
+ 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>> = {}> {
11
16
  /**
12
17
  * Default constructor
13
18
  */
14
- new (): ECSpresso<ComponentTypes, EventTypes, ResourceTypes>;
19
+ new (): ECSpresso<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates>;
15
20
  }
16
21
  /**
17
22
  * ECSpresso is the central ECS framework class that connects all features.
18
23
  * It handles creation and management of entities, components, and systems, and provides lifecycle hooks.
19
24
  */
20
- export default class ECSpresso<ComponentTypes extends Record<string, any> = {}, EventTypes extends Record<string, any> = {}, ResourceTypes extends Record<string, any> = {}> {
25
+ 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>> = {}> {
21
26
  /** Library version*/
22
27
  static readonly VERSION: string;
23
28
  /** Access/modify stored components and entities*/
@@ -32,10 +37,25 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
32
37
  private _sortedSystems;
33
38
  /** Track installed bundles to prevent duplicates*/
34
39
  private _installedBundles;
40
+ /** Disabled system groups */
41
+ private _disabledGroups;
42
+ /** Asset manager for loading and accessing assets */
43
+ private _assetManager;
44
+ /** Screen manager for state/screen transitions */
45
+ private _screenManager;
46
+ /** Reactive query manager for enter/exit callbacks */
47
+ private _reactiveQueryManager;
48
+ /** Post-update hooks to be called after all systems in update() */
49
+ private _postUpdateHooks;
35
50
  /**
36
51
  * Creates a new ECSpresso instance.
37
52
  */
38
53
  constructor();
54
+ /**
55
+ * Sets up component lifecycle hooks for reactive query tracking
56
+ * @private
57
+ */
58
+ private _setupReactiveQueryHooks;
39
59
  /**
40
60
  * Creates a new ECSpresso builder for type-safe bundle installation.
41
61
  * This is the preferred way to create an ECSpresso instance with bundles.
@@ -50,7 +70,7 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
50
70
  * .build();
51
71
  * ```
52
72
  */
53
- static create<C extends Record<string, any> = {}, E extends Record<string, any> = {}, R extends Record<string, any> = {}>(): ECSpressoBuilder<C, E, R>;
73
+ 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>;
54
74
  /**
55
75
  * Adds a system directly to this ECSpresso instance
56
76
  * @param label Unique name to identify the system
@@ -66,7 +86,9 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
66
86
  * Initialize all resources and systems
67
87
  * This method:
68
88
  * 1. Initializes all resources that were added as factory functions
69
- * 2. Calls the onInitialize lifecycle hook on all systems
89
+ * 2. Sets up asset manager and loads eager assets
90
+ * 3. Sets up screen manager
91
+ * 4. Calls the onInitialize lifecycle hook on all systems
70
92
  *
71
93
  * This is useful for game startup to ensure all resources are ready
72
94
  * and systems are properly initialized before the game loop begins.
@@ -95,6 +117,28 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
95
117
  * @returns true if the system was found and updated, false otherwise
96
118
  */
97
119
  updateSystemPriority(label: string, priority: number): boolean;
120
+ /**
121
+ * Disable a system group. Systems in this group will be skipped during update().
122
+ * @param groupName The name of the group to disable
123
+ */
124
+ disableSystemGroup(groupName: string): void;
125
+ /**
126
+ * Enable a system group. Systems in this group will run during update().
127
+ * @param groupName The name of the group to enable
128
+ */
129
+ enableSystemGroup(groupName: string): void;
130
+ /**
131
+ * Check if a system group is enabled.
132
+ * @param groupName The name of the group to check
133
+ * @returns true if the group is enabled (or doesn't exist), false if disabled
134
+ */
135
+ isSystemGroupEnabled(groupName: string): boolean;
136
+ /**
137
+ * Get all system labels that belong to a specific group.
138
+ * @param groupName The name of the group
139
+ * @returns Array of system labels in the group
140
+ */
141
+ getSystemsInGroup(groupName: string): string[];
98
142
  /**
99
143
  * Remove a system by its label
100
144
  * Calls the system's onDetach method with this ECSpresso instance if defined
@@ -106,7 +150,7 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
106
150
  * Internal method to register a system with this ECSpresso instance
107
151
  * @internal Used by SystemBuilder - replaces direct private property access
108
152
  */
109
- _registerSystem(system: System<ComponentTypes, any, any, EventTypes, ResourceTypes>): void;
153
+ _registerSystem(system: System<ComponentTypes, any, any, EventTypes, ResourceTypes, AssetTypes, ScreenStates>): void;
110
154
  /**
111
155
  * Check if a resource exists
112
156
  */
@@ -118,13 +162,29 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
118
162
  /**
119
163
  * Add a resource to the ECS instance
120
164
  */
121
- addResource<K extends keyof ResourceTypes>(key: K, resource: ResourceTypes[K] | ((ecs: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>) => ResourceTypes[K] | Promise<ResourceTypes[K]>)): this;
165
+ addResource<K extends keyof ResourceTypes>(key: K, resource: ResourceTypes[K] | ((ecs: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>) => ResourceTypes[K] | Promise<ResourceTypes[K]>) | {
166
+ dependsOn?: readonly string[];
167
+ factory: (ecs: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>) => ResourceTypes[K] | Promise<ResourceTypes[K]>;
168
+ onDispose?: (resource: ResourceTypes[K], ecs?: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>) => void | Promise<void>;
169
+ }): this;
122
170
  /**
123
- * Remove a resource from the ECS instance
171
+ * Remove a resource from the ECS instance (without calling onDispose)
124
172
  * @param key The resource key to remove
125
173
  * @returns True if the resource was removed, false if it didn't exist
126
174
  */
127
175
  removeResource<K extends keyof ResourceTypes>(key: K): boolean;
176
+ /**
177
+ * Dispose a single resource, calling its onDispose callback if defined
178
+ * @param key The resource key to dispose
179
+ * @returns True if the resource existed and was disposed, false if it didn't exist
180
+ */
181
+ disposeResource<K extends keyof ResourceTypes>(key: K): Promise<boolean>;
182
+ /**
183
+ * Dispose all initialized resources in reverse dependency order.
184
+ * Resources that depend on others are disposed first.
185
+ * Calls each resource's onDispose callback if defined.
186
+ */
187
+ disposeResources(): Promise<void>;
128
188
  /**
129
189
  * Update an existing resource using an updater function
130
190
  * @param key The resource key to update
@@ -155,44 +215,371 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
155
215
  */
156
216
  spawn<T extends {
157
217
  [K in keyof ComponentTypes]?: ComponentTypes[K];
158
- }>(components: T & Record<Exclude<keyof T, keyof ComponentTypes>, never>): Entity<ComponentTypes>;
218
+ }>(components: T & Record<Exclude<keyof T, keyof ComponentTypes>, never>): FilteredEntity<ComponentTypes, keyof T & keyof ComponentTypes, never>;
159
219
  /**
160
220
  * Get all entities with specific components
161
221
  */
162
222
  getEntitiesWithQuery<WithComponents extends keyof ComponentTypes, WithoutComponents extends keyof ComponentTypes = never>(withComponents: ReadonlyArray<WithComponents>, withoutComponents?: ReadonlyArray<WithoutComponents>): Array<FilteredEntity<ComponentTypes, WithComponents, WithoutComponents>>;
223
+ /**
224
+ * Remove an entity (and optionally its descendants)
225
+ * @param entityOrId Entity or entity ID to remove
226
+ * @param options Options for removal (cascade: true by default)
227
+ * @returns true if entity was removed
228
+ */
229
+ removeEntity(entityOrId: number | Entity<ComponentTypes>, options?: RemoveEntityOptions): boolean;
230
+ /**
231
+ * Create an entity as a child of another entity with initial components
232
+ * @param parentId The parent entity ID
233
+ * @param components Initial components to add
234
+ * @returns The created child entity
235
+ */
236
+ spawnChild<T extends {
237
+ [K in keyof ComponentTypes]?: ComponentTypes[K];
238
+ }>(parentId: number, components: T & Record<Exclude<keyof T, keyof ComponentTypes>, never>): FilteredEntity<ComponentTypes, keyof T & keyof ComponentTypes, never>;
239
+ /**
240
+ * Set the parent of an entity
241
+ * @param childId The entity to set as a child
242
+ * @param parentId The entity to set as the parent
243
+ */
244
+ setParent(childId: number, parentId: number): this;
245
+ /**
246
+ * Remove the parent relationship for an entity (orphan it)
247
+ * @param childId The entity to orphan
248
+ * @returns true if a parent was removed, false if entity had no parent
249
+ */
250
+ removeParent(childId: number): boolean;
251
+ /**
252
+ * Get the parent of an entity
253
+ * @param entityId The entity to get the parent of
254
+ * @returns The parent entity ID, or null if no parent
255
+ */
256
+ getParent(entityId: number): number | null;
257
+ /**
258
+ * Get all children of an entity in insertion order
259
+ * @param parentId The parent entity
260
+ * @returns Readonly array of child entity IDs
261
+ */
262
+ getChildren(parentId: number): readonly number[];
263
+ /**
264
+ * Get a child at a specific index
265
+ * @param parentId The parent entity
266
+ * @param index The index of the child
267
+ * @returns The child entity ID, or null if index is out of bounds
268
+ */
269
+ getChildAt(parentId: number, index: number): number | null;
270
+ /**
271
+ * Get the index of a child within its parent's children list
272
+ * @param parentId The parent entity
273
+ * @param childId The child entity to find
274
+ * @returns The index of the child, or -1 if not found
275
+ */
276
+ getChildIndex(parentId: number, childId: number): number;
277
+ /**
278
+ * Get all ancestors of an entity in order [parent, grandparent, ...]
279
+ * @param entityId The entity to get ancestors of
280
+ * @returns Readonly array of ancestor entity IDs
281
+ */
282
+ getAncestors(entityId: number): readonly number[];
283
+ /**
284
+ * Get all descendants of an entity in depth-first order
285
+ * @param entityId The entity to get descendants of
286
+ * @returns Readonly array of descendant entity IDs
287
+ */
288
+ getDescendants(entityId: number): readonly number[];
289
+ /**
290
+ * Get the root ancestor of an entity (topmost parent), or self if no parent
291
+ * @param entityId The entity to get the root of
292
+ * @returns The root entity ID
293
+ */
294
+ getRoot(entityId: number): number;
295
+ /**
296
+ * Get siblings of an entity (other children of the same parent)
297
+ * @param entityId The entity to get siblings of
298
+ * @returns Readonly array of sibling entity IDs
299
+ */
300
+ getSiblings(entityId: number): readonly number[];
301
+ /**
302
+ * Check if an entity is a descendant of another entity
303
+ * @param entityId The potential descendant
304
+ * @param ancestorId The potential ancestor
305
+ * @returns true if entityId is a descendant of ancestorId
306
+ */
307
+ isDescendantOf(entityId: number, ancestorId: number): boolean;
308
+ /**
309
+ * Check if an entity is an ancestor of another entity
310
+ * @param entityId The potential ancestor
311
+ * @param descendantId The potential descendant
312
+ * @returns true if entityId is an ancestor of descendantId
313
+ */
314
+ isAncestorOf(entityId: number, descendantId: number): boolean;
315
+ /**
316
+ * Get all root entities (entities that have children but no parent)
317
+ * @returns Readonly array of root entity IDs
318
+ */
319
+ getRootEntities(): readonly number[];
320
+ /**
321
+ * Traverse the hierarchy in parent-first (breadth-first) order.
322
+ * Parents are guaranteed to be visited before their children.
323
+ * @param callback Function called for each entity with (entityId, parentId, depth)
324
+ * @param options Optional traversal options (roots to filter to specific subtrees)
325
+ */
326
+ forEachInHierarchy(callback: (entityId: number, parentId: number | null, depth: number) => void, options?: HierarchyIteratorOptions): void;
327
+ /**
328
+ * Generator-based hierarchy traversal in parent-first (breadth-first) order.
329
+ * Supports early termination via break.
330
+ * @param options Optional traversal options (roots to filter to specific subtrees)
331
+ * @yields HierarchyEntry for each entity in parent-first order
332
+ */
333
+ hierarchyIterator(options?: HierarchyIteratorOptions): Generator<HierarchyEntry, void, unknown>;
334
+ /**
335
+ * Emit a hierarchy changed event
336
+ * @internal
337
+ */
338
+ private _emitHierarchyChanged;
163
339
  /**
164
340
  * Get all installed bundle IDs
165
341
  */
166
342
  get installedBundles(): string[];
167
343
  get entityManager(): EntityManager<ComponentTypes>;
168
344
  get eventBus(): EventBus<EventTypes>;
345
+ /**
346
+ * Register a callback when a specific component is added to any entity
347
+ * @param componentName The component key
348
+ * @param handler Function receiving the new component value and the entity
349
+ * @returns Unsubscribe function to remove the callback
350
+ */
351
+ onComponentAdded<K extends keyof ComponentTypes>(componentName: K, handler: (value: ComponentTypes[K], entity: Entity<ComponentTypes>) => void): () => void;
352
+ /**
353
+ * Register a callback when a specific component is removed from any entity
354
+ * @param componentName The component key
355
+ * @param handler Function receiving the old component value and the entity
356
+ * @returns Unsubscribe function to remove the callback
357
+ */
358
+ onComponentRemoved<K extends keyof ComponentTypes>(componentName: K, handler: (oldValue: ComponentTypes[K], entity: Entity<ComponentTypes>) => void): () => void;
359
+ /**
360
+ * Add a reactive query that triggers callbacks when entities enter/exit the query match.
361
+ * @param name Unique name for the query
362
+ * @param definition Query definition with with/without arrays and onEnter/onExit callbacks
363
+ */
364
+ addReactiveQuery<WithComponents extends keyof ComponentTypes, WithoutComponents extends keyof ComponentTypes = never>(name: string, definition: ReactiveQueryDefinition<ComponentTypes, WithComponents, WithoutComponents>): void;
365
+ /**
366
+ * Remove a reactive query by name.
367
+ * @param name Name of the query to remove
368
+ * @returns true if the query existed and was removed, false otherwise
369
+ */
370
+ removeReactiveQuery(name: string): boolean;
371
+ /**
372
+ * Subscribe to an event (convenience wrapper for eventBus.subscribe)
373
+ * @param eventType The event type to subscribe to
374
+ * @param callback The callback to invoke when the event is published
375
+ * @returns An unsubscribe function
376
+ */
377
+ on<E extends keyof EventTypes>(eventType: E, callback: (data: EventTypes[E]) => void): () => void;
378
+ /**
379
+ * Unsubscribe from an event by callback reference (convenience wrapper for eventBus.unsubscribe)
380
+ * @param eventType The event type to unsubscribe from
381
+ * @param callback The callback to remove
382
+ * @returns true if the callback was found and removed, false otherwise
383
+ */
384
+ off<E extends keyof EventTypes>(eventType: E, callback: (data: EventTypes[E]) => void): boolean;
385
+ /**
386
+ * Register a hook that runs after all systems in update()
387
+ * @param callback The hook to call after all systems have processed
388
+ * @returns An unsubscribe function to remove the hook
389
+ */
390
+ onPostUpdate(callback: (ecs: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>, deltaTime: number) => void): () => void;
391
+ /**
392
+ * Get a loaded asset by key. Throws if not loaded.
393
+ */
394
+ getAsset<K extends keyof AssetTypes>(key: K): AssetTypes[K];
395
+ /**
396
+ * Get a loaded asset or undefined if not loaded
397
+ */
398
+ getAssetOrUndefined<K extends keyof AssetTypes>(key: K): AssetTypes[K] | undefined;
399
+ /**
400
+ * Get a handle to an asset with status information
401
+ */
402
+ getAssetHandle<K extends keyof AssetTypes>(key: K): AssetHandle<AssetTypes[K]>;
403
+ /**
404
+ * Check if an asset is loaded
405
+ */
406
+ isAssetLoaded<K extends keyof AssetTypes>(key: K): boolean;
407
+ /**
408
+ * Load a single asset
409
+ */
410
+ loadAsset<K extends keyof AssetTypes>(key: K): Promise<AssetTypes[K]>;
411
+ /**
412
+ * Load all assets in a group
413
+ */
414
+ loadAssetGroup(groupName: string): Promise<void>;
415
+ /**
416
+ * Check if all assets in a group are loaded
417
+ */
418
+ isAssetGroupLoaded(groupName: string): boolean;
419
+ /**
420
+ * Get the loading progress of a group (0-1)
421
+ */
422
+ getAssetGroupProgress(groupName: string): number;
423
+ /**
424
+ * Transition to a new screen, clearing the stack
425
+ */
426
+ setScreen<K extends keyof ScreenStates>(name: K, config: ScreenStates[K] extends ScreenDefinition<infer C, any> ? C : never): Promise<void>;
427
+ /**
428
+ * Push a screen onto the stack (overlay)
429
+ */
430
+ pushScreen<K extends keyof ScreenStates>(name: K, config: ScreenStates[K] extends ScreenDefinition<infer C, any> ? C : never): Promise<void>;
431
+ /**
432
+ * Pop the current screen and return to the previous one
433
+ */
434
+ popScreen(): Promise<void>;
435
+ /**
436
+ * Get the current screen name
437
+ */
438
+ getCurrentScreen(): keyof ScreenStates | null;
439
+ /**
440
+ * Get the current screen config (immutable)
441
+ */
442
+ getScreenConfig<K extends keyof ScreenStates>(): ScreenStates[K] extends ScreenDefinition<infer C, any> ? Readonly<C> : never;
443
+ /**
444
+ * Get the current screen config or null
445
+ */
446
+ getScreenConfigOrNull<K extends keyof ScreenStates>(): (ScreenStates[K] extends ScreenDefinition<infer C, any> ? Readonly<C> : never) | null;
447
+ /**
448
+ * Get the current screen state (mutable)
449
+ */
450
+ getScreenState<K extends keyof ScreenStates>(): ScreenStates[K] extends ScreenDefinition<any, infer S> ? S : never;
451
+ /**
452
+ * Get the current screen state or null
453
+ */
454
+ getScreenStateOrNull<K extends keyof ScreenStates>(): (ScreenStates[K] extends ScreenDefinition<any, infer S> ? S : never) | null;
455
+ /**
456
+ * Update the current screen state
457
+ */
458
+ updateScreenState<K extends keyof ScreenStates>(update: Partial<ScreenStates[K] extends ScreenDefinition<any, infer S> ? S : never> | ((current: ScreenStates[K] extends ScreenDefinition<any, infer S> ? S : never) => Partial<ScreenStates[K] extends ScreenDefinition<any, infer S> ? S : never>)): void;
459
+ /**
460
+ * Check if a screen is the current screen
461
+ */
462
+ isCurrentScreen(screenName: keyof ScreenStates): boolean;
463
+ /**
464
+ * Check if a screen is active (current or in stack)
465
+ */
466
+ isScreenActive(screenName: keyof ScreenStates): boolean;
467
+ /**
468
+ * Get the screen stack depth
469
+ */
470
+ getScreenStackDepth(): number;
471
+ /**
472
+ * Internal method to set the asset manager
473
+ * @internal Used by ECSpressoBuilder
474
+ */
475
+ _setAssetManager(manager: AssetManager<AssetTypes>): void;
476
+ /**
477
+ * Internal method to set the screen manager
478
+ * @internal Used by ECSpressoBuilder
479
+ */
480
+ _setScreenManager(manager: ScreenManager<ScreenStates>): void;
169
481
  /**
170
482
  * Internal method to install a bundle into this ECSpresso instance.
171
483
  * Called by the ECSpressoBuilder during the build process.
172
484
  * The type safety is guaranteed by the builder's type system.
173
485
  */
174
- _installBundle<C extends Record<string, any>, E extends Record<string, any>, R extends Record<string, any>>(bundle: Bundle<C, E, R>): this;
486
+ _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;
175
487
  }
488
+ /**
489
+ * Resource factory with optional dependencies and disposal callback
490
+ */
491
+ type ResourceFactoryWithDeps<T> = {
492
+ dependsOn?: readonly string[];
493
+ factory: (context?: any) => T | Promise<T>;
494
+ onDispose?: (resource: T, context?: any) => void | Promise<void>;
495
+ };
176
496
  /**
177
497
  * Builder class for ECSpresso that provides fluent type-safe bundle installation.
178
498
  * Handles type checking during build process to ensure type safety.
179
499
  */
180
- export declare class ECSpressoBuilder<C extends Record<string, any> = {}, E extends Record<string, any> = {}, R extends Record<string, any> = {}> {
500
+ 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>> = {}> {
181
501
  /** The ECSpresso instance being built*/
182
502
  private ecspresso;
503
+ /** Asset configurator for collecting asset definitions */
504
+ private assetConfigurator;
505
+ /** Screen configurator for collecting screen definitions */
506
+ private screenConfigurator;
507
+ /** Pending resources to add during build */
508
+ private pendingResources;
183
509
  constructor();
184
510
  /**
185
511
  * Add the first bundle when starting with empty types.
186
512
  * This overload allows any bundle to be added to an empty ECSpresso instance.
187
513
  */
188
- withBundle<BC extends Record<string, any>, BE extends Record<string, any>, BR extends Record<string, any>>(this: ECSpressoBuilder<{}, {}, {}>, bundle: Bundle<BC, BE, BR>): ECSpressoBuilder<BC, BE, BR>;
514
+ 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>;
189
515
  /**
190
516
  * Add a subsequent bundle with type checking.
191
517
  * This overload enforces bundle type compatibility.
192
518
  */
193
- 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>;
519
+ 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>;
520
+ /**
521
+ * Add a resource during ECSpresso construction
522
+ * @param key The resource key
523
+ * @param resource The resource value, factory function, or factory with dependencies/disposal
524
+ * @returns This builder with updated resource types
525
+ *
526
+ * @example
527
+ * ```typescript
528
+ * ECSpresso.create<Components, Events, Resources>()
529
+ * .withResource('config', { debug: true })
530
+ * .withResource('counter', () => 42)
531
+ * .withResource('derived', {
532
+ * dependsOn: ['base'],
533
+ * factory: (ecs) => ecs.getResource('base') * 2,
534
+ * onDispose: (value) => console.log('Disposed:', value)
535
+ * })
536
+ * .build();
537
+ * ```
538
+ */
539
+ 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>;
540
+ /**
541
+ * Configure assets for this ECSpresso instance
542
+ * @param configurator Function that receives an AssetConfigurator and returns it after adding assets
543
+ * @returns This builder with updated asset types
544
+ *
545
+ * @example
546
+ * ```typescript
547
+ * ECSpresso.create<Components, Events, Resources>()
548
+ * .withAssets(assets => assets
549
+ * .add('playerSprite', () => loadTexture('player.png'))
550
+ * .addGroup('level1', {
551
+ * background: () => loadTexture('level1-bg.png'),
552
+ * music: () => loadAudio('level1.mp3'),
553
+ * })
554
+ * )
555
+ * .build();
556
+ * ```
557
+ */
558
+ withAssets<NewA extends Record<string, unknown>>(configurator: (assets: AssetConfigurator<{}>) => AssetConfigurator<NewA>): ECSpressoBuilder<C, E, R, A & NewA, S>;
559
+ /**
560
+ * Configure screens for this ECSpresso instance
561
+ * @param configurator Function that receives a ScreenConfigurator and returns it after adding screens
562
+ * @returns This builder with updated screen types
563
+ *
564
+ * @example
565
+ * ```typescript
566
+ * ECSpresso.create<Components, Events, Resources>()
567
+ * .withScreens(screens => screens
568
+ * .add('loading', {
569
+ * initialState: () => ({ progress: 0 }),
570
+ * })
571
+ * .add('gameplay', {
572
+ * initialState: ({ level }) => ({ score: 0, level }),
573
+ * requiredAssetGroups: ['level1'],
574
+ * })
575
+ * )
576
+ * .build();
577
+ * ```
578
+ */
579
+ withScreens<NewS extends Record<string, ScreenDefinition<any, any>>>(configurator: (screens: ScreenConfigurator<{}>) => ScreenConfigurator<NewS>): ECSpressoBuilder<C, E, R, A, S & NewS>;
194
580
  /**
195
581
  * Complete the build process and return the built ECSpresso instance
196
582
  */
197
- build(): ECSpresso<C, E, R>;
583
+ build(): ECSpresso<C, E, R, A, S>;
198
584
  }
585
+ export {};
@@ -1,4 +1,4 @@
1
- import type { Entity, FilteredEntity } from "./types";
1
+ import type { Entity, FilteredEntity, RemoveEntityOptions, HierarchyEntry, HierarchyIteratorOptions } from "./types";
2
2
  export default class EntityManager<ComponentTypes> {
3
3
  private nextId;
4
4
  private entities;
@@ -11,6 +11,10 @@ export default class EntityManager<ComponentTypes> {
11
11
  * Callbacks registered for component removals
12
12
  */
13
13
  private removedCallbacks;
14
+ /**
15
+ * Hierarchy manager for parent-child relationships
16
+ */
17
+ private hierarchyManager;
14
18
  createEntity(): Entity<ComponentTypes>;
15
19
  addComponent<ComponentName extends keyof ComponentTypes>(entityOrId: number | Entity<ComponentTypes>, componentName: ComponentName, data: ComponentTypes[ComponentName]): this;
16
20
  /**
@@ -24,18 +28,128 @@ export default class EntityManager<ComponentTypes> {
24
28
  removeComponent<ComponentName extends keyof ComponentTypes>(entityOrId: number | Entity<ComponentTypes>, componentName: ComponentName): this;
25
29
  getComponent<ComponentName extends keyof ComponentTypes>(entityId: number, componentName: ComponentName): ComponentTypes[ComponentName] | null;
26
30
  getEntitiesWithQuery<WithComponents extends keyof ComponentTypes = never, WithoutComponents extends keyof ComponentTypes = never>(required?: ReadonlyArray<WithComponents>, excluded?: ReadonlyArray<WithoutComponents>): Array<FilteredEntity<ComponentTypes, WithComponents extends never ? never : WithComponents, WithoutComponents extends never ? never : WithoutComponents>>;
27
- removeEntity(entityOrId: number | Entity<ComponentTypes>): boolean;
31
+ removeEntity(entityOrId: number | Entity<ComponentTypes>, options?: RemoveEntityOptions): boolean;
32
+ /**
33
+ * Internal method to remove a single entity without cascade logic
34
+ */
35
+ private removeEntityInternal;
28
36
  getEntity(entityId: number): Entity<ComponentTypes> | undefined;
29
37
  /**
30
38
  * Register a callback when a specific component is added to any entity
31
39
  * @param componentName The component key
32
40
  * @param handler Function receiving the new component value and the entity
41
+ * @returns Unsubscribe function to remove the callback
33
42
  */
34
- onComponentAdded<ComponentName extends keyof ComponentTypes>(componentName: ComponentName, handler: (value: ComponentTypes[ComponentName], entity: Entity<ComponentTypes>) => void): this;
43
+ onComponentAdded<ComponentName extends keyof ComponentTypes>(componentName: ComponentName, handler: (value: ComponentTypes[ComponentName], entity: Entity<ComponentTypes>) => void): () => void;
35
44
  /**
36
45
  * Register a callback when a specific component is removed from any entity
37
46
  * @param componentName The component key
38
47
  * @param handler Function receiving the old component value and the entity
48
+ * @returns Unsubscribe function to remove the callback
49
+ */
50
+ onComponentRemoved<ComponentName extends keyof ComponentTypes>(componentName: ComponentName, handler: (oldValue: ComponentTypes[ComponentName], entity: Entity<ComponentTypes>) => void): () => void;
51
+ /**
52
+ * Create an entity as a child of another entity with initial components
53
+ * @param parentId The parent entity ID
54
+ * @param components Initial components to add
55
+ * @returns The created child entity
56
+ */
57
+ spawnChild<T extends {
58
+ [K in keyof ComponentTypes]?: ComponentTypes[K];
59
+ }>(parentId: number, components: T & Record<Exclude<keyof T, keyof ComponentTypes>, never>): FilteredEntity<ComponentTypes, keyof T & keyof ComponentTypes, never>;
60
+ /**
61
+ * Set the parent of an entity
62
+ * @param childId The entity to set as a child
63
+ * @param parentId The entity to set as the parent
64
+ */
65
+ setParent(childId: number, parentId: number): this;
66
+ /**
67
+ * Remove the parent relationship for an entity (orphan it)
68
+ * @param childId The entity to orphan
69
+ * @returns true if a parent was removed, false if entity had no parent
70
+ */
71
+ removeParent(childId: number): boolean;
72
+ /**
73
+ * Get the parent of an entity
74
+ * @param entityId The entity to get the parent of
75
+ * @returns The parent entity ID, or null if no parent
76
+ */
77
+ getParent(entityId: number): number | null;
78
+ /**
79
+ * Get all children of an entity in insertion order
80
+ * @param parentId The parent entity
81
+ * @returns Readonly array of child entity IDs
82
+ */
83
+ getChildren(parentId: number): readonly number[];
84
+ /**
85
+ * Get a child at a specific index
86
+ * @param parentId The parent entity
87
+ * @param index The index of the child
88
+ * @returns The child entity ID, or null if index is out of bounds
89
+ */
90
+ getChildAt(parentId: number, index: number): number | null;
91
+ /**
92
+ * Get the index of a child within its parent's children list
93
+ * @param parentId The parent entity
94
+ * @param childId The child entity to find
95
+ * @returns The index of the child, or -1 if not found
96
+ */
97
+ getChildIndex(parentId: number, childId: number): number;
98
+ /**
99
+ * Get all ancestors of an entity in order [parent, grandparent, ...]
100
+ * @param entityId The entity to get ancestors of
101
+ * @returns Readonly array of ancestor entity IDs
102
+ */
103
+ getAncestors(entityId: number): readonly number[];
104
+ /**
105
+ * Get all descendants of an entity in depth-first order
106
+ * @param entityId The entity to get descendants of
107
+ * @returns Readonly array of descendant entity IDs
108
+ */
109
+ getDescendants(entityId: number): readonly number[];
110
+ /**
111
+ * Get the root ancestor of an entity (topmost parent), or self if no parent
112
+ * @param entityId The entity to get the root of
113
+ * @returns The root entity ID
114
+ */
115
+ getRoot(entityId: number): number;
116
+ /**
117
+ * Get siblings of an entity (other children of the same parent)
118
+ * @param entityId The entity to get siblings of
119
+ * @returns Readonly array of sibling entity IDs
120
+ */
121
+ getSiblings(entityId: number): readonly number[];
122
+ /**
123
+ * Check if an entity is a descendant of another entity
124
+ * @param entityId The potential descendant
125
+ * @param ancestorId The potential ancestor
126
+ * @returns true if entityId is a descendant of ancestorId
127
+ */
128
+ isDescendantOf(entityId: number, ancestorId: number): boolean;
129
+ /**
130
+ * Check if an entity is an ancestor of another entity
131
+ * @param entityId The potential ancestor
132
+ * @param descendantId The potential descendant
133
+ * @returns true if entityId is an ancestor of descendantId
134
+ */
135
+ isAncestorOf(entityId: number, descendantId: number): boolean;
136
+ /**
137
+ * Get all root entities (entities that have children but no parent)
138
+ * @returns Readonly array of root entity IDs
139
+ */
140
+ getRootEntities(): readonly number[];
141
+ /**
142
+ * Traverse the hierarchy in parent-first (breadth-first) order.
143
+ * Parents are guaranteed to be visited before their children.
144
+ * @param callback Function called for each entity with (entityId, parentId, depth)
145
+ * @param options Optional traversal options (roots to filter to specific subtrees)
146
+ */
147
+ forEachInHierarchy(callback: (entityId: number, parentId: number | null, depth: number) => void, options?: HierarchyIteratorOptions): void;
148
+ /**
149
+ * Generator-based hierarchy traversal in parent-first (breadth-first) order.
150
+ * Supports early termination via break.
151
+ * @param options Optional traversal options (roots to filter to specific subtrees)
152
+ * @yields HierarchyEntry for each entity in parent-first order
39
153
  */
40
- onComponentRemoved<ComponentName extends keyof ComponentTypes>(componentName: ComponentName, handler: (oldValue: ComponentTypes[ComponentName], entity: Entity<ComponentTypes>) => void): this;
154
+ hierarchyIterator(options?: HierarchyIteratorOptions): Generator<HierarchyEntry, void, unknown>;
41
155
  }