ecspresso 0.1.4 → 0.2.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.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  *(pronounced "ex-presso")*
4
4
 
5
- __Note: This is a VERY early work in progress. 0 work on performance has been done while the API is being nailed down. The documention is also being autogenerated while ECSpresso is being iterated on.__
5
+ __Note: This is a VERY early work in progress. No work on performance has been done while the API is being nailed down. The documention is also being autogenerated while ECSpresso is being iterated on.__
6
6
 
7
7
  A type-safe, modular, and extensible Entity Component System (ECS) framework for TypeScript.
8
8
 
@@ -13,6 +13,7 @@ A type-safe, modular, and extensible Entity Component System (ECS) framework for
13
13
  - 💡 **Flexible**: Easily create entities, add components, and build systems with a clean, fluent API
14
14
  - 🔄 **Event-Driven**: Integrated event bus for communication between systems
15
15
  - 🗄️ **Resource Management**: Global resources for sharing state across systems
16
+ - ⏱️ **Priority Control**: Set execution priority for systems to ensure proper processing order
16
17
 
17
18
  ## Installation
18
19
 
@@ -42,11 +43,22 @@ interface Events {
42
43
  interface Resources {
43
44
  score: { value: number };
44
45
  gameState: 'playing' | 'paused' | 'gameOver';
46
+ assets: { textures: Record<string, any>; sounds: Record<string, any> };
45
47
  }
46
48
 
47
49
  // Create a world instance directly
48
50
  const world = new ECSpresso<Components, Events, Resources>();
49
51
 
52
+ // Add resources - can be direct values or factory functions
53
+ world.addResource('score', { value: 0 });
54
+ world.addResource('gameState', 'paused');
55
+ world.addResource('assets', async () => {
56
+ // Simulate loading game assets
57
+ const textures = await loadTextures();
58
+ const sounds = await loadSounds();
59
+ return { textures, sounds };
60
+ });
61
+
50
62
  // Add a movement system directly to the world
51
63
  world.addSystem('movement')
52
64
  .addQuery('movingEntities', {
@@ -58,6 +70,12 @@ world.addSystem('movement')
58
70
  entity.components.position.y += entity.components.velocity.y * deltaTime;
59
71
  }
60
72
  })
73
+ .setOnInitialize(async (ecs) => {
74
+ // One-time initialization of the system
75
+ console.log('Setting up movement system...');
76
+ const gameState = ecs.getResource('gameState');
77
+ gameState.lastMovementUpdate = Date.now();
78
+ })
61
79
  .build(); // Don't forget to call build() to finalize the system
62
80
 
63
81
  // Create an entity with position and velocity components
@@ -65,6 +83,9 @@ const entity = world.entityManager.createEntity();
65
83
  world.entityManager.addComponent(entity.id, 'position', { x: 0, y: 0 });
66
84
  world.entityManager.addComponent(entity.id, 'velocity', { x: 10, y: 5 });
67
85
 
86
+ // Initialize everything (resources and systems) in one call
87
+ await world.initialize();
88
+
68
89
  // Run a single update
69
90
  world.update(1/60);
70
91
 
@@ -79,7 +100,7 @@ Bundles are a powerful way to organize game features:
79
100
 
80
101
  ```typescript
81
102
  // Create a player input bundle
82
- const inputBundle = new Bundle<Components, Events, Resources>()
103
+ const inputBundle = new Bundle<Components, Events, Resources>('input-bundle')
83
104
  .addSystem('playerInput')
84
105
  .setProcess((_queries, _deltaTime, ecs) => {
85
106
  // Handle keyboard input and modify player velocity
@@ -87,7 +108,7 @@ const inputBundle = new Bundle<Components, Events, Resources>()
87
108
  });
88
109
 
89
110
  // Create a rendering bundle
90
- const renderBundle = new Bundle<Components, Events, Resources>()
111
+ const renderBundle = new Bundle<Components, Events, Resources>('render-bundle')
91
112
  .addSystem('renderer')
92
113
  .addQuery('sprites', { with: ['position', 'sprite'] })
93
114
  .setProcess((queries) => {
@@ -99,8 +120,14 @@ const renderBundle = new Bundle<Components, Events, Resources>()
99
120
  });
100
121
 
101
122
  // Create a scoring bundle that adds a resource and listens for events
102
- const scoringBundle = new Bundle<Components, Events, Resources>()
123
+ const scoringBundle = new Bundle<Components, Events, Resources>('scoring-bundle')
103
124
  .addResource('score', { value: 0 })
125
+ // Resources can also be added using factory functions
126
+ .addResource('gameStats', () => ({
127
+ highScore: 0,
128
+ totalPlayTime: 0,
129
+ sessionStartTime: Date.now()
130
+ }))
104
131
  .addSystem('scoreKeeper')
105
132
  .setEventHandlers({
106
133
  scoreChange: {
@@ -112,13 +139,45 @@ const scoringBundle = new Bundle<Components, Events, Resources>()
112
139
  }
113
140
  });
114
141
 
142
+ // Create a game initialization bundle with event handlers
143
+ const initBundle = new Bundle<Components, Events, Resources>('init-bundle')
144
+ .addSystem('initialization')
145
+ .setOnInitialize(async (ecs) => {
146
+ console.log('Game systems initializing...');
147
+ // Do one-time system setup here
148
+ })
149
+ .setEventHandlers({
150
+ gameStart: {
151
+ async handler(data, ecs) {
152
+ console.log('Game starting...');
153
+
154
+ // Initialize all resources and systems
155
+ await ecs.initialize();
156
+
157
+ // Resources and systems are now ready to use
158
+ const assets = ecs.getResource('assets');
159
+ console.log(`Loaded ${Object.keys(assets.textures).length} textures`);
160
+
161
+ // Continue with game initialization
162
+ // ...
163
+ }
164
+ }
165
+ });
166
+
115
167
  // Create the game world with all features using the builder pattern
116
168
  const game = ECSpresso.create<Components, Events, Resources>()
117
- .withBundle(physicsBundle)
169
+ .withBundle(initBundle)
118
170
  .withBundle(inputBundle)
119
171
  .withBundle(renderBundle)
120
172
  .withBundle(scoringBundle)
121
- .build();
173
+ .build()
174
+ .addResource('assets', async () => {
175
+ // This won't execute until initializeResources is called
176
+ return { textures: await loadTextures(), sounds: await loadSounds() };
177
+ });
178
+
179
+ // Start the game
180
+ game.eventBus.publish('gameStart', {});
122
181
  ```
123
182
 
124
183
  ## Type Safety with the Builder Pattern
@@ -127,8 +186,8 @@ ECSpresso uses a builder pattern to provide strong type checking for bundle comp
127
186
 
128
187
  ```typescript
129
188
  // These bundles have compatible component types
130
- const bundle1 = new Bundle<{position: {x: number, y: number}}, {}, {}>();
131
- const bundle2 = new Bundle<{velocity: {x: number, y: number}}, {}, {}>();
189
+ const bundle1 = new Bundle<{position: {x: number, y: number}}>('bundle1');
190
+ const bundle2 = new Bundle<{velocity: {x: number, y: number}}>('bundle2');
132
191
 
133
192
  // Create a world with both bundles - TypeScript will allow this
134
193
  const world = ECSpresso.create()
@@ -137,8 +196,8 @@ const world = ECSpresso.create()
137
196
  .build();
138
197
 
139
198
  // These bundles have conflicting component types
140
- const bundle3 = new Bundle<{position: {x: number, y: number}}, {}, {}>();
141
- const bundle4 = new Bundle<{position: string}, {}, {}>();
199
+ const bundle3 = new Bundle<{position: {x: number, y: number}}>('bundle3');
200
+ const bundle4 = new Bundle<{position: string}>('bundle4');
142
201
 
143
202
  // TypeScript will show an error because bundles have conflicting types
144
203
  const world2 = ECSpresso.create()
@@ -181,7 +240,7 @@ world.entityManager.removeComponent(entity.id, 'velocity');
181
240
  world.entityManager.removeEntity(entity.id);
182
241
  ```
183
242
 
184
- ## Working with Systems
243
+ ## Working with Systems and Queries
185
244
 
186
245
  Systems can be added directly to an ECSpresso instance:
187
246
 
@@ -190,26 +249,147 @@ const world = ECSpresso.create<Components, Events, Resources>()
190
249
  .build();
191
250
 
192
251
  world.addSystem('physicsSystem')
252
+ // Set system execution priority (higher numbers execute first)
253
+ .setPriority(50)
254
+ // Query entities that have both position and velocity components
193
255
  .addQuery('movingEntities', {
194
256
  with: ['position', 'velocity']
195
257
  })
258
+ // Query entities that have position but not player component
259
+ .addQuery('nonPlayerObjects', {
260
+ with: ['position'],
261
+ without: ['player']
262
+ })
263
+ // Query entities with different component combinations
264
+ .addQuery('flyingNonPlayerEntities', {
265
+ with: ['flying', 'position'],
266
+ without: ['player', 'grounded']
267
+ })
196
268
  .setProcess((queries, deltaTime) => {
269
+ // Process moving entities
197
270
  for (const entity of queries.movingEntities) {
198
- // Update positions based on velocity
199
271
  entity.components.position.x += entity.components.velocity.x * deltaTime;
200
272
  entity.components.position.y += entity.components.velocity.y * deltaTime;
201
273
  }
274
+
275
+ // Process non-player objects
276
+ for (const entity of queries.nonPlayerObjects) {
277
+ // Do something with non-player objects
278
+ }
279
+
280
+ // Process flying non-player entities
281
+ for (const entity of queries.flyingNonPlayerEntities) {
282
+ // Apply flying behavior
283
+ }
202
284
  })
203
285
  .build(); // Finalizes and adds the system to the world
204
286
  ```
205
287
 
288
+ ## System Lifecycle Hook
289
+
290
+ ECSpresso systems have two lifecycle hooks that you can implement:
291
+
292
+ ```typescript
293
+ // Add a system with all lifecycle hooks
294
+ world.addSystem('gameSystem')
295
+ // Called during the ECSpresso.initialize() method
296
+ // Good for one-time setup that depends on resources
297
+ .setOnInitialize(async (ecs) => {
298
+ console.log('System initializing');
299
+ // Load resources, set up game state, etc.
300
+
301
+ // Can be async and await other operations
302
+ await loadLevelData(ecs);
303
+ })
304
+
305
+ // Called when the system is removed from the ECSpresso instance
306
+ .setOnDetach((ecs) => {
307
+ console.log('System detached');
308
+ // Clean up resources, cancel subscriptions, etc.
309
+ })
310
+ .build();
311
+ ```
312
+
313
+ The `initialize` method on the ECSpresso instance initializes all resources and systems:
314
+
315
+ ```typescript
316
+ await ecs.initialize();
317
+ ```
318
+
319
+ ## System Priority
320
+
321
+ ECSpresso allows you to control the execution order of systems using priorities:
322
+
323
+ ```typescript
324
+ // Systems with higher priority values execute before those with lower values
325
+ // Default priority is 0 if not specified
326
+
327
+ // Rendering system (runs first)
328
+ world.addSystem('renderSystem')
329
+ .setPriority(100)
330
+ .setProcess(() => {
331
+ // Rendering logic
332
+ })
333
+ .build();
334
+
335
+ // Physics system (runs second)
336
+ world.addSystem('physicsSystem')
337
+ .setPriority(50)
338
+ .setProcess(() => {
339
+ // Physics update logic
340
+ })
341
+ .build();
342
+
343
+ // Cleanup system (runs last)
344
+ world.addSystem('cleanupSystem')
345
+ .setPriority(0) // Default priority if not specified
346
+ .setProcess(() => {
347
+ // Cleanup logic
348
+ })
349
+ .build();
350
+ ```
351
+
352
+ Systems with the same priority value execute in the order they were registered, maintaining backward compatibility with existing code.
353
+
354
+ You can also update a system's priority dynamically at runtime:
355
+
356
+ ```typescript
357
+ // Change a system's priority (higher numbers execute first)
358
+ world.updateSystemPriority('physicsSystem', 110); // Now physics will run before rendering
359
+ ```
360
+
361
+ Priority also works with systems added through bundles:
362
+
363
+ ```typescript
364
+ const highPriorityBundle = new Bundle<Components>()
365
+ .addSystem('importantSystem')
366
+ .setPriority(100)
367
+ .setProcess(() => {
368
+ // This will run first
369
+ });
370
+
371
+ const lowPriorityBundle = new Bundle<Components>()
372
+ .addSystem('lateSystem')
373
+ .setPriority(0)
374
+ .setProcess(() => {
375
+ // This will run last
376
+ });
377
+
378
+ const world = ECSpresso.create<Components>()
379
+ .withBundle(lowPriorityBundle) // Added first but runs last due to priority
380
+ .withBundle(highPriorityBundle) // Added second but runs first due to priority
381
+ .build();
382
+ ```
383
+
384
+ The system priority implementation is optimized with a cached sorting mechanism that only re-sorts systems when priorities change or when systems are added or removed, avoiding unnecessary sorting during each update cycle.
385
+
206
386
  ## Event System
207
387
 
208
388
  The event system allows communication between systems:
209
389
 
210
390
  ```typescript
211
391
  // Define an event handler in a system
212
- const collisionBundle = new Bundle<Components, Events, Resources>()
392
+ const collisionBundle = new Bundle<Components, Events, Resources>('collision-bundle')
213
393
  .addSystem('collisionResponse')
214
394
  .setEventHandlers({
215
395
  collision: {
@@ -244,7 +424,7 @@ unsubscribe();
244
424
  Resources provide global state accessible to all systems:
245
425
 
246
426
  ```typescript
247
- // Add a resource
427
+ // Add a resource directly
248
428
  world.addResource('score', { value: 0 });
249
429
 
250
430
  // Get a resource
@@ -255,6 +435,46 @@ score.value += 10;
255
435
  const hasScore = world.hasResource('score');
256
436
  ```
257
437
 
258
- ## License
438
+ ### Resource Factory Functions
439
+
440
+ Resources can also be created using factory functions, which is useful for lazy initialization or async resources:
441
+
442
+ ```typescript
443
+ // Add a resource using a synchronous factory function
444
+ world.addResource('controlMap', () => {
445
+ console.log('Creating control map');
446
+ return {
447
+ up: false,
448
+ down: false,
449
+ left: false,
450
+ right: false
451
+ };
452
+ });
453
+
454
+ // Add a resource using an asynchronous factory function
455
+ world.addResource('gameAssets', async () => {
456
+ console.log('Loading game assets...');
457
+ const assets = await loadAssets();
458
+ return assets;
459
+ });
460
+
461
+ // Factory functions are executed when the resource is first accessed
462
+ const controlMap = world.getResource('controlMap'); // Factory executes here
463
+
464
+ // Or when explicitly initialized
465
+ await world.initializeResources(); // Initializes all pending resources
466
+
467
+ // You can also initialize specific resources
468
+ await world.initializeResources('gameAssets', 'controlMap');
469
+ ```
470
+
471
+ Factory functions are useful for:
472
+ - Lazy loading of expensive resources
473
+ - Async initialization of resources requiring network or file operations
474
+ - Resources that depend on other systems being initialized first
475
+ - Avoiding circular dependencies in your initialization code
476
+
477
+ When using async factory functions, ensure you either:
478
+ 1. Call `initializeResources()` explicitly before accessing the resource, or
479
+ 2. Use `await` when getting a resource that might return a Promise
259
480
 
260
- MIT
package/dist/bundle.d.ts CHANGED
@@ -4,7 +4,7 @@ import type ECSpresso from './ecspresso';
4
4
  * Bundle class that encapsulates a set of components, resources, events, and systems
5
5
  * that can be merged into a ECSpresso instance
6
6
  */
7
- export default class Bundle<ComponentTypes extends Record<string, any> = Record<string, any>, EventTypes extends Record<string, any> = Record<string, any>, ResourceTypes extends Record<string, any> = Record<string, any>> {
7
+ export default class Bundle<ComponentTypes extends Record<string, any> = {}, EventTypes extends Record<string, any> = {}, ResourceTypes extends Record<string, any> = {}> {
8
8
  private _systems;
9
9
  private _resources;
10
10
  private _id;
@@ -25,9 +25,9 @@ export default class Bundle<ComponentTypes extends Record<string, any> = Record<
25
25
  /**
26
26
  * Add a resource to this bundle
27
27
  * @param label The resource key
28
- * @param resource The resource value
28
+ * @param resource The resource value or a factory function that returns the resource
29
29
  */
30
- addResource<K extends keyof ResourceTypes>(label: K, resource: ResourceTypes[K]): this;
30
+ addResource<K extends keyof ResourceTypes>(label: K, resource: ResourceTypes[K] | (() => ResourceTypes[K] | Promise<ResourceTypes[K]>)): this;
31
31
  /**
32
32
  * Get all systems defined in this bundle
33
33
  * Returns built System objects instead of SystemBuilders
@@ -47,7 +47,7 @@ export default class Bundle<ComponentTypes extends Record<string, any> = Record<
47
47
  * @param key The resource key
48
48
  * @returns The resource value or undefined if not found
49
49
  */
50
- getResource<K extends keyof ResourceTypes>(key: K): ResourceTypes[K] | undefined;
50
+ getResource<K extends keyof ResourceTypes>(key: K): ResourceTypes[K];
51
51
  /**
52
52
  * Get all system builders in this bundle
53
53
  */
@@ -60,6 +60,8 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
60
60
  private _resourceManager;
61
61
  /** Registered systems that will be updated in order*/
62
62
  private _systems;
63
+ /** Cached sorted systems for efficient updates */
64
+ private _sortedSystems;
63
65
  /** Track installed bundles to prevent duplicates*/
64
66
  private _installedBundles;
65
67
  /**
@@ -93,11 +95,38 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
93
95
  */
94
96
  update(deltaTime: number): void;
95
97
  /**
96
- * Internal method to install a bundle into this ECSpresso instance.
97
- * Called by the ECSpressoBuilder during the build process.
98
- * The type safety is guaranteed by the builder's type system.
98
+ * Initialize all resources and systems
99
+ * This method:
100
+ * 1. Initializes all resources that were added as factory functions
101
+ * 2. Calls the onInitialize lifecycle hook on all systems
102
+ *
103
+ * This is useful for game startup to ensure all resources are ready
104
+ * and systems are properly initialized before the game loop begins.
105
+ *
106
+ * @param resourceKeys Optional array of specific resource keys to initialize
107
+ * @returns Promise that resolves when everything is initialized
108
+ */
109
+ initialize(): Promise<void>;
110
+ /**
111
+ * Initialize specific resources or all resources that were added as factory functions but haven't been initialized yet.
112
+ * This is useful when you need to ensure resources are ready before proceeding.
113
+ * @param keys Optional array of resource keys to initialize. If not provided, all pending resources will be initialized.
114
+ * @returns Promise that resolves when the specified resources are initialized
115
+ */
116
+ initializeResources<K extends keyof ResourceTypes>(...keys: K[]): Promise<void>;
117
+ /**
118
+ * Sort the systems array by priority (higher priority first)
119
+ * Called internally when system list changes
120
+ * @private
99
121
  */
100
- _installBundle<C extends Record<string, any>, E extends Record<string, any>, R extends Record<string, any>>(bundle: Bundle<C, E, R>): this;
122
+ private _sortSystems;
123
+ /**
124
+ * Update the priority of a system
125
+ * @param label The unique label of the system to update
126
+ * @param priority The new priority value (higher values execute first)
127
+ * @returns true if the system was found and updated, false otherwise
128
+ */
129
+ updateSystemPriority(label: string, priority: number): boolean;
101
130
  /**
102
131
  * Remove a system by its label
103
132
  * Calls the system's onDetach method with this ECSpresso instance if defined
@@ -116,7 +145,7 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
116
145
  /**
117
146
  * Add a resource to the ECS instance
118
147
  */
119
- addResource<K extends keyof ResourceTypes>(key: K, resource: ResourceTypes[K]): this;
148
+ addResource<K extends keyof ResourceTypes>(key: K, resource: ResourceTypes[K] | (() => ResourceTypes[K] | Promise<ResourceTypes[K]>)): this;
120
149
  /**
121
150
  * Check if an entity has a component
122
151
  */
@@ -132,6 +161,12 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
132
161
  get entityManager(): EntityManager<ComponentTypes>;
133
162
  get eventBus(): EventBus<EventTypes>;
134
163
  get resourceManager(): ResourceManager<ResourceTypes>;
164
+ /**
165
+ * Internal method to install a bundle into this ECSpresso instance.
166
+ * Called by the ECSpressoBuilder during the build process.
167
+ * The type safety is guaranteed by the builder's type system.
168
+ */
169
+ _installBundle<C extends Record<string, any>, E extends Record<string, any>, R extends Record<string, any>>(bundle: Bundle<C, E, R>): this;
135
170
  }
136
171
  /**
137
172
  * Builder class for ECSpresso that provides fluent type-safe bundle installation.
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- class J{nextId=1;entities=new Map;componentIndices=new Map;createEntity(){let j=this.nextId++,g={id:j,components:{}};return this.entities.set(j,g),g}addComponent(j,g,f){let D=typeof j==="number"?this.entities.get(j):j;if(!D)throw new Error(`Entity ${j} does not exist`);if(D.components[g]=f,!this.componentIndices.has(g))this.componentIndices.set(g,new Set);return this.componentIndices.get(g)?.add(D.id),this}addComponents(j,g){let f=typeof j==="number"?this.entities.get(j):j;if(!f)throw new Error(`Entity ${j} does not exist`);for(let D in g)this.addComponent(f,D,g[D]);return this}removeComponent(j,g){let f=this.entities.get(j);if(!f)throw new Error(`Entity ${j} does not exist`);delete f.components[g],this.componentIndices.get(g)?.delete(j)}getComponent(j,g){let f=this.entities.get(j);if(!f)throw new Error(`Entity ${j} does not exist`);return f.components[g]||null}getEntitiesWithComponents(j=[],g=[]){if(j.length===0){if(g.length===0)return Array.from(this.entities.values());return Array.from(this.entities.values()).filter((L)=>{return g.every((F)=>!(F in L.components))})}let f=j.reduce((L,F)=>{let H=this.componentIndices.get(F),x=H?H.size:0,G=this.componentIndices.get(L)?.size??1/0;return x<G?F:L},j[0]);return Array.from(this.componentIndices.get(f)||[]).filter((L)=>{let F=this.entities.get(L);return F&&j.every((H)=>(H in F.components))&&g.every((H)=>!(H in F.components))}).map((L)=>this.entities.get(L))}removeEntity(j){let g=typeof j==="number"?this.entities.get(j):j;if(!g)return!1;for(let f of Object.keys(g.components))this.componentIndices.get(f)?.delete(g.id);return this.entities.delete(g.id)}getEntity(j){return this.entities.get(j)}}class M{handlers=new Map;subscribe(j,g){return this.addHandler(j,g,!1)}once(j,g){return this.addHandler(j,g,!0)}addHandler(j,g,f){if(!this.handlers.has(j))this.handlers.set(j,[]);let D={callback:g,once:f};return this.handlers.get(j).push(D),()=>{let L=this.handlers.get(j);if(L){let F=L.indexOf(D);if(F!==-1)L.splice(F,1)}}}publish(j,g){let f=this.handlers.get(j);if(!f)return;let D=[...f],L=[];for(let F of D)if(F.callback(g),F.once)L.push(F);if(L.length>0)for(let F of L){let H=f.indexOf(F);if(H!==-1)f.splice(H,1)}}clear(){this.handlers.clear()}clearEvent(j){this.handlers.delete(j)}}class P{resources=new Map;add(j,g){return this.resources.set(j,g),this}get(j){let g=this.resources.get(j);if(g===void 0)throw new Error(`Resource ${String(j)} not found`);return g}getOptional(j){return this.resources.get(j)}has(j){return this.resources.has(j)}remove(j){return this.resources.delete(j)}getKeys(){return Array.from(this.resources.keys())}}class V{_label;_ecspresso;_bundle;queries={};processFunction;attachFunction;detachFunction;eventHandlers;constructor(j,g=null,f=null){this._label=j;this._ecspresso=g;this._bundle=f}get label(){return this._label}get bundle(){return this._bundle}get ecspresso(){return this._ecspresso}addQuery(j,g){let f=this;return f.queries={...this.queries,[j]:g},f}setProcess(j){return this.processFunction=j,this}setOnAttach(j){return this.attachFunction=j,this}setOnDetach(j){return this.detachFunction=j,this}setEventHandlers(j){return this.eventHandlers=j,this}build(j){let g={label:this._label,entityQueries:this.queries};if(this.processFunction)g.process=this.processFunction;if(this.attachFunction)g.onAttach=this.attachFunction;if(this.detachFunction)g.onDetach=this.detachFunction;if(this.eventHandlers)g.eventHandlers=this.eventHandlers;if(this._ecspresso)X(g,this._ecspresso);if(j)X(g,j);return this}}function X(j,g){if(g._systems.push(j),j.onAttach?.(g),!j.eventHandlers)return;for(let f in j.eventHandlers){let D=j.eventHandlers[f]?.handler;D&&g.eventBus.subscribe(f,(L)=>{D(L,g)})}}function Y(j,g){return new V(j,g)}function Z(j,g){return new V(j,null,g)}var _="0.1.4";class Q{static VERSION=_;_entityManager;_eventBus;_resourceManager;_systems=[];_installedBundles=new Set;constructor(){this._entityManager=new J,this._eventBus=new M,this._resourceManager=new P}static create(){return new $}addSystem(j){return Y(j,this)}update(j){for(let g of this._systems){if(!g.process)continue;let f={};if(g.entityQueries)for(let D in g.entityQueries){let L=g.entityQueries[D];if(L)f[D]=this._entityManager.getEntitiesWithComponents(L.with,L.without||[])}g.process(f,j,this)}}_installBundle(j){if(this._installedBundles.has(j.id))return this;this._installedBundles.add(j.id),j.registerSystemsWithEcspresso(this);let g=j.getResources();for(let[f,D]of g.entries())this._resourceManager.add(f,D);return this}removeSystem(j){let g=this._systems.findIndex((D)=>D.label===j);if(g===-1)return!1;let f=this._systems[g];if(!f)return!1;if(f.onDetach)f.onDetach(this);return this._systems.splice(g,1),!0}hasResource(j){return this._resourceManager.has(j)}getResource(j){let g=this._resourceManager.getOptional(j);if(!g)throw new Error(`Resource "${j.toString()}" not found`);return g}addResource(j,g){return this._resourceManager.add(j,g),this}hasComponent(j,g){return this._entityManager.getComponent(j,g)!==null}getEntitiesWithComponents(j,g=[]){return this._entityManager.getEntitiesWithComponents(j,g)}get installedBundles(){return Array.from(this._installedBundles)}get entityManager(){return this._entityManager}get eventBus(){return this._eventBus}get resourceManager(){return this._resourceManager}}class ${ecspresso;constructor(){this.ecspresso=new Q}withBundle(j){return this.ecspresso._installBundle(j),this}build(){return this.ecspresso}}function W(){return`bundle_${Date.now().toString(36)}_${Math.random().toString(36).substring(2,9)}`}class U{_systems=[];_resources=new Map;_id;constructor(j){this._id=j||W()}get id(){return this._id}set id(j){this._id=j}addSystem(j){let g=Z(j,this);return this._systems.push(g),g}addResource(j,g){return this._resources.set(j,g),this}getSystems(){return this._systems.map((j)=>j.build())}registerSystemsWithEcspresso(j){for(let g of this._systems)g.build(j)}getResources(){return new Map(this._resources)}getResource(j){return this._resources.get(j)}getSystemBuilders(){return[...this._systems]}hasResource(j){return this._resources.has(j)}}function A(j,...g){if(g.length===0)return new U(j);let f=new U(j);for(let D of g){for(let L of D.getSystemBuilders())f.addSystem(L);for(let[L,F]of D.getResources().entries())f.addResource(L,F)}return f}var o=Q;export{A as mergeBundles,o as default,V as SystemBuilder,P as ResourceManager,M as EventBus,J as EntityManager,U as Bundle};
1
+ class G{nextId=1;entities=new Map;componentIndices=new Map;createEntity(){let f=this.nextId++,g={id:f,components:{}};return this.entities.set(f,g),g}addComponent(f,g,j){let x=typeof f==="number"?this.entities.get(f):f;if(!x)throw new Error(`Entity ${f} does not exist`);if(x.components[g]=j,!this.componentIndices.has(g))this.componentIndices.set(g,new Set);return this.componentIndices.get(g)?.add(x.id),this}addComponents(f,g){let j=typeof f==="number"?this.entities.get(f):f;if(!j)throw new Error(`Entity ${f} does not exist`);for(let x in g)this.addComponent(j,x,g[x]);return this}removeComponent(f,g){let j=this.entities.get(f);if(!j)throw new Error(`Entity ${f} does not exist`);delete j.components[g],this.componentIndices.get(g)?.delete(f)}getComponent(f,g){let j=this.entities.get(f);if(!j)throw new Error(`Entity ${f} does not exist`);return j.components[g]||null}getEntitiesWithComponents(f=[],g=[]){if(f.length===0){if(g.length===0)return Array.from(this.entities.values());return Array.from(this.entities.values()).filter((L)=>{return g.every((D)=>!(D in L.components))})}let j=f.reduce((L,D)=>{let F=this.componentIndices.get(D),_=F?F.size:0,$=this.componentIndices.get(L)?.size??1/0;return _<$?D:L},f[0]);return Array.from(this.componentIndices.get(j)||[]).filter((L)=>{let D=this.entities.get(L);return D&&f.every((F)=>(F in D.components))&&g.every((F)=>!(F in D.components))}).map((L)=>this.entities.get(L))}removeEntity(f){let g=typeof f==="number"?this.entities.get(f):f;if(!g)return!1;for(let j of Object.keys(g.components))this.componentIndices.get(j)?.delete(g.id);return this.entities.delete(g.id)}getEntity(f){return this.entities.get(f)}}class H{handlers=new Map;subscribe(f,g){return this.addHandler(f,g,!1)}once(f,g){return this.addHandler(f,g,!0)}addHandler(f,g,j){if(!this.handlers.has(f))this.handlers.set(f,[]);let x={callback:g,once:j};return this.handlers.get(f).push(x),()=>{let L=this.handlers.get(f);if(L){let D=L.indexOf(x);if(D!==-1)L.splice(D,1)}}}publish(f,g){let j=this.handlers.get(f);if(!j)return;let x=[...j],L=[];for(let D of x)if(D.callback(g),D.once)L.push(D);if(L.length>0)for(let D of L){let F=j.indexOf(D);if(F!==-1)j.splice(F,1)}}clear(){this.handlers.clear()}clearEvent(f){this.handlers.delete(f)}}class J{resources=new Map;resourceFactories=new Map;initializedResourceKeys=new Set;add(f,g){if(typeof g==="function"&&!g.toString().startsWith("class"))this.resourceFactories.set(f,g);else this.resources.set(f,g),this.initializedResourceKeys.add(f);return this}get(f){let g=this.resources.get(f);if(g!==void 0)return g;let j=this.resourceFactories.get(f);if(j===void 0)throw new Error(`Resource ${String(f)} not found`);let x=j();if(!(x instanceof Promise))this.resources.set(f,x),this.initializedResourceKeys.add(f);return x}getOptional(f){try{return this.get(f)}catch{return}}has(f){return this.resources.has(f)||this.resourceFactories.has(f)}remove(f){let g=this.resources.delete(f),j=this.resourceFactories.delete(f);if(this.initializedResourceKeys.has(f))this.initializedResourceKeys.delete(f);return g||j}getKeys(){let f=new Set([...this.resources.keys(),...this.resourceFactories.keys()]);return Array.from(f)}needsInitialization(f){return this.resourceFactories.has(f)&&!this.initializedResourceKeys.has(f)}getPendingInitializationKeys(){return Array.from(this.resourceFactories.keys()).filter((f)=>!this.initializedResourceKeys.has(f))}async initializeResource(f){if(!this.resourceFactories.has(f)||this.initializedResourceKeys.has(f))return;let j=await this.resourceFactories.get(f)();this.resources.set(f,j),this.initializedResourceKeys.add(f)}async initializeResources(...f){if(f.length===0){let g=this.getPendingInitializationKeys();await Promise.all(g.map((j)=>this.initializeResource(j)));return}await Promise.all(f.map((g)=>this.initializeResource(g)))}}class Q{_label;_ecspresso;_bundle;queries={};processFunction;detachFunction;initializeFunction;eventHandlers;_priority=0;constructor(f,g=null,j=null){this._label=f;this._ecspresso=g;this._bundle=j}get label(){return this._label}get bundle(){return this._bundle}get ecspresso(){return this._ecspresso}setPriority(f){return this._priority=f,this}addQuery(f,g){let j=this;return j.queries={...this.queries,[f]:g},j}setProcess(f){return this.processFunction=f,this}setOnDetach(f){return this.detachFunction=f,this}setOnInitialize(f){return this.initializeFunction=f,this}setEventHandlers(f){return this.eventHandlers=f,this}build(f){let g={label:this._label,entityQueries:this.queries,priority:this._priority};if(this.processFunction)g.process=this.processFunction;if(this.detachFunction)g.onDetach=this.detachFunction;if(this.initializeFunction)g.onInitialize=this.initializeFunction;if(this.eventHandlers)g.eventHandlers=this.eventHandlers;if(this._ecspresso)U(g,this._ecspresso);if(f)U(g,f);return this}}function U(f,g){if(g._systems.push(f),g._sortSystems(),!f.eventHandlers)return;for(let j in f.eventHandlers){let x=f.eventHandlers[j]?.handler;x&&g.eventBus.subscribe(j,(L)=>{x(L,g)})}}function V(f,g){return new Q(f,g)}function X(f,g){return new Q(f,null,g)}var Y="0.2.0";class M{static VERSION=Y;_entityManager;_eventBus;_resourceManager;_systems=[];_sortedSystems=[];_installedBundles=new Set;constructor(){this._entityManager=new G,this._eventBus=new H,this._resourceManager=new J,this._sortedSystems=[]}static create(){return new Z}addSystem(f){return V(f,this)}update(f){for(let g of this._sortedSystems){if(!g.process)continue;let j={};if(g.entityQueries)for(let x in g.entityQueries){let L=g.entityQueries[x];if(L)j[x]=this._entityManager.getEntitiesWithComponents(L.with,L.without||[])}g.process(j,f,this)}}async initialize(){await this.initializeResources();for(let f of this._systems)await f.onInitialize?.(this)}async initializeResources(...f){await this._resourceManager.initializeResources(...f)}_sortSystems(){this._sortedSystems=[...this._systems].sort((f,g)=>{let j=f.priority??0;return(g.priority??0)-j})}updateSystemPriority(f,g){let j=this._systems.find((x)=>x.label===f);if(!j)return!1;return j.priority=g,this._sortSystems(),!0}removeSystem(f){let g=this._systems.findIndex((x)=>x.label===f);if(g===-1)return!1;let j=this._systems[g];if(!j)return!1;if(j.onDetach)j.onDetach(this);return this._systems.splice(g,1),this._sortSystems(),!0}hasResource(f){return this._resourceManager.has(f)}getResource(f){let g=this._resourceManager.get(f);if(!g)throw new Error(`Resource "${f.toString()}" not found`);return g}addResource(f,g){return this._resourceManager.add(f,g),this}hasComponent(f,g){return this._entityManager.getComponent(f,g)!==null}getEntitiesWithComponents(f,g=[]){return this._entityManager.getEntitiesWithComponents(f,g)}get installedBundles(){return Array.from(this._installedBundles)}get entityManager(){return this._entityManager}get eventBus(){return this._eventBus}get resourceManager(){return this._resourceManager}_installBundle(f){if(this._installedBundles.has(f.id))return this;this._installedBundles.add(f.id),f.registerSystemsWithEcspresso(this);let g=f.getResources();for(let[j,x]of g.entries())this._resourceManager.add(j,x);return this}}class Z{ecspresso;constructor(){this.ecspresso=new M}withBundle(f){return this.ecspresso._installBundle(f),this}build(){return this.ecspresso}}function K(){return`bundle_${Date.now().toString(36)}_${Math.random().toString(36).substring(2,9)}`}class P{_systems=[];_resources=new Map;_id;constructor(f){this._id=f||K()}get id(){return this._id}set id(f){this._id=f}addSystem(f){let g=X(f,this);return this._systems.push(g),g}addResource(f,g){return this._resources.set(f,g),this}getSystems(){return this._systems.map((f)=>f.build())}registerSystemsWithEcspresso(f){for(let g of this._systems)g.build(f)}getResources(){return new Map(this._resources)}getResource(f){return this._resources.get(f)}getSystemBuilders(){return[...this._systems]}hasResource(f){return this._resources.has(f)}}function W(f,...g){if(g.length===0)return new P(f);let j=new P(f);for(let x of g){for(let L of x.getSystemBuilders())j.addSystem(L.label);for(let[L,D]of x.getResources().entries())j.addResource(L,D)}return j}var d=M;export{W as mergeBundles,d as default,Q as SystemBuilder,J as ResourceManager,H as EventBus,G as EntityManager,P as Bundle};
2
2
 
3
- //# debugId=5B3F11C4615EDE1564756E2164756E21
3
+ //# debugId=F24A71CB687B735F64756E2164756E21
4
4
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -4,13 +4,13 @@
4
4
  "sourcesContent": [
5
5
  "import type { Entity, FilteredEntity } from \"./types\";\n\nexport default\nclass EntityManager<ComponentTypes> {\n\tprivate nextId: number = 1;\n\tprivate entities: Map<number, Entity<ComponentTypes>> = new Map();\n\tprivate componentIndices: Map<keyof ComponentTypes, Set<number>> = new Map();\n\n\tcreateEntity(): Entity<ComponentTypes> {\n\t\tconst id = this.nextId++;\n\t\tconst entity: Entity<ComponentTypes> = { id, components: {} };\n\t\tthis.entities.set(id, entity);\n\t\treturn entity;\n\t}\n\n\t// TODO: Component object pooling if(/when) garbage collection is an issue...?\n\taddComponent<ComponentName extends keyof ComponentTypes>(\n\t\tentityOrId: number | Entity<ComponentTypes>,\n\t\tcomponentName: ComponentName,\n\t\tdata: ComponentTypes[ComponentName]\n\t) {\n\t\tconst entity = typeof entityOrId === 'number' ?\n\t\t\tthis.entities.get(entityOrId) :\n\t\t\tentityOrId;\n\n\t\tif (!entity) throw new Error(`Entity ${entityOrId} does not exist`);\n\n\t\tentity.components[componentName] = data;\n\n\t\t// Update component index\n\t\tif (!this.componentIndices.has(componentName)) {\n\t\t\tthis.componentIndices.set(componentName, new Set());\n\t\t}\n\t\tthis.componentIndices.get(componentName)?.add(entity.id);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Add multiple components to an entity at once\n\t * @param entityOrId Entity or entity ID to add components to\n\t * @param components Object with component names as keys and component data as values\n\t */\n\taddComponents<\n\t\tT extends { [K in keyof ComponentTypes]?: ComponentTypes[K] }\n\t>(\n\t\tentityOrId: number | Entity<ComponentTypes>,\n\t\tcomponents: T & Record<Exclude<keyof T, keyof ComponentTypes>, never>\n\t) {\n\t\tconst entity = typeof entityOrId === 'number' ?\n\t\t\tthis.entities.get(entityOrId) :\n\t\t\tentityOrId;\n\n\t\tif (!entity) throw new Error(`Entity ${entityOrId} does not exist`);\n\n\t\tfor (const componentName in components) {\n\t\t\tthis.addComponent(\n\t\t\t\tentity,\n\t\t\t\tcomponentName as keyof ComponentTypes,\n\t\t\t\tcomponents[componentName as keyof T] as ComponentTypes[keyof ComponentTypes]\n\t\t\t);\n\t\t}\n\n\t\treturn this;\n\t}\n\n\tremoveComponent<ComponentName extends keyof ComponentTypes>(entityId: number, componentName: ComponentName) {\n\t\tconst entity = this.entities.get(entityId);\n\t\tif (!entity) throw new Error(`Entity ${entityId} does not exist`);\n\n\t\tdelete entity.components[componentName];\n\n\t\t// Update component index\n\t\tthis.componentIndices.get(componentName)?.delete(entityId);\n\t}\n\n\tgetComponent<ComponentName extends keyof ComponentTypes>(entityId: number, componentName: ComponentName): ComponentTypes[ComponentName] | null {\n\t\tconst entity = this.entities.get(entityId);\n\n\t\tif (!entity) throw new Error(`Entity ${entityId} does not exist`);\n\n\t\treturn entity.components[componentName] || null;\n\t}\n\n\tgetEntitiesWithComponents<\n\t\tWithComponents extends keyof ComponentTypes = never,\n\t\tWithoutComponents extends keyof ComponentTypes = never\n\t>(\n\t\trequired: ReadonlyArray<WithComponents> = [],\n\t\texcluded: ReadonlyArray<WithoutComponents> = [],\n\t): Array<FilteredEntity<ComponentTypes, WithComponents extends never ? never : WithComponents, WithoutComponents extends never ? never : WithoutComponents>> {\n\t\t// Use the smallest component set as base for better performance\n\t\tif (required.length === 0) {\n\t\t\tif (excluded.length === 0) {\n\t\t\t\treturn Array.from(this.entities.values()) as any;\n\t\t\t}\n\n\t\t\treturn Array\n\t\t\t\t.from(this.entities.values())\n\t\t\t\t.filter((entity) => {\n\t\t\t\t\treturn excluded.every(comp => !(comp in entity.components));\n\t\t\t\t}) as any;\n\t\t}\n\n\t\t// Find the component with the smallest entity set to start with\n\t\tconst smallestComponent = required.reduce((smallest, comp) => {\n\t\t\tconst set = this.componentIndices.get(comp);\n\t\t\tconst currentSize = set ? set.size : 0;\n\t\t\tconst smallestSize = this.componentIndices.get(smallest!)?.size ?? Infinity;\n\n\t\t\treturn currentSize < smallestSize ? comp : smallest;\n\t\t}, required[0])!;\n\n\t\t// Start with the entities from the smallest component set\n\t\tconst candidates = Array.from(this.componentIndices.get(smallestComponent) || []);\n\n\t\t// Return full entity objects, not just IDs\n\t\treturn candidates\n\t\t\t.filter(id => {\n\t\t\t\tconst entity = this.entities.get(id);\n\t\t\t\treturn (\n\t\t\t\t\tentity &&\n\t\t\t\t\trequired.every(comp => comp in entity.components) &&\n\t\t\t\t\texcluded.every(comp => !(comp in entity.components))\n\t\t\t\t);\n\t\t\t})\n\t\t\t.map(id => this.entities.get(id)!) as Array<FilteredEntity<ComponentTypes, WithComponents extends never ? never : WithComponents, WithoutComponents extends never ? never : WithoutComponents>>;\n\t}\n\n\tremoveEntity(entityOrId: number | Entity<ComponentTypes>): boolean {\n\t\tconst entity = typeof entityOrId === 'number' ?\n\t\t\tthis.entities.get(entityOrId) :\n\t\t\tentityOrId;\n\n\t\tif (!entity) return false;\n\n\t\t// Remove entity from all component indices\n\t\tfor (const componentName of Object.keys(entity.components) as Array<keyof ComponentTypes>) {\n\t\t\tthis.componentIndices.get(componentName)?.delete(entity.id);\n\t\t}\n\n\t\t// Remove the entity itself\n\t\treturn this.entities.delete(entity.id);\n\t}\n\n\tgetEntity(entityId: number): Entity<ComponentTypes> | undefined {\n\t\treturn this.entities.get(entityId);\n\t}\n}\n",
6
6
  "import type { EventHandler } from \"./types\";\n\nexport default\nclass EventBus<EventTypes> {\n\tprivate handlers: Map<string, Array<EventHandler<any>>> = new Map();\n\n\t/**\n\t * Subscribe to an event\n\t */\n\tsubscribe<E extends keyof EventTypes>(\n\t\teventType: E,\n\t\tcallback: (data: EventTypes[E]) => void\n\t): () => void {\n\t\treturn this.addHandler(eventType, callback, false);\n\t}\n\n\t/**\n\t * Subscribe to an event once\n\t */\n\tonce<E extends keyof EventTypes>(\n\t\teventType: E,\n\t\tcallback: (data: EventTypes[E]) => void\n\t): () => void {\n\t\treturn this.addHandler(eventType, callback, true);\n\t}\n\n\t/**\n\t * Internal method to add an event handler\n\t */\n\tprivate addHandler<E extends keyof EventTypes>(\n\t\teventType: E,\n\t\tcallback: (data: EventTypes[E]) => void,\n\t\tonce: boolean\n\t): () => void {\n\t\tif (!this.handlers.has(eventType as string)) {\n\t\t\tthis.handlers.set(eventType as string, []);\n\t\t}\n\n\t\tconst handler: EventHandler<any> = {\n\t\t\tcallback,\n\t\t\tonce\n\t\t};\n\n\t\tthis.handlers.get(eventType as string)!.push(handler);\n\n\t\t// Return unsubscribe function\n\t\treturn () => {\n\t\t\tconst handlers = this.handlers.get(eventType as string);\n\t\t\tif (handlers) {\n\t\t\t\tconst index = handlers.indexOf(handler);\n\t\t\t\tif (index !== -1) {\n\t\t\t\t\thandlers.splice(index, 1);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n\n\tpublish<E extends keyof EventTypes>(\n\t\teventType: E,\n\t\tdata?: EventTypes[E]\n\t): void {\n\t\tconst handlers = this.handlers.get(eventType as string);\n\t\tif (!handlers) return;\n\n\t\t// Create a copy of handlers to avoid issues with handlers that modify the array\n\t\tconst handlersToCall = [...handlers];\n\n\t\t// Call all handlers and collect handlers to remove\n\t\tconst handlersToRemove: EventHandler<any>[] = [];\n\n\t\tfor (const handler of handlersToCall) {\n\t\t\thandler.callback(data as EventTypes[E]);\n\t\t\tif (handler.once) {\n\t\t\t\thandlersToRemove.push(handler);\n\t\t\t}\n\t\t}\n\n\t\tif (handlersToRemove.length > 0) {\n\t\t\tfor (const handler of handlersToRemove) {\n\t\t\t\tconst index = handlers.indexOf(handler);\n\t\t\t\tif (index !== -1) {\n\t\t\t\t\thandlers.splice(index, 1);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tclear(): void {\n\t\tthis.handlers.clear();\n\t}\n\n\tclearEvent<E extends keyof EventTypes>(eventType: E): void {\n\t\tthis.handlers.delete(eventType as string);\n\t}\n}\n",
7
- "export default\nclass ResourceManager<ResourceTypes extends Record<string, any> = Record<string, any>> {\n\tprivate resources: Map<string, any> = new Map();\n\n\t/**\n\t * Add a resource to the manager\n\t * @param label The resource key\n\t * @param resource The resource value\n\t * @returns The resource manager instance for chaining\n\t */\n\tadd<K extends keyof ResourceTypes | string>(label: K, resource: K extends keyof ResourceTypes ? ResourceTypes[K] : any) {\n\t\tthis.resources.set(label as string, resource);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Get a resource from the manager\n\t * @param label The resource key\n\t * @returns The resource value\n\t * @throws Error if resource not found\n\t */\n\tget<K extends keyof ResourceTypes | string>(label: K): K extends keyof ResourceTypes ? ResourceTypes[K] : any {\n\t\tconst resource = this.resources.get(label as string);\n\n\t\tif (resource === undefined) {\n\t\t\tthrow new Error(`Resource ${String(label)} not found`);\n\t\t}\n\n\t\treturn resource as any;\n\t}\n\n\t/**\n\t * Get a resource from the manager, returning undefined if not found\n\t * @param label The resource key\n\t * @returns The resource value or undefined if not found\n\t */\n\tgetOptional<K extends keyof ResourceTypes | string>(label: K): K extends keyof ResourceTypes ? ResourceTypes[K] : any | undefined {\n\t\tconst resource = this.resources.get(label as string);\n\t\treturn resource as any | undefined;\n\t}\n\n\t/**\n\t * Check if a resource exists\n\t * @param label The resource key\n\t * @returns True if the resource exists\n\t */\n\thas<K extends keyof ResourceTypes | string>(label: K): boolean {\n\t\treturn this.resources.has(label as string);\n\t}\n\n\t/**\n\t * Remove a resource\n\t * @param label The resource key\n\t * @returns True if the resource was removed\n\t */\n\tremove<K extends keyof ResourceTypes | string>(label: K): boolean {\n\t\treturn this.resources.delete(label as string);\n\t}\n\n\t/**\n\t * Get all resource keys\n\t * @returns Array of resource keys\n\t */\n\tgetKeys(): Array<string> {\n\t\treturn Array.from(this.resources.keys());\n\t}\n}\n",
8
- "import Bundle from \"./bundle\";\nimport ECSpresso from \"./ecspresso\";\nimport type { FilteredEntity, System } from \"./types\";\n\n/**\n * Builder class for creating type-safe ECS Systems with proper query inference\n */\nexport class SystemBuilder<\n\tComponentTypes extends Record<string, any> = Record<string, any>,\n\tEventTypes extends Record<string, any> = Record<string, any>,\n\tResourceTypes extends Record<string, any> = Record<string, any>,\n\tQueries extends Record<string, QueryDefinition<ComponentTypes>> = {},\n> {\n\tprivate queries: Queries = {} as Queries;\n\tprivate processFunction?: ProcessFunction<ComponentTypes, EventTypes, ResourceTypes, Queries>;\n\tprivate attachFunction?: LifecycleFunction<ComponentTypes, EventTypes, ResourceTypes>;\n\tprivate detachFunction?: LifecycleFunction<ComponentTypes, EventTypes, ResourceTypes>;\n\tprivate eventHandlers?: {\n\t\t[EventName in keyof EventTypes]?: {\n\t\t\thandler(\n\t\t\t\tdata: EventTypes[EventName],\n\t\t\t\tecs: ECSpresso<\n\t\t\t\t\tComponentTypes & Record<string, any>,\n\t\t\t\t\tEventTypes,\n\t\t\t\t\tResourceTypes\n\t\t\t\t>,\n\t\t\t): void;\n\t\t};\n\t};\n\n\tconstructor(\n\t\tprivate _label: string,\n\t\tprivate _ecspresso: ECSpresso<ComponentTypes, EventTypes, ResourceTypes> | null = null,\n\t\tprivate _bundle: Bundle<ComponentTypes, EventTypes, ResourceTypes> | null = null,\n\t) {}\n\n\tget label() {\n\t\treturn this._label;\n\t}\n\n\t/**\n\t * Returns the associated bundle if one was provided in the constructor\n\t */\n\tget bundle() {\n\t\treturn this._bundle;\n\t}\n\n\t/**\n\t * Returns the associated ECSpresso instance if one was provided in the constructor\n\t */\n\tget ecspresso() {\n\t\treturn this._ecspresso;\n\t}\n\n\t/**\n\t * Add a query definition to the system\n\t */\n\taddQuery<\n\t\tQueryName extends string,\n\t\tWithComponents extends keyof ComponentTypes,\n\t\tWithoutComponents extends keyof ComponentTypes = never,\n\t\tNewQueries extends Queries & Record<QueryName, QueryDefinition<ComponentTypes, WithComponents, WithoutComponents>> =\n\t\t\tQueries & Record<QueryName, QueryDefinition<ComponentTypes, WithComponents, WithoutComponents>>\n\t>(\n\t\tname: QueryName,\n\t\tdefinition: {\n\t\t\twith: ReadonlyArray<WithComponents>;\n\t\t\twithout?: ReadonlyArray<WithoutComponents>;\n\t\t}\n\t): this extends SystemBuilderWithEcspresso<ComponentTypes, EventTypes, ResourceTypes, Queries>\n\t\t? SystemBuilderWithEcspresso<ComponentTypes, EventTypes, ResourceTypes, NewQueries>\n\t\t: this extends SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, Queries>\n\t\t\t? SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, NewQueries>\n\t\t\t: SystemBuilder<ComponentTypes, EventTypes, ResourceTypes, NewQueries> {\n\t\t// Cast is needed because TypeScript can't preserve the type information\n\t\t// when modifying an object property\n\t\tconst newBuilder = this as any;\n\t\tnewBuilder.queries = {\n\t\t\t...this.queries,\n\t\t\t[name]: definition,\n\t\t};\n\t\treturn newBuilder;\n\t}\n\n\t/**\n\t * Set the system's process function that runs each update\n\t * @param process Function to process entities matching the system's queries each update\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\tsetProcess(\n\t\tprocess: ProcessFunction<ComponentTypes, EventTypes, ResourceTypes, Queries>\n\t): this {\n\t\tthis.processFunction = process;\n\t\treturn this;\n\t}\n\n\t/**\n\t * Set the onAttach lifecycle hook\n\t * Called when the system is attached to the ECS\n\t * @param onAttach Function to run when this system is attached to the ECS\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\tsetOnAttach(\n\t\tonAttach: LifecycleFunction<ComponentTypes, EventTypes, ResourceTypes>\n\t): this {\n\t\tthis.attachFunction = onAttach;\n\t\treturn this;\n\t}\n\n\t/**\n\t * Set the onDetach lifecycle hook\n\t * Called when the system is removed from the ECS\n\t * @param onDetach Function to run when this system is detached from the ECS\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\tsetOnDetach(\n\t\tonDetach: LifecycleFunction<ComponentTypes, EventTypes, ResourceTypes>\n\t): this {\n\t\tthis.detachFunction = onDetach;\n\t\treturn this;\n\t}\n\n\t/**\n\t * Set event handlers for the system\n\t * These handlers will be automatically subscribed when the system is attached\n\t * @param handlers Object mapping event names to handler functions\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\tsetEventHandlers(\n\t\thandlers: {\n\t\t\t[EventName in keyof EventTypes]?: {\n\t\t\t\thandler(\n\t\t\t\t\tdata: EventTypes[EventName],\n\t\t\t\t\tecs: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>\n\t\t\t\t): void;\n\t\t\t};\n\t\t}\n\t): this {\n\t\tthis.eventHandlers = handlers;\n\t\treturn this;\n\t}\n\n\t/**\n\t * Build the final system object\n\t */\n\tbuild(ecspresso?: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>) {\n\t\tconst system: System<ComponentTypes, any, any, EventTypes, ResourceTypes> = {\n\t\t\tlabel: this._label,\n\t\t\tentityQueries: this.queries,\n\t\t};\n\n\t\tif (this.processFunction) {\n\t\t\tsystem.process = this.processFunction;\n\t\t}\n\n\t\tif (this.attachFunction) {\n\t\t\tsystem.onAttach = this.attachFunction;\n\t\t}\n\n\t\tif (this.detachFunction) {\n\t\t\tsystem.onDetach = this.detachFunction;\n\t\t}\n\n\t\tif (this.eventHandlers) {\n\t\t\tsystem.eventHandlers = this.eventHandlers;\n\t\t}\n\n\t\tif (this._ecspresso) {\n\t\t\tregisterSystemWithEcspresso(system, this._ecspresso);\n\t\t}\n\n\t\tif(ecspresso) {\n\t\t\tregisterSystemWithEcspresso(system, ecspresso);\n\t\t}\n\n\t\treturn this;\n\t}\n}\n\n/**\n * Helper function to register a system with an ECSpresso instance\n * This handles attaching the system and setting up event handlers\n * @internal Used by SystemBuilder and Bundle\n */\nexport function registerSystemWithEcspresso<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>\n>(\n\tsystem: System<ComponentTypes, any, any, EventTypes, ResourceTypes>,\n\tecspresso: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>\n) {\n\t// Add system to ECSpresso's system list\n\tecspresso[\"_systems\"].push(system);\n\n\tsystem.onAttach?.(ecspresso);\n\n\tif(!system.eventHandlers) return;\n\n\tfor (const eventName in system.eventHandlers) {\n\t\tconst handler = system.eventHandlers[eventName]?.handler;\n\n\t\thandler && ecspresso.eventBus.subscribe(eventName, (data) => {\n\t\t\thandler(data, ecspresso);\n\t\t});\n\t}\n}\n\n// Helper type definitions\ntype QueryDefinition<\n\tComponentTypes,\n\tWithComponents extends keyof ComponentTypes = any,\n\tWithoutComponents extends keyof ComponentTypes = any,\n> = {\n\twith: ReadonlyArray<WithComponents>;\n\twithout?: ReadonlyArray<WithoutComponents>;\n};\n\ntype QueryResults<\n\tComponentTypes,\n\tQueries extends Record<string, QueryDefinition<ComponentTypes>>,\n> = {\n\t[QueryName in keyof Queries]: QueryName extends string\n\t\t? FilteredEntity<\n\t\t\tComponentTypes,\n\t\t\tQueries[QueryName] extends QueryDefinition<ComponentTypes, infer W, any> ? W : never,\n\t\t\tQueries[QueryName] extends QueryDefinition<ComponentTypes, any, infer WO> ? WO : never\n\t\t>[]\n\t\t: never;\n};\n\n/**\n * Function signature for system process methods\n * @param queries Results of entity queries defined by the system\n * @param deltaTime Time elapsed since last update in seconds\n * @param ecs The ECSpresso instance providing access to all ECS functionality\n */\ntype ProcessFunction<\n\tComponentTypes,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>,\n\tQueries extends Record<string, QueryDefinition<ComponentTypes>>,\n> = (\n\tqueries: QueryResults<ComponentTypes, Queries>,\n\tdeltaTime: number,\n\tecs: ECSpresso<\n\t\tComponentTypes & Record<string, any>,\n\t\tEventTypes,\n\t\tResourceTypes\n\t>\n) => void;\n\n/**\n * Function signature for system lifecycle hooks (onAttach and onDetach)\n * @param ecs The ECSpresso instance providing access to all ECS functionality\n */\ntype LifecycleFunction<\n\tComponentTypes,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>,\n> = (\n\tecs: ECSpresso<\n\t\tComponentTypes & Record<string, any>,\n\t\tEventTypes,\n\t\tResourceTypes\n\t>,\n) => void;\n\n/**\n * Create a SystemBuilder attached to an ECSpresso instance\n * Helper function used by ECSpresso.addSystem\n */\nexport function createEcspressoSystemBuilder<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>\n>(\n\tlabel: string,\n\tecspresso: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>\n): SystemBuilderWithEcspresso<ComponentTypes, EventTypes, ResourceTypes> {\n\treturn new SystemBuilder<ComponentTypes, EventTypes, ResourceTypes>(\n\t\tlabel,\n\t\tecspresso\n\t) as SystemBuilderWithEcspresso<ComponentTypes, EventTypes, ResourceTypes>;\n}\n\n/**\n * Create a SystemBuilder attached to a Bundle\n * Helper function used by Bundle.addSystem\n */\nexport function createBundleSystemBuilder<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>\n>(\n\tlabel: string,\n\tbundle: Bundle<ComponentTypes, EventTypes, ResourceTypes>\n): SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes> {\n\treturn new SystemBuilder<ComponentTypes, EventTypes, ResourceTypes>(\n\t\tlabel,\n\t\tnull,\n\t\tbundle\n\t) as SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes>;\n}\n\n// Type interfaces for specialized SystemBuilders\n\n/**\n * SystemBuilder with a guaranteed non-null reference to an ECSpresso instance\n */\nexport interface SystemBuilderWithEcspresso<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>,\n\tQueries extends Record<string, QueryDefinition<ComponentTypes>> = {}\n> extends SystemBuilder<ComponentTypes, EventTypes, ResourceTypes, Queries> {\n\treadonly ecspresso: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>;\n}\n\n/**\n * SystemBuilder with a guaranteed non-null reference to a Bundle\n */\nexport interface SystemBuilderWithBundle<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>,\n\tQueries extends Record<string, QueryDefinition<ComponentTypes>> = {}\n> extends SystemBuilder<ComponentTypes, EventTypes, ResourceTypes, Queries> {\n\treadonly bundle: Bundle<ComponentTypes, EventTypes, ResourceTypes>;\n}\n",
9
- "import EntityManager from \"./entity-manager\";\nimport EventBus from \"./event-bus\";\nimport ResourceManager from \"./resource-manager\";\nimport type { System, FilteredEntity } from \"./types\";\nimport type Bundle from \"./bundle\";\nimport { createEcspressoSystemBuilder } from \"./system-builder\";\nimport { version } from \"../package.json\";\n\n/**\n\t* Type helper to detect conflicting types between two record types.\n\t* Returns a union of keys that exist in both T and U but have incompatible types.\n*/\ntype GetConflictingKeys<T, U> = {\n\t[K in keyof T & keyof U]: T[K] extends U[K]\n\t\t? U[K] extends T[K]\n\t\t\t? never\n\t\t\t: K\n\t\t: K\n}[keyof T & keyof U];\n\n/**\n\t* Simplified type helper to check bundle type compatibility.\n\t* Returns true if bundles can be merged without type conflicts.\n*/\ntype BundlesAreCompatible<\n\tC1 extends Record<string, any>,\n\tC2 extends Record<string, any>,\n\tE1 extends Record<string, any>,\n\tE2 extends Record<string, any>,\n\tR1 extends Record<string, any>,\n\tR2 extends Record<string, any>\n> =\n\t// If all base types are empty, any bundle is compatible\n\t[keyof C1] extends [never]\n\t\t? [keyof E1] extends [never]\n\t\t\t? [keyof R1] extends [never]\n\t\t\t\t? true\n\t\t\t\t: GetConflictingKeys<R1, R2> extends never ? true : false\n\t\t\t: GetConflictingKeys<E1, E2> extends never\n\t\t\t\t? GetConflictingKeys<R1, R2> extends never ? true : false\n\t\t\t\t: false\n\t\t: GetConflictingKeys<C1, C2> extends never\n\t\t\t? GetConflictingKeys<E1, E2> extends never\n\t\t\t\t? GetConflictingKeys<R1, R2> extends never\n\t\t\t\t\t? true\n\t\t\t\t\t: false\n\t\t\t\t: false\n\t\t\t: false;\n\n/**\n\t* Interface declaration for ECSpresso constructor to ensure type augmentation works properly.\n\t* This merges with the class declaration below.\n*/\nexport default interface ECSpresso<\n\tComponentTypes extends Record<string, any> = {},\n\tEventTypes extends Record<string, any> = {},\n\tResourceTypes extends Record<string, any> = {},\n> {\n\t/**\n\t\t* Default constructor\n\t*/\n\tnew(): ECSpresso<ComponentTypes, EventTypes, ResourceTypes>;\n}\n\n/**\n\t* Static methods on the ECSpresso class\n*/\nexport default interface ECSpresso {\n\t\t/**\n\t\t\t* Create a new ECSpresso builder with type-safe bundle installation.\n\t\t\t* This is the preferred way to create an ECSpresso instance with bundles.\n\t\t *\n\t\t\t* Example:\n\t\t\t* ```typescript\n\t\t\t* const ecs = ECSpresso.create<MyComponents, MyEvents, MyResources>()\n\t\t *\t .withBundle(bundle1)\n\t\t *\t .withBundle(bundle2)\n\t\t *\t .build();\n\t\t\t* ```\n\t\t*/\n\t\tcreate<\n\t\t\t\tBaseC extends Record<string, any> = {},\n\t\t\t\tBaseE extends Record<string, any> = {},\n\t\t\t\tBaseR extends Record<string, any> = {},\n\t\t>(): ECSpressoBuilder<BaseC, BaseE, BaseR>;\n}\n\n/**\n\t* ECSpresso is the central ECS framework class that connects all features.\n\t* It handles creation and management of entities, components, and systems, and provides lifecycle hooks.\n*/\nexport default class ECSpresso<\n\tComponentTypes extends Record<string, any> = {},\n\tEventTypes extends Record<string, any> = {},\n\tResourceTypes extends Record<string, any> = {},\n> {\n\t/** Library version*/\n\tpublic static readonly VERSION = version;\n\n\t/** Access/modify stored components and entities*/\n\tprivate _entityManager: EntityManager<ComponentTypes>;\n\t/** Publish/subscribe to events*/\n\tprivate _eventBus: EventBus<EventTypes>;\n\t/** Access/modify registered resources*/\n\tprivate _resourceManager: ResourceManager<ResourceTypes>;\n\n\t/** Registered systems that will be updated in order*/\n\tprivate _systems: Array<System<ComponentTypes, any, any, EventTypes, ResourceTypes>> = [];\n\t/** Track installed bundles to prevent duplicates*/\n\tprivate _installedBundles: Set<string> = new Set();\n\n\t/**\n\t\t* Creates a new ECSpresso instance.\n\t*/\n\tconstructor() {\n\t\tthis._entityManager = new EntityManager<ComponentTypes>();\n\t\tthis._eventBus = new EventBus<EventTypes>();\n\t\tthis._resourceManager = new ResourceManager<ResourceTypes>();\n\t}\n\n\t/**\n\t\t* Creates a new ECSpresso builder for type-safe bundle installation.\n\t\t* This is the preferred way to create an ECSpresso instance with bundles.\n\t *\n\t\t* @returns A builder instance for fluent method chaining\n\t *\n\t\t* @example\n\t\t* ```typescript\n\t\t* const ecs = ECSpresso.create<BaseComponents, BaseEvents, BaseResources>()\n\t *\t .withBundle(bundle1)\n\t *\t .withBundle(bundle2)\n\t *\t .build();\n\t\t* ```\n\t*/\n\tstatic create<\n\t\tC extends Record<string, any> = {},\n\t\tE extends Record<string, any> = {},\n\t\tR extends Record<string, any> = {},\n\t>(): ECSpressoBuilder<C, E, R> {\n\t\treturn new ECSpressoBuilder<C, E, R>();\n\t}\n\n\t/**\n\t\t* Adds a system directly to this ECSpresso instance\n\t\t* @param label Unique name to identify the system\n\t\t* @returns A SystemBuilder instance for method chaining\n\t*/\n\taddSystem(label: string) {\n\t\treturn createEcspressoSystemBuilder<\n\t\t\tComponentTypes,\n\t\t\tEventTypes,\n\t\t\tResourceTypes\n\t\t>(label, this);\n\t}\n\n\t/**\n\t\t* Update all systems, passing deltaTime and query results to each system's process function\n\t\t* @param deltaTime Time elapsed since the last update (in seconds)\n\t*/\n\tupdate(deltaTime: number) {\n\t\tfor (const system of this._systems) {\n\t\t\tif (!system.process) continue;\n\n\t\t\t// Prepare query results for each defined query in the system\n\t\t\tconst queryResults: Record<string, any> = {};\n\n\t\t\tif (system.entityQueries) {\n\t\t\t\tfor (const queryName in system.entityQueries) {\n\t\t\t\t\tconst query = system.entityQueries[queryName];\n\t\t\t\t\tif (query) {\n\t\t\t\t\t\tqueryResults[queryName] = this._entityManager.getEntitiesWithComponents(\n\t\t\t\t\t\t\tquery.with,\n\t\t\t\t\t\t\tquery.without || []\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Call the system's process function\n\t\t\tsystem.process(queryResults, deltaTime, this);\n\t\t}\n\t}\n\n\t/**\n\t\t* Internal method to install a bundle into this ECSpresso instance.\n\t\t* Called by the ECSpressoBuilder during the build process.\n\t\t* The type safety is guaranteed by the builder's type system.\n\t*/\n\t_installBundle<\n\t\tC extends Record<string, any>,\n\t\tE extends Record<string, any>,\n\t\tR extends Record<string, any>\n\t>(bundle: Bundle<C, E, R>): this {\n\t\t// Prevent duplicate installation of the same bundle\n\t\tif (this._installedBundles.has(bundle.id)) {\n\t\t\treturn this;\n\t\t}\n\n\t\t// Mark this bundle as installed\n\t\tthis._installedBundles.add(bundle.id);\n\n\t\t// Register systems from the bundle\n\t\t// Type casting is necessary because the generic parameters don't match\n\t\tbundle.registerSystemsWithEcspresso(this as any);\n\n\t\t// Register resources from the bundle\n\t\tconst resources = bundle.getResources();\n\t\tfor (const [key, value] of resources.entries()) {\n\t\t\t// Type compatibility is guaranteed by the builder's type system\n\t\t\tthis._resourceManager.add(key as unknown as keyof ResourceTypes, value as any);\n\t\t}\n\n\t\treturn this;\n\t}\n\n\t/**\n\t\t* Remove a system by its label\n\t\t* Calls the system's onDetach method with this ECSpresso instance if defined\n\t\t* @param label The unique label of the system to remove\n\t\t* @returns true if the system was found and removed, false otherwise\n\t*/\n\tremoveSystem(label: string): boolean {\n\t\tconst index = this._systems.findIndex(system => system.label === label);\n\t\tif (index === -1) return false;\n\n\t\tconst system = this._systems[index];\n\t\t// This should never happen since we just found the system by index\n\t\tif (!system) return false;\n\n\t\t// Call the onDetach lifecycle hook if defined\n\t\tif (system.onDetach) {\n\t\t\tsystem.onDetach(this);\n\t\t}\n\n\t\t// Remove system\n\t\tthis._systems.splice(index, 1);\n\t\treturn true;\n\t}\n\n\t/**\n\t\t* Check if a resource exists\n\t*/\n\thasResource<K extends keyof ResourceTypes>(key: K): boolean {\n\t\treturn this._resourceManager.has(key);\n\t}\n\n\t/**\n\t\t* Get a resource if it exists, or undefined if not\n\t*/\n\tgetResource<K extends keyof ResourceTypes>(key: K): ResourceTypes[K] {\n\t\tconst resource = this._resourceManager.getOptional(key);\n\n\t\tif (!resource) throw new Error(`Resource \"${key.toString()}\" not found`);\n\n\t\treturn resource;\n\t}\n\n\t/**\n\t\t* Add a resource to the ECS instance\n\t*/\n\taddResource<K extends keyof ResourceTypes>(key: K, resource: ResourceTypes[K]): this {\n\t\tthis._resourceManager.add(key, resource);\n\t\treturn this;\n\t}\n\n\t/**\n\t\t* Check if an entity has a component\n\t*/\n\thasComponent<K extends keyof ComponentTypes>(\n\t\tentityId: number,\n\t\tcomponentName: K\n\t): boolean {\n\t\tconst component = this._entityManager.getComponent(entityId, componentName);\n\t\treturn component !== null;\n\t}\n\n\t/**\n\t\t* Get all entities with specific components\n\t*/\n\tgetEntitiesWithComponents<\n\t\tWithComponents extends keyof ComponentTypes,\n\t\tWithoutComponents extends keyof ComponentTypes = never\n\t>(\n\t\twithComponents: ReadonlyArray<WithComponents>,\n\t\twithoutComponents: ReadonlyArray<WithoutComponents> = []\n\t): Array<FilteredEntity<ComponentTypes, WithComponents, WithoutComponents>> {\n\t\treturn this._entityManager.getEntitiesWithComponents(\n\t\t\twithComponents,\n\t\t\twithoutComponents\n\t\t);\n\t}\n\n\t/**\n\t\t* Get all installed bundle IDs\n\t*/\n\tget installedBundles(): string[] {\n\t\treturn Array.from(this._installedBundles);\n\t}\n\n\t// Getters for the internal managers\n\tget entityManager() {\n\t\treturn this._entityManager;\n\t}\n\n\tget eventBus() {\n\t\treturn this._eventBus;\n\t}\n\n\tget resourceManager() {\n\t\treturn this._resourceManager;\n\t}\n}\n\n/**\n\t* Builder class for ECSpresso that provides fluent type-safe bundle installation.\n\t* Handles type checking during build process to ensure type safety.\n*/\nexport class ECSpressoBuilder<\n\tC extends Record<string, any> = {},\n\tE extends Record<string, any> = {},\n\tR extends Record<string, any> = {}\n> {\n\t/** The ECSpresso instance being built*/\n\tprivate ecspresso: ECSpresso<C, E, R>;\n\n\tconstructor() {\n\t\tthis.ecspresso = new ECSpresso<C, E, R>();\n\t}\n\n\t/**\n\t\t* Add the first bundle when starting with empty types.\n\t\t* This overload allows any bundle to be added to an empty ECSpresso instance.\n\t*/\n\twithBundle<\n\t\tBC extends Record<string, any>,\n\t\tBE extends Record<string, any>,\n\t\tBR extends Record<string, any>\n\t>(\n\t\tthis: ECSpressoBuilder<{}, {}, {}>,\n\t\tbundle: Bundle<BC, BE, BR>\n\t): ECSpressoBuilder<BC, BE, BR>;\n\n\t/**\n\t\t* Add a subsequent bundle with type checking.\n\t\t* This overload enforces bundle type compatibility.\n\t*/\n\twithBundle<\n\t\tBC extends Record<string, any>,\n\t\tBE extends Record<string, any>,\n\t\tBR extends Record<string, any>\n\t>(\n\t\tbundle: BundlesAreCompatible<C, BC, E, BE, R, BR> extends true\n\t\t\t? Bundle<BC, BE, BR>\n\t\t\t: never\n\t): ECSpressoBuilder<C & BC, E & BE, R & BR>;\n\n\t/**\n\t\t* Implementation of both overloads.\n\t\t* Since the type compatibility is checked in the method signature,\n\t\t* we can safely assume the bundle is compatible here.\n\t*/\n\twithBundle<\n\t\tBC extends Record<string, any>,\n\t\tBE extends Record<string, any>,\n\t\tBR extends Record<string, any>\n\t>(\n\t\tbundle: Bundle<BC, BE, BR>\n\t): ECSpressoBuilder<C & BC, E & BE, R & BR> {\n\t\t// Install the bundle\n\t\t// Type compatibility is guaranteed by method overloads\n\t\tthis.ecspresso._installBundle(bundle as any);\n\n\t\t// Return a builder with the updated type parameters\n\t\treturn this as unknown as ECSpressoBuilder<C & BC, E & BE, R & BR>;\n\t}\n\n\t/**\n\t\t* Complete the build process and return the built ECSpresso instance\n\t*/\n\tbuild(): ECSpresso<C, E, R> {\n\t\treturn this.ecspresso;\n\t}\n}\n",
10
- "import { createBundleSystemBuilder, SystemBuilder } from './system-builder';\nimport type ECSpresso from './ecspresso';\n\n/**\n * Generates a unique ID for a bundle\n */\nfunction generateBundleId(): string {\n\treturn `bundle_${Date.now().toString(36)}_${Math.random().toString(36).substring(2, 9)}`;\n}\n\n/**\n * Bundle class that encapsulates a set of components, resources, events, and systems\n * that can be merged into a ECSpresso instance\n */\nexport default class Bundle<\n\tComponentTypes extends Record<string, any> = Record<string, any>,\n\tEventTypes extends Record<string, any> = Record<string, any>,\n\tResourceTypes extends Record<string, any> = Record<string, any>,\n> {\n\tprivate _systems: SystemBuilder<ComponentTypes, EventTypes, ResourceTypes, any>[] = [];\n\tprivate _resources: Map<keyof ResourceTypes, ResourceTypes[keyof ResourceTypes]> = new Map();\n\tprivate _id: string;\n\n\tconstructor(id?: string) {\n\t\tthis._id = id || generateBundleId();\n\t}\n\n\t/**\n\t * Get the unique ID of this bundle\n\t */\n\tget id(): string {\n\t\treturn this._id;\n\t}\n\n\t/**\n\t * Set the ID of this bundle\n\t * @internal Used by combineBundles\n\t */\n\tset id(value: string) {\n\t\tthis._id = value;\n\t}\n\n\t/**\n\t * Add a system to this bundle\n\t */\n\taddSystem(label: string) {\n\t\tconst system = createBundleSystemBuilder<ComponentTypes, EventTypes, ResourceTypes>(label, this);\n\n\t\tthis._systems.push(system);\n\n\t\treturn system;\n\t}\n\n\t/**\n\t * Add a resource to this bundle\n\t * @param label The resource key\n\t * @param resource The resource value\n\t */\n\taddResource<K extends keyof ResourceTypes>(label: K, resource: ResourceTypes[K]) {\n\t\tthis._resources.set(label, resource);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Get all systems defined in this bundle\n\t * Returns built System objects instead of SystemBuilders\n\t */\n\tgetSystems() {\n\t\treturn this._systems.map(system => system.build());\n\t}\n\n\t/**\n\t * Register all systems in this bundle with an ECSpresso instance\n\t * @internal Used by ECSpresso when adding a bundle\n\t */\n\tregisterSystemsWithEcspresso(ecspresso: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>) {\n\t\tfor (const systemBuilder of this._systems) {\n\t\t\tsystemBuilder.build(ecspresso);\n\t\t}\n\t}\n\n\t/**\n\t * Get all resources defined in this bundle\n\t */\n\tgetResources(): Map<keyof ResourceTypes, ResourceTypes[keyof ResourceTypes]> {\n\t\treturn new Map(this._resources);\n\t}\n\n\t/**\n\t * Get a specific resource by key\n\t * @param key The resource key\n\t * @returns The resource value or undefined if not found\n\t */\n\tgetResource<K extends keyof ResourceTypes>(key: K): ResourceTypes[K] | undefined {\n\t\treturn this._resources.get(key) as ResourceTypes[K] | undefined;\n\t}\n\n\t/**\n\t * Get all system builders in this bundle\n\t */\n\tgetSystemBuilders(): SystemBuilder<ComponentTypes, EventTypes, ResourceTypes, any>[] {\n\t\treturn [...this._systems];\n\t}\n\n\t/**\n\t * Check if this bundle has a specific resource\n\t * @param key The resource key to check\n\t * @returns True if the resource exists\n\t */\n\thasResource<K extends keyof ResourceTypes>(key: K): boolean {\n\t\treturn this._resources.has(key);\n\t}\n}\n\n// Check if object has exactly the same type\ntype Exactly<T, U> =\n T extends U\n ? U extends T\n ? true\n : false\n : false;\n\n// Create a type error for incompatible types\ntype IncompatibleBundles<\n C1 extends Record<string, any>,\n C2 extends Record<string, any>,\n E1 extends Record<string, any>,\n E2 extends Record<string, any>,\n R1 extends Record<string, any>,\n R2 extends Record<string, any>\n> = {\n [K in keyof C1 & keyof C2]: Exactly<C1[K], C2[K]> extends false ? never : unknown;\n} & {\n [K in keyof E1 & keyof E2]: Exactly<E1[K], E2[K]> extends false ? never : unknown;\n} & {\n [K in keyof R1 & keyof R2]: Exactly<R1[K], R2[K]> extends false ? never : unknown;\n};\n\n/**\n * Function that merges multiple bundles into a single bundle\n */\nexport function mergeBundles<\n C1 extends Record<string, any>,\n E1 extends Record<string, any>,\n R1 extends Record<string, any>,\n C2 extends Record<string, any>,\n E2 extends Record<string, any>,\n R2 extends Record<string, any>\n>(\n id: string,\n bundle1: Bundle<C1, E1, R1>,\n bundle2: Bundle<C2, E2, R2> & IncompatibleBundles<C1, C2, E1, E2, R1, R2>\n): Bundle<C1 & C2, E1 & E2, R1 & R2>;\n\nexport function mergeBundles<\n ComponentTypes extends Record<string, any>,\n EventTypes extends Record<string, any>,\n ResourceTypes extends Record<string, any>\n>(\n id: string,\n ...bundles: Array<Bundle<ComponentTypes, EventTypes, ResourceTypes>>\n): Bundle<ComponentTypes, EventTypes, ResourceTypes>;\n\nexport function mergeBundles(\n id: string,\n ...bundles: Array<Bundle<any, any, any>>\n): Bundle<any, any, any> {\n if (bundles.length === 0) {\n return new Bundle(id);\n }\n\n const combined = new Bundle(id);\n\n for (const bundle of bundles) {\n for (const system of bundle.getSystemBuilders()) {\n combined.addSystem(system as any);\n }\n\n // Add resources from this bundle\n for (const [label, resource] of bundle.getResources().entries()) {\n combined.addResource(label as any, resource);\n }\n }\n\n return combined as any;\n}\n",
7
+ "export default\nclass ResourceManager<ResourceTypes extends Record<string, any> = Record<string, any>> {\n\tprivate resources: Map<string, any> = new Map();\n\tprivate resourceFactories: Map<string, () => any | Promise<any>> = new Map();\n\tprivate initializedResourceKeys: Set<string> = new Set();\n\n\t/**\n\t * Add a resource to the manager\n\t * @param label The resource key\n\t * @param resource The resource value or a factory function that returns the resource\n\t * @returns The resource manager instance for chaining\n\t */\n\tadd<K extends keyof ResourceTypes | string, R = K extends keyof ResourceTypes ? ResourceTypes[K] : any>(\n\t\tlabel: K,\n\t\tresource: R | (() => R | Promise<R>)\n\t) {\n\t\tif (typeof resource === 'function' && !resource.toString().startsWith('class')) {\n\t\t\t// Likely a factory function\n\t\t\tthis.resourceFactories.set(label as string, resource as () => any | Promise<any>);\n\t\t} else {\n\t\t\t// Direct resource value\n\t\t\tthis.resources.set(label as string, resource);\n\t\t\tthis.initializedResourceKeys.add(label as string);\n\t\t}\n\t\treturn this;\n\t}\n\n\t/**\n\t * Get a resource from the manager\n\t * @param label The resource key\n\t * @returns The resource value\n\t * @throws Error if resource not found\n\t */\n\tget<K extends keyof ResourceTypes | string>(\n\t\tlabel: K\n\t): K extends keyof ResourceTypes ? ResourceTypes[K] : any {\n\t\t// Check if we already have the initialized resource\n\t\tconst resource = this.resources.get(label as string);\n\t\tif (resource !== undefined) {\n\t\t\treturn resource as any;\n\t\t}\n\n\t\t// Check if we have a factory for this resource\n\t\tconst factory = this.resourceFactories.get(label as string);\n\t\tif (factory === undefined) {\n\t\t\tthrow new Error(`Resource ${String(label)} not found`);\n\t\t}\n\n\t\t// Initialize the resource synchronously (this might throw if the factory returns a Promise)\n\t\tconst initializedResource = factory();\n\n\t\t// If it's not a Promise, store it immediately\n\t\tif (!(initializedResource instanceof Promise)) {\n\t\t\tthis.resources.set(label as string, initializedResource);\n\t\t\tthis.initializedResourceKeys.add(label as string);\n\t\t}\n\n\t\treturn initializedResource as any;\n\t}\n\n\t/**\n\t * Get a resource from the manager, returning undefined if not found\n\t * @param label The resource key\n\t * @returns The resource value or undefined if not found\n\t */\n\tgetOptional<K extends keyof ResourceTypes | string>(label: K): K extends keyof ResourceTypes ? ResourceTypes[K] | undefined : any {\n\t\ttry {\n\t\t\treturn this.get(label);\n\t\t} catch {\n\t\t\treturn undefined as any;\n\t\t}\n\t}\n\n\t/**\n\t * Check if a resource exists\n\t * @param label The resource key\n\t * @returns True if the resource exists\n\t */\n\thas<K extends keyof ResourceTypes | string>(label: K): boolean {\n\t\treturn this.resources.has(label as string) || this.resourceFactories.has(label as string);\n\t}\n\n\t/**\n\t * Remove a resource\n\t * @param label The resource key\n\t * @returns True if the resource was removed\n\t */\n\tremove<K extends keyof ResourceTypes | string>(label: K): boolean {\n\t\tconst resourceRemoved = this.resources.delete(label as string);\n\t\tconst factoryRemoved = this.resourceFactories.delete(label as string);\n\t\tif (this.initializedResourceKeys.has(label as string)) {\n\t\t\tthis.initializedResourceKeys.delete(label as string);\n\t\t}\n\t\treturn resourceRemoved || factoryRemoved;\n\t}\n\n\t/**\n\t * Get all resource keys\n\t * @returns Array of resource keys\n\t */\n\tgetKeys(): Array<string> {\n\t\tconst keys = new Set([\n\t\t\t...this.resources.keys(),\n\t\t\t...this.resourceFactories.keys()\n\t\t]);\n\t\treturn Array.from(keys);\n\t}\n\n\t/**\n\t * Check if a resource needs to be initialized\n\t * @param label The resource key\n\t * @returns True if the resource needs initialization\n\t */\n\tneedsInitialization<K extends keyof ResourceTypes | string>(label: K): boolean {\n\t\treturn this.resourceFactories.has(label as string) && !this.initializedResourceKeys.has(label as string);\n\t}\n\n\t/**\n\t * Get all resource keys that need to be initialized\n\t * @returns Array of resource keys that need initialization\n\t */\n\tgetPendingInitializationKeys(): Array<string> {\n\t\treturn Array\n\t\t\t.from(this.resourceFactories.keys())\n\t\t\t.filter(key => !this.initializedResourceKeys.has(key));\n\t}\n\n\t/**\n\t * Initialize a specific resource if it's a factory function\n\t * @param label The resource key\n\t * @returns Promise that resolves when the resource is initialized\n\t */\n\tasync initializeResource<K extends keyof ResourceTypes | string>(label: K): Promise<void> {\n\t\tif (!this.resourceFactories.has(label as string) || this.initializedResourceKeys.has(label as string)) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst factory = this.resourceFactories.get(label as string)!;\n\t\tconst initializedResource = await factory();\n\t\tthis.resources.set(label as string, initializedResource);\n\t\tthis.initializedResourceKeys.add(label as string);\n\t}\n\n\t/**\n\t * Initialize specific resources or all resources that haven't been initialized yet\n\t * @param keys Optional array of resource keys to initialize. If not provided, all pending resources will be initialized.\n\t * @returns Promise that resolves when the specified resources are initialized\n\t */\n\tasync initializeResources<K extends keyof ResourceTypes | string>(\n\t\t...keys: K[]\n\t): Promise<void> {\n\t\t// If no keys provided, initialize all pending resources\n\t\tif (keys.length === 0) {\n\t\t\tconst pendingKeys = this.getPendingInitializationKeys();\n\t\t\tawait Promise.all(pendingKeys.map(key => this.initializeResource(key)));\n\t\t\treturn;\n\t\t}\n\n\t\t// Otherwise, initialize only the specified resources\n\t\tawait Promise.all(\n\t\t\tkeys.map(key => this.initializeResource(key))\n\t\t);\n\t}\n}\n",
8
+ "import Bundle from \"./bundle\";\nimport ECSpresso from \"./ecspresso\";\nimport type { FilteredEntity, System } from \"./types\";\n\n/**\n * Builder class for creating type-safe ECS Systems with proper query inference\n */\nexport class SystemBuilder<\n\tComponentTypes extends Record<string, any> = Record<string, any>,\n\tEventTypes extends Record<string, any> = Record<string, any>,\n\tResourceTypes extends Record<string, any> = Record<string, any>,\n\tQueries extends Record<string, QueryDefinition<ComponentTypes>> = {},\n> {\n\tprivate queries: Queries = {} as Queries;\n\tprivate processFunction?: ProcessFunction<ComponentTypes, EventTypes, ResourceTypes, Queries>;\n\tprivate detachFunction?: LifecycleFunction<ComponentTypes, EventTypes, ResourceTypes>;\n\tprivate initializeFunction?: LifecycleFunction<ComponentTypes, EventTypes, ResourceTypes>;\n\tprivate eventHandlers?: {\n\t\t[EventName in keyof EventTypes]?: {\n\t\t\thandler(\n\t\t\t\tdata: EventTypes[EventName],\n\t\t\t\tecs: ECSpresso<\n\t\t\t\t\tComponentTypes & Record<string, any>,\n\t\t\t\t\tEventTypes,\n\t\t\t\t\tResourceTypes\n\t\t\t\t>,\n\t\t\t): void;\n\t\t};\n\t};\n\tprivate _priority = 0; // Default priority is 0\n\n\tconstructor(\n\t\tprivate _label: string,\n\t\tprivate _ecspresso: ECSpresso<ComponentTypes, EventTypes, ResourceTypes> | null = null,\n\t\tprivate _bundle: Bundle<ComponentTypes, EventTypes, ResourceTypes> | null = null,\n\t) {}\n\n\tget label() {\n\t\treturn this._label;\n\t}\n\n\t/**\n\t * Returns the associated bundle if one was provided in the constructor\n\t */\n\tget bundle() {\n\t\treturn this._bundle;\n\t}\n\n\t/**\n\t * Returns the associated ECSpresso instance if one was provided in the constructor\n\t */\n\tget ecspresso() {\n\t\treturn this._ecspresso;\n\t}\n\n\t// TODO: Should this be a setter?\n\t/**\n\t * Set the priority of this system. Systems with higher priority values\n\t * execute before those with lower values. Systems with the same priority\n\t * execute in the order they were registered.\n\t * @param priority The priority value (default: 0)\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\tsetPriority(priority: number): this {\n\t\tthis._priority = priority;\n\t\treturn this;\n\t}\n\n\t/**\n\t * Add a query definition to the system\n\t */\n\taddQuery<\n\t\tQueryName extends string,\n\t\tWithComponents extends keyof ComponentTypes,\n\t\tWithoutComponents extends keyof ComponentTypes = never,\n\t\tNewQueries extends Queries & Record<QueryName, QueryDefinition<ComponentTypes, WithComponents, WithoutComponents>> =\n\t\t\tQueries & Record<QueryName, QueryDefinition<ComponentTypes, WithComponents, WithoutComponents>>\n\t>(\n\t\tname: QueryName,\n\t\tdefinition: {\n\t\t\twith: ReadonlyArray<WithComponents>;\n\t\t\twithout?: ReadonlyArray<WithoutComponents>;\n\t\t}\n\t): this extends SystemBuilderWithEcspresso<ComponentTypes, EventTypes, ResourceTypes, Queries>\n\t\t? SystemBuilderWithEcspresso<ComponentTypes, EventTypes, ResourceTypes, NewQueries>\n\t\t: this extends SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, Queries>\n\t\t\t? SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, NewQueries>\n\t\t\t: SystemBuilder<ComponentTypes, EventTypes, ResourceTypes, NewQueries> {\n\t\t// Cast is needed because TypeScript can't preserve the type information\n\t\t// when modifying an object property\n\t\tconst newBuilder = this as any;\n\t\tnewBuilder.queries = {\n\t\t\t...this.queries,\n\t\t\t[name]: definition,\n\t\t};\n\t\treturn newBuilder;\n\t}\n\n\t/**\n\t * Set the system's process function that runs each update\n\t * @param process Function to process entities matching the system's queries each update\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\tsetProcess(\n\t\tprocess: ProcessFunction<ComponentTypes, EventTypes, ResourceTypes, Queries>\n\t): this {\n\t\tthis.processFunction = process;\n\t\treturn this;\n\t}\n\n\t/**\n\t * Set the onDetach lifecycle hook\n\t * Called when the system is removed from the ECS\n\t * @param onDetach Function to run when this system is detached from the ECS\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\tsetOnDetach(\n\t\tonDetach: LifecycleFunction<ComponentTypes, EventTypes, ResourceTypes>\n\t): this {\n\t\tthis.detachFunction = onDetach;\n\t\treturn this;\n\t}\n\n\t/**\n\t * Set the onInitialize lifecycle hook\n\t * Called when the system is initialized via ECSpresso.initialize() method\n\t * @param onInitialize Function to run when this system is initialized\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\tsetOnInitialize(\n\t\tonInitialize: LifecycleFunction<ComponentTypes, EventTypes, ResourceTypes>\n\t): this {\n\t\tthis.initializeFunction = onInitialize;\n\t\treturn this;\n\t}\n\n\t/**\n\t * Set event handlers for the system\n\t * These handlers will be automatically subscribed when the system is attached\n\t * @param handlers Object mapping event names to handler functions\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\tsetEventHandlers(\n\t\thandlers: {\n\t\t\t[EventName in keyof EventTypes]?: {\n\t\t\t\thandler(\n\t\t\t\t\tdata: EventTypes[EventName],\n\t\t\t\t\tecs: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>\n\t\t\t\t): void;\n\t\t\t};\n\t\t}\n\t): this {\n\t\tthis.eventHandlers = handlers;\n\t\treturn this;\n\t}\n\n\t/**\n\t * Build the final system object\n\t */\n\tbuild(ecspresso?: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>) {\n\t\tconst system: System<ComponentTypes, any, any, EventTypes, ResourceTypes> = {\n\t\t\tlabel: this._label,\n\t\t\tentityQueries: this.queries,\n\t\t\tpriority: this._priority,\n\t\t};\n\n\t\tif (this.processFunction) {\n\t\t\tsystem.process = this.processFunction;\n\t\t}\n\n\t\tif (this.detachFunction) {\n\t\t\tsystem.onDetach = this.detachFunction;\n\t\t}\n\n\t\tif (this.initializeFunction) {\n\t\t\tsystem.onInitialize = this.initializeFunction;\n\t\t}\n\n\t\tif (this.eventHandlers) {\n\t\t\tsystem.eventHandlers = this.eventHandlers;\n\t\t}\n\n\t\tif (this._ecspresso) {\n\t\t\tregisterSystemWithEcspresso(system, this._ecspresso);\n\t\t}\n\n\t\tif(ecspresso) {\n\t\t\tregisterSystemWithEcspresso(system, ecspresso);\n\t\t}\n\n\t\treturn this;\n\t}\n}\n\n/**\n * Helper function to register a system with an ECSpresso instance\n * This handles attaching the system and setting up event handlers\n * @internal Used by SystemBuilder and Bundle\n */\nexport function registerSystemWithEcspresso<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>\n>(\n\tsystem: System<ComponentTypes, any, any, EventTypes, ResourceTypes>,\n\tecspresso: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>\n) {\n\t// TODO: Remove the index notation workaround hack for private property access\n\t// Add system to ECSpresso's system list\n\tecspresso[\"_systems\"].push(system);\n\n\t// Trigger sorting of systems by priority\n\tecspresso[\"_sortSystems\"]();\n\n\tif(!system.eventHandlers) return;\n\n\tfor (const eventName in system.eventHandlers) {\n\t\tconst handler = system.eventHandlers[eventName]?.handler;\n\n\t\thandler && ecspresso.eventBus.subscribe(eventName, (data) => {\n\t\t\thandler(data, ecspresso);\n\t\t});\n\t}\n}\n\n// Helper type definitions\ntype QueryDefinition<\n\tComponentTypes,\n\tWithComponents extends keyof ComponentTypes = any,\n\tWithoutComponents extends keyof ComponentTypes = any,\n> = {\n\twith: ReadonlyArray<WithComponents>;\n\twithout?: ReadonlyArray<WithoutComponents>;\n};\n\ntype QueryResults<\n\tComponentTypes,\n\tQueries extends Record<string, QueryDefinition<ComponentTypes>>,\n> = {\n\t[QueryName in keyof Queries]: QueryName extends string\n\t\t? FilteredEntity<\n\t\t\tComponentTypes,\n\t\t\tQueries[QueryName] extends QueryDefinition<ComponentTypes, infer W, any> ? W : never,\n\t\t\tQueries[QueryName] extends QueryDefinition<ComponentTypes, any, infer WO> ? WO : never\n\t\t>[]\n\t\t: never;\n};\n\n/**\n * Function signature for system process methods\n * @param queries Results of entity queries defined by the system\n * @param deltaTime Time elapsed since last update in seconds\n * @param ecs The ECSpresso instance providing access to all ECS functionality\n */\ntype ProcessFunction<\n\tComponentTypes,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>,\n\tQueries extends Record<string, QueryDefinition<ComponentTypes>>,\n> = (\n\tqueries: QueryResults<ComponentTypes, Queries>,\n\tdeltaTime: number,\n\tecs: ECSpresso<\n\t\tComponentTypes & Record<string, any>,\n\t\tEventTypes,\n\t\tResourceTypes\n\t>\n) => void;\n\n/**\n * Type for system initialization functions\n * These can be asynchronous\n */\ntype LifecycleFunction<\n\tComponentTypes,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>,\n> = (\n\tecs: ECSpresso<\n\t\tComponentTypes & Record<string, any>,\n\t\tEventTypes,\n\t\tResourceTypes\n\t>,\n) => void | Promise<void>;\n\n/**\n * Create a SystemBuilder attached to an ECSpresso instance\n * Helper function used by ECSpresso.addSystem\n */\nexport function createEcspressoSystemBuilder<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>\n>(\n\tlabel: string,\n\tecspresso: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>\n): SystemBuilderWithEcspresso<ComponentTypes, EventTypes, ResourceTypes> {\n\treturn new SystemBuilder<ComponentTypes, EventTypes, ResourceTypes>(\n\t\tlabel,\n\t\tecspresso\n\t) as SystemBuilderWithEcspresso<ComponentTypes, EventTypes, ResourceTypes>;\n}\n\n/**\n * Create a SystemBuilder attached to a Bundle\n * Helper function used by Bundle.addSystem\n */\nexport function createBundleSystemBuilder<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>\n>(\n\tlabel: string,\n\tbundle: Bundle<ComponentTypes, EventTypes, ResourceTypes>\n): SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes> {\n\treturn new SystemBuilder<ComponentTypes, EventTypes, ResourceTypes>(\n\t\tlabel,\n\t\tnull,\n\t\tbundle\n\t) as SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes>;\n}\n\n// Type interfaces for specialized SystemBuilders\n\n/**\n * SystemBuilder with a guaranteed non-null reference to an ECSpresso instance\n */\nexport interface SystemBuilderWithEcspresso<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>,\n\tQueries extends Record<string, QueryDefinition<ComponentTypes>> = {}\n> extends SystemBuilder<ComponentTypes, EventTypes, ResourceTypes, Queries> {\n\treadonly ecspresso: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>;\n}\n\n/**\n * SystemBuilder with a guaranteed non-null reference to a Bundle\n */\nexport interface SystemBuilderWithBundle<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>,\n\tQueries extends Record<string, QueryDefinition<ComponentTypes>> = {}\n> extends SystemBuilder<ComponentTypes, EventTypes, ResourceTypes, Queries> {\n\treadonly bundle: Bundle<ComponentTypes, EventTypes, ResourceTypes>;\n}\n",
9
+ "import EntityManager from \"./entity-manager\";\nimport EventBus from \"./event-bus\";\nimport ResourceManager from \"./resource-manager\";\nimport type { System, FilteredEntity } from \"./types\";\nimport type Bundle from \"./bundle\";\nimport { createEcspressoSystemBuilder } from \"./system-builder\";\nimport { version } from \"../package.json\";\n\n/**\n\t* Type helper to detect conflicting types between two record types.\n\t* Returns a union of keys that exist in both T and U but have incompatible types.\n*/\ntype GetConflictingKeys<T, U> = {\n\t[K in keyof T & keyof U]: T[K] extends U[K]\n\t\t? U[K] extends T[K]\n\t\t\t? never\n\t\t\t: K\n\t\t: K\n}[keyof T & keyof U];\n\n/**\n\t* Simplified type helper to check bundle type compatibility.\n\t* Returns true if bundles can be merged without type conflicts.\n*/\ntype BundlesAreCompatible<\n\tC1 extends Record<string, any>,\n\tC2 extends Record<string, any>,\n\tE1 extends Record<string, any>,\n\tE2 extends Record<string, any>,\n\tR1 extends Record<string, any>,\n\tR2 extends Record<string, any>\n> =\n\t// If all base types are empty, any bundle is compatible\n\t[keyof C1] extends [never]\n\t\t? [keyof E1] extends [never]\n\t\t\t? [keyof R1] extends [never]\n\t\t\t\t? true\n\t\t\t\t: GetConflictingKeys<R1, R2> extends never ? true : false\n\t\t\t: GetConflictingKeys<E1, E2> extends never\n\t\t\t\t? GetConflictingKeys<R1, R2> extends never ? true : false\n\t\t\t\t: false\n\t\t: GetConflictingKeys<C1, C2> extends never\n\t\t\t? GetConflictingKeys<E1, E2> extends never\n\t\t\t\t? GetConflictingKeys<R1, R2> extends never\n\t\t\t\t\t? true\n\t\t\t\t\t: false\n\t\t\t\t: false\n\t\t\t: false;\n\n/**\n\t* Interface declaration for ECSpresso constructor to ensure type augmentation works properly.\n\t* This merges with the class declaration below.\n*/\nexport default interface ECSpresso<\n\tComponentTypes extends Record<string, any> = {},\n\tEventTypes extends Record<string, any> = {},\n\tResourceTypes extends Record<string, any> = {},\n> {\n\t/**\n\t\t* Default constructor\n\t*/\n\tnew(): ECSpresso<ComponentTypes, EventTypes, ResourceTypes>;\n}\n\n/**\n\t* Static methods on the ECSpresso class\n*/\nexport default interface ECSpresso {\n\t\t/**\n\t\t\t* Create a new ECSpresso builder with type-safe bundle installation.\n\t\t\t* This is the preferred way to create an ECSpresso instance with bundles.\n\t\t *\n\t\t\t* Example:\n\t\t\t* ```typescript\n\t\t\t* const ecs = ECSpresso.create<MyComponents, MyEvents, MyResources>()\n\t\t *\t .withBundle(bundle1)\n\t\t *\t .withBundle(bundle2)\n\t\t *\t .build();\n\t\t\t* ```\n\t\t*/\n\t\tcreate<\n\t\t\t\tBaseC extends Record<string, any> = {},\n\t\t\t\tBaseE extends Record<string, any> = {},\n\t\t\t\tBaseR extends Record<string, any> = {},\n\t\t>(): ECSpressoBuilder<BaseC, BaseE, BaseR>;\n}\n\n/**\n\t* ECSpresso is the central ECS framework class that connects all features.\n\t* It handles creation and management of entities, components, and systems, and provides lifecycle hooks.\n*/\nexport default class ECSpresso<\n\tComponentTypes extends Record<string, any> = {},\n\tEventTypes extends Record<string, any> = {},\n\tResourceTypes extends Record<string, any> = {},\n> {\n\t/** Library version*/\n\tpublic static readonly VERSION = version;\n\n\t/** Access/modify stored components and entities*/\n\tprivate _entityManager: EntityManager<ComponentTypes>;\n\t/** Publish/subscribe to events*/\n\tprivate _eventBus: EventBus<EventTypes>;\n\t/** Access/modify registered resources*/\n\tprivate _resourceManager: ResourceManager<ResourceTypes>;\n\n\t/** Registered systems that will be updated in order*/\n\tprivate _systems: Array<System<ComponentTypes, any, any, EventTypes, ResourceTypes>> = [];\n\t/** Cached sorted systems for efficient updates */\n\tprivate _sortedSystems: Array<System<ComponentTypes, any, any, EventTypes, ResourceTypes>> = [];\n\t/** Track installed bundles to prevent duplicates*/\n\tprivate _installedBundles: Set<string> = new Set();\n\n\t/**\n\t\t* Creates a new ECSpresso instance.\n\t*/\n\tconstructor() {\n\t\tthis._entityManager = new EntityManager<ComponentTypes>();\n\t\tthis._eventBus = new EventBus<EventTypes>();\n\t\tthis._resourceManager = new ResourceManager<ResourceTypes>();\n\t\tthis._sortedSystems = []; // Initialize the sorted systems array\n\t}\n\n\t/**\n\t\t* Creates a new ECSpresso builder for type-safe bundle installation.\n\t\t* This is the preferred way to create an ECSpresso instance with bundles.\n\t *\n\t\t* @returns A builder instance for fluent method chaining\n\t *\n\t\t* @example\n\t\t* ```typescript\n\t\t* const ecs = ECSpresso.create<BaseComponents, BaseEvents, BaseResources>()\n\t *\t .withBundle(bundle1)\n\t *\t .withBundle(bundle2)\n\t *\t .build();\n\t\t* ```\n\t*/\n\tstatic create<\n\t\tC extends Record<string, any> = {},\n\t\tE extends Record<string, any> = {},\n\t\tR extends Record<string, any> = {},\n\t>(): ECSpressoBuilder<C, E, R> {\n\t\treturn new ECSpressoBuilder<C, E, R>();\n\t}\n\n\t/**\n\t\t* Adds a system directly to this ECSpresso instance\n\t\t* @param label Unique name to identify the system\n\t\t* @returns A SystemBuilder instance for method chaining\n\t*/\n\taddSystem(label: string) {\n\t\treturn createEcspressoSystemBuilder<\n\t\t\tComponentTypes,\n\t\t\tEventTypes,\n\t\t\tResourceTypes\n\t\t>(label, this);\n\t}\n\n\t/**\n\t\t* Update all systems, passing deltaTime and query results to each system's process function\n\t\t* @param deltaTime Time elapsed since the last update (in seconds)\n\t*/\n\tupdate(deltaTime: number) {\n\t\t// Use the cached sorted systems array instead of re-sorting on every update\n\t\tfor (const system of this._sortedSystems) {\n\t\t\tif (!system.process) continue;\n\n\t\t\t// Prepare query results for each defined query in the system\n\t\t\tconst queryResults: Record<string, any> = {};\n\n\t\t\tif (system.entityQueries) {\n\t\t\t\tfor (const queryName in system.entityQueries) {\n\t\t\t\t\tconst query = system.entityQueries[queryName];\n\t\t\t\t\tif (query) {\n\t\t\t\t\t\tqueryResults[queryName] = this._entityManager.getEntitiesWithComponents(\n\t\t\t\t\t\t\tquery.with,\n\t\t\t\t\t\t\tquery.without || []\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Call the system's process function\n\t\t\tsystem.process(queryResults, deltaTime, this);\n\t\t}\n\t}\n\n\t/**\n\t * Initialize all resources and systems\n\t * This method:\n\t * 1. Initializes all resources that were added as factory functions\n\t * 2. Calls the onInitialize lifecycle hook on all systems\n\t *\n\t * This is useful for game startup to ensure all resources are ready\n\t * and systems are properly initialized before the game loop begins.\n\t *\n\t * @param resourceKeys Optional array of specific resource keys to initialize\n\t * @returns Promise that resolves when everything is initialized\n\t */\n\tasync initialize(): Promise<void> {\n\t\tawait this.initializeResources();\n\n\t\tfor (const system of this._systems) {\n\t\t\tawait system.onInitialize?.(this);\n\t\t}\n\t}\n\n\t/**\n\t * Initialize specific resources or all resources that were added as factory functions but haven't been initialized yet.\n\t * This is useful when you need to ensure resources are ready before proceeding.\n\t * @param keys Optional array of resource keys to initialize. If not provided, all pending resources will be initialized.\n\t * @returns Promise that resolves when the specified resources are initialized\n\t */\n\tasync initializeResources<K extends keyof ResourceTypes>(...keys: K[]): Promise<void> {\n\t\tawait this._resourceManager.initializeResources(...keys);\n\t}\n\n\t/**\n\t\t* Sort the systems array by priority (higher priority first)\n\t\t* Called internally when system list changes\n\t\t* @private\n\t*/\n\tprivate _sortSystems(): void {\n\t\tthis._sortedSystems = [...this._systems].sort((a, b) => {\n\t\t\tconst priorityA = a.priority ?? 0;\n\t\t\tconst priorityB = b.priority ?? 0;\n\t\t\treturn priorityB - priorityA; // Higher priority executes first\n\t\t});\n\t}\n\n\t/**\n\t\t* Update the priority of a system\n\t\t* @param label The unique label of the system to update\n\t\t* @param priority The new priority value (higher values execute first)\n\t\t* @returns true if the system was found and updated, false otherwise\n\t*/\n\tupdateSystemPriority(label: string, priority: number): boolean {\n\t\tconst system = this._systems.find(system => system.label === label);\n\t\tif (!system) return false;\n\n\t\t// Set the new priority\n\t\tsystem.priority = priority;\n\n\t\t// Re-sort the systems array\n\t\tthis._sortSystems();\n\n\t\treturn true;\n\t}\n\n\t/**\n\t\t* Remove a system by its label\n\t\t* Calls the system's onDetach method with this ECSpresso instance if defined\n\t\t* @param label The unique label of the system to remove\n\t\t* @returns true if the system was found and removed, false otherwise\n\t*/\n\tremoveSystem(label: string): boolean {\n\t\tconst index = this._systems.findIndex(system => system.label === label);\n\t\tif (index === -1) return false;\n\n\t\tconst system = this._systems[index];\n\t\t// This should never happen since we just found the system by index\n\t\tif (!system) return false;\n\n\t\t// Call the onDetach lifecycle hook if defined\n\t\tif (system.onDetach) {\n\t\t\tsystem.onDetach(this);\n\t\t}\n\n\t\t// Remove system\n\t\tthis._systems.splice(index, 1);\n\n\t\t// Re-sort systems\n\t\tthis._sortSystems();\n\n\t\treturn true;\n\t}\n\n\t/**\n\t\t* Check if a resource exists\n\t*/\n\thasResource<K extends keyof ResourceTypes>(key: K): boolean {\n\t\treturn this._resourceManager.has(key);\n\t}\n\n\t/**\n\t\t* Get a resource if it exists, or undefined if not\n\t*/\n\tgetResource<K extends keyof ResourceTypes>(key: K): ResourceTypes[K] {\n\t\tconst resource = this._resourceManager.get(key);\n\n\t\tif (!resource) throw new Error(`Resource \"${key.toString()}\" not found`);\n\n\t\treturn resource;\n\t}\n\n\t/**\n\t\t* Add a resource to the ECS instance\n\t*/\n\taddResource<K extends keyof ResourceTypes>(\n\t\tkey: K,\n\t\tresource: ResourceTypes[K] | (() => ResourceTypes[K] | Promise<ResourceTypes[K]>)\n\t): this {\n\t\tthis._resourceManager.add(key, resource);\n\t\treturn this;\n\t}\n\n\t/**\n\t\t* Check if an entity has a component\n\t*/\n\thasComponent<K extends keyof ComponentTypes>(\n\t\tentityId: number,\n\t\tcomponentName: K\n\t): boolean {\n\t\tconst component = this._entityManager.getComponent(entityId, componentName);\n\t\treturn component !== null;\n\t}\n\n\t/**\n\t\t* Get all entities with specific components\n\t*/\n\tgetEntitiesWithComponents<\n\t\tWithComponents extends keyof ComponentTypes,\n\t\tWithoutComponents extends keyof ComponentTypes = never\n\t>(\n\t\twithComponents: ReadonlyArray<WithComponents>,\n\t\twithoutComponents: ReadonlyArray<WithoutComponents> = []\n\t): Array<FilteredEntity<ComponentTypes, WithComponents, WithoutComponents>> {\n\t\treturn this._entityManager.getEntitiesWithComponents(\n\t\t\twithComponents,\n\t\t\twithoutComponents\n\t\t);\n\t}\n\n\t/**\n\t\t* Get all installed bundle IDs\n\t*/\n\tget installedBundles(): string[] {\n\t\treturn Array.from(this._installedBundles);\n\t}\n\n\t// Getters for the internal managers\n\tget entityManager() {\n\t\treturn this._entityManager;\n\t}\n\n\tget eventBus() {\n\t\treturn this._eventBus;\n\t}\n\n\tget resourceManager() {\n\t\treturn this._resourceManager;\n\t}\n\n\t/**\n\t\t* Internal method to install a bundle into this ECSpresso instance.\n\t\t* Called by the ECSpressoBuilder during the build process.\n\t\t* The type safety is guaranteed by the builder's type system.\n\t*/\n\t_installBundle<\n\t\tC extends Record<string, any>,\n\t\tE extends Record<string, any>,\n\t\tR extends Record<string, any>\n\t>(bundle: Bundle<C, E, R>): this {\n\t\t// Prevent duplicate installation of the same bundle\n\t\tif (this._installedBundles.has(bundle.id)) {\n\t\t\treturn this;\n\t\t}\n\n\t\t// Mark this bundle as installed\n\t\tthis._installedBundles.add(bundle.id);\n\n\t\t// Register systems from the bundle\n\t\t// The type compatibility is ensured by the builder's withBundle method\n\t\t// We need this cast due to TypeScript's limitations with generics\n\t\ttype BundleEcspresso = ECSpresso<C, E, R>;\n\t\tbundle.registerSystemsWithEcspresso(this as unknown as BundleEcspresso);\n\n\t\t// Register resources from the bundle\n\t\tconst resources = bundle.getResources();\n\t\tfor (const [key, value] of resources.entries()) {\n\t\t\t// Instead of casting, use the add method's flexibility\n\t\t\tthis._resourceManager.add(key as string, value);\n\t\t}\n\n\t\treturn this;\n\t}\n}\n\n/**\n\t* Builder class for ECSpresso that provides fluent type-safe bundle installation.\n\t* Handles type checking during build process to ensure type safety.\n*/\nexport class ECSpressoBuilder<\n\tC extends Record<string, any> = {},\n\tE extends Record<string, any> = {},\n\tR extends Record<string, any> = {}\n> {\n\t/** The ECSpresso instance being built*/\n\tprivate ecspresso: ECSpresso<C, E, R>;\n\n\tconstructor() {\n\t\tthis.ecspresso = new ECSpresso<C, E, R>();\n\t}\n\n\t/**\n\t\t* Add the first bundle when starting with empty types.\n\t\t* This overload allows any bundle to be added to an empty ECSpresso instance.\n\t*/\n\twithBundle<\n\t\tBC extends Record<string, any>,\n\t\tBE extends Record<string, any>,\n\t\tBR extends Record<string, any>\n\t>(\n\t\tthis: ECSpressoBuilder<{}, {}, {}>,\n\t\tbundle: Bundle<BC, BE, BR>\n\t): ECSpressoBuilder<BC, BE, BR>;\n\n\t/**\n\t\t* Add a subsequent bundle with type checking.\n\t\t* This overload enforces bundle type compatibility.\n\t*/\n\twithBundle<\n\t\tBC extends Record<string, any>,\n\t\tBE extends Record<string, any>,\n\t\tBR extends Record<string, any>\n\t>(\n\t\tbundle: BundlesAreCompatible<C, BC, E, BE, R, BR> extends true\n\t\t\t? Bundle<BC, BE, BR>\n\t\t\t: never\n\t): ECSpressoBuilder<C & BC, E & BE, R & BR>;\n\n\t/**\n\t\t* Implementation of both overloads.\n\t\t* Since the type compatibility is checked in the method signature,\n\t\t* we can safely assume the bundle is compatible here.\n\t*/\n\twithBundle<\n\t\tBC extends Record<string, any>,\n\t\tBE extends Record<string, any>,\n\t\tBR extends Record<string, any>\n\t>(\n\t\tbundle: Bundle<BC, BE, BR>\n\t): ECSpressoBuilder<C & BC, E & BE, R & BR> {\n\t\t// Install the bundle\n\t\t// Type compatibility is guaranteed by method overloads\n\t\tthis.ecspresso._installBundle(bundle);\n\n\t\t// Return a builder with the updated type parameters\n\t\treturn this as unknown as ECSpressoBuilder<C & BC, E & BE, R & BR>;\n\t}\n\n\t/**\n\t\t* Complete the build process and return the built ECSpresso instance\n\t*/\n\tbuild(): ECSpresso<C, E, R> {\n\t\treturn this.ecspresso;\n\t}\n}\n",
10
+ "import { createBundleSystemBuilder, SystemBuilder } from './system-builder';\nimport type ECSpresso from './ecspresso';\n\n/**\n * Generates a unique ID for a bundle\n */\nfunction generateBundleId(): string {\n\treturn `bundle_${Date.now().toString(36)}_${Math.random().toString(36).substring(2, 9)}`;\n}\n\n/**\n * Bundle class that encapsulates a set of components, resources, events, and systems\n * that can be merged into a ECSpresso instance\n */\nexport default class Bundle<\n\tComponentTypes extends Record<string, any> = {},\n\tEventTypes extends Record<string, any> = {},\n\tResourceTypes extends Record<string, any> = {},\n> {\n\tprivate _systems: SystemBuilder<ComponentTypes, EventTypes, ResourceTypes, any>[] = [];\n\tprivate _resources: Map<keyof ResourceTypes, ResourceTypes[keyof ResourceTypes]> = new Map();\n\tprivate _id: string;\n\n\tconstructor(id?: string) {\n\t\tthis._id = id || generateBundleId();\n\t}\n\n\t/**\n\t * Get the unique ID of this bundle\n\t */\n\tget id(): string {\n\t\treturn this._id;\n\t}\n\n\t/**\n\t * Set the ID of this bundle\n\t * @internal Used by combineBundles\n\t */\n\tset id(value: string) {\n\t\tthis._id = value;\n\t}\n\n\t/**\n\t * Add a system to this bundle\n\t */\n\taddSystem(label: string) {\n\t\tconst system = createBundleSystemBuilder<ComponentTypes, EventTypes, ResourceTypes>(label, this);\n\n\t\tthis._systems.push(system);\n\n\t\treturn system;\n\t}\n\n\t/**\n\t * Add a resource to this bundle\n\t * @param label The resource key\n\t * @param resource The resource value or a factory function that returns the resource\n\t */\n\taddResource<K extends keyof ResourceTypes>(\n\t\tlabel: K,\n\t\tresource: ResourceTypes[K] | (() => ResourceTypes[K] | Promise<ResourceTypes[K]>)\n\t) {\n\t\t// We need this cast because TypeScript doesn't recognize that a value of type\n\t\t// ResourceTypes[K] | (() => ResourceTypes[K] | Promise<ResourceTypes[K]>)\n\t\t// can be properly assigned to Map<keyof ResourceTypes, ResourceTypes[keyof ResourceTypes]>\n\t\tthis._resources.set(label, resource as unknown as ResourceTypes[K]);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Get all systems defined in this bundle\n\t * Returns built System objects instead of SystemBuilders\n\t */\n\tgetSystems() {\n\t\treturn this._systems.map(system => system.build());\n\t}\n\n\t/**\n\t * Register all systems in this bundle with an ECSpresso instance\n\t * @internal Used by ECSpresso when adding a bundle\n\t */\n\tregisterSystemsWithEcspresso(ecspresso: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>) {\n\t\tfor (const systemBuilder of this._systems) {\n\t\t\tsystemBuilder.build(ecspresso);\n\t\t}\n\t}\n\n\t/**\n\t * Get all resources defined in this bundle\n\t */\n\tgetResources(): Map<keyof ResourceTypes, ResourceTypes[keyof ResourceTypes]> {\n\t\treturn new Map(this._resources);\n\t}\n\n\t/**\n\t * Get a specific resource by key\n\t * @param key The resource key\n\t * @returns The resource value or undefined if not found\n\t */\n\tgetResource<K extends keyof ResourceTypes>(key: K): ResourceTypes[K] {\n\t\treturn this._resources.get(key) as ResourceTypes[K];\n\t}\n\n\t/**\n\t * Get all system builders in this bundle\n\t */\n\tgetSystemBuilders(): SystemBuilder<ComponentTypes, EventTypes, ResourceTypes, any>[] {\n\t\treturn [...this._systems];\n\t}\n\n\t/**\n\t * Check if this bundle has a specific resource\n\t * @param key The resource key to check\n\t * @returns True if the resource exists\n\t */\n\thasResource<K extends keyof ResourceTypes>(key: K): boolean {\n\t\treturn this._resources.has(key);\n\t}\n}\n\n// Check if object has exactly the same type\ntype Exactly<T, U> =\n\tT extends U\n\t\t? U extends T\n\t\t\t? true\n\t\t\t: false\n\t\t: false;\n\n// Create a type error for incompatible types\ntype IncompatibleBundles<\n\tC1 extends Record<string, any>,\n\tC2 extends Record<string, any>,\n\tE1 extends Record<string, any>,\n\tE2 extends Record<string, any>,\n\tR1 extends Record<string, any>,\n\tR2 extends Record<string, any>\n> = {\n\t[K in keyof C1 & keyof C2]: Exactly<C1[K], C2[K]> extends false ? never : unknown;\n} & {\n\t[K in keyof E1 & keyof E2]: Exactly<E1[K], E2[K]> extends false ? never : unknown;\n} & {\n\t[K in keyof R1 & keyof R2]: Exactly<R1[K], R2[K]> extends false ? never : unknown;\n};\n\n/**\n * Function that merges multiple bundles into a single bundle\n */\nexport function mergeBundles<\n\tC1 extends Record<string, any>,\n\tE1 extends Record<string, any>,\n\tR1 extends Record<string, any>,\n\tC2 extends Record<string, any>,\n\tE2 extends Record<string, any>,\n\tR2 extends Record<string, any>\n>(\n\tid: string,\n\tbundle1: Bundle<C1, E1, R1>,\n\tbundle2: Bundle<C2, E2, R2> & IncompatibleBundles<C1, C2, E1, E2, R1, R2>\n): Bundle<C1 & C2, E1 & E2, R1 & R2>;\n\nexport function mergeBundles<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>\n>(\n\tid: string,\n\t...bundles: Array<Bundle<ComponentTypes, EventTypes, ResourceTypes>>\n): Bundle<ComponentTypes, EventTypes, ResourceTypes>;\n\nexport function mergeBundles(\n\tid: string,\n\t...bundles: Array<Bundle>\n): Bundle {\n\tif (bundles.length === 0) {\n\t\treturn new Bundle(id);\n\t}\n\n\tconst combined = new Bundle(id);\n\n\tfor (const bundle of bundles) {\n\t\tfor (const system of bundle.getSystemBuilders()) {\n\t\t\tcombined.addSystem(system.label);\n\t\t}\n\n\t\t// Add resources from this bundle\n\t\tfor (const [label, resource] of bundle.getResources().entries()) {\n\t\t\tcombined.addResource(label, resource);\n\t\t}\n\t}\n\n\treturn combined;\n}\n",
11
11
  "import ECSpresso from './ecspresso';\nimport { SystemBuilder } from './system-builder';\nimport Bundle, { mergeBundles } from './bundle';\n\nexport * from './types';\nexport { default as EntityManager } from './entity-manager';\nexport { default as EventBus } from './event-bus';\nexport { default as ResourceManager } from './resource-manager';\nexport { SystemBuilder };\nexport { Bundle, mergeBundles };\nexport default ECSpresso;\n"
12
12
  ],
13
- "mappings": "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",
14
- "debugId": "5B3F11C4615EDE1564756E2164756E21",
13
+ "mappings": "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",
14
+ "debugId": "F24A71CB687B735F64756E2164756E21",
15
15
  "names": []
16
16
  }
@@ -1,12 +1,14 @@
1
1
  export default class ResourceManager<ResourceTypes extends Record<string, any> = Record<string, any>> {
2
2
  private resources;
3
+ private resourceFactories;
4
+ private initializedResourceKeys;
3
5
  /**
4
6
  * Add a resource to the manager
5
7
  * @param label The resource key
6
- * @param resource The resource value
8
+ * @param resource The resource value or a factory function that returns the resource
7
9
  * @returns The resource manager instance for chaining
8
10
  */
9
- add<K extends keyof ResourceTypes | string>(label: K, resource: K extends keyof ResourceTypes ? ResourceTypes[K] : any): this;
11
+ add<K extends keyof ResourceTypes | string, R = K extends keyof ResourceTypes ? ResourceTypes[K] : any>(label: K, resource: R | (() => R | Promise<R>)): this;
10
12
  /**
11
13
  * Get a resource from the manager
12
14
  * @param label The resource key
@@ -19,7 +21,7 @@ export default class ResourceManager<ResourceTypes extends Record<string, any> =
19
21
  * @param label The resource key
20
22
  * @returns The resource value or undefined if not found
21
23
  */
22
- getOptional<K extends keyof ResourceTypes | string>(label: K): K extends keyof ResourceTypes ? ResourceTypes[K] : any | undefined;
24
+ getOptional<K extends keyof ResourceTypes | string>(label: K): K extends keyof ResourceTypes ? ResourceTypes[K] | undefined : any;
23
25
  /**
24
26
  * Check if a resource exists
25
27
  * @param label The resource key
@@ -37,4 +39,27 @@ export default class ResourceManager<ResourceTypes extends Record<string, any> =
37
39
  * @returns Array of resource keys
38
40
  */
39
41
  getKeys(): Array<string>;
42
+ /**
43
+ * Check if a resource needs to be initialized
44
+ * @param label The resource key
45
+ * @returns True if the resource needs initialization
46
+ */
47
+ needsInitialization<K extends keyof ResourceTypes | string>(label: K): boolean;
48
+ /**
49
+ * Get all resource keys that need to be initialized
50
+ * @returns Array of resource keys that need initialization
51
+ */
52
+ getPendingInitializationKeys(): Array<string>;
53
+ /**
54
+ * Initialize a specific resource if it's a factory function
55
+ * @param label The resource key
56
+ * @returns Promise that resolves when the resource is initialized
57
+ */
58
+ initializeResource<K extends keyof ResourceTypes | string>(label: K): Promise<void>;
59
+ /**
60
+ * Initialize specific resources or all resources that haven't been initialized yet
61
+ * @param keys Optional array of resource keys to initialize. If not provided, all pending resources will be initialized.
62
+ * @returns Promise that resolves when the specified resources are initialized
63
+ */
64
+ initializeResources<K extends keyof ResourceTypes | string>(...keys: K[]): Promise<void>;
40
65
  }
@@ -10,9 +10,10 @@ export declare class SystemBuilder<ComponentTypes extends Record<string, any> =
10
10
  private _bundle;
11
11
  private queries;
12
12
  private processFunction?;
13
- private attachFunction?;
14
13
  private detachFunction?;
14
+ private initializeFunction?;
15
15
  private eventHandlers?;
16
+ private _priority;
16
17
  constructor(_label: string, _ecspresso?: ECSpresso<ComponentTypes, EventTypes, ResourceTypes> | null, _bundle?: Bundle<ComponentTypes, EventTypes, ResourceTypes> | null);
17
18
  get label(): string;
18
19
  /**
@@ -23,6 +24,14 @@ export declare class SystemBuilder<ComponentTypes extends Record<string, any> =
23
24
  * Returns the associated ECSpresso instance if one was provided in the constructor
24
25
  */
25
26
  get ecspresso(): ECSpresso<ComponentTypes, EventTypes, ResourceTypes> | null;
27
+ /**
28
+ * Set the priority of this system. Systems with higher priority values
29
+ * execute before those with lower values. Systems with the same priority
30
+ * execute in the order they were registered.
31
+ * @param priority The priority value (default: 0)
32
+ * @returns This SystemBuilder instance for method chaining
33
+ */
34
+ setPriority(priority: number): this;
26
35
  /**
27
36
  * Add a query definition to the system
28
37
  */
@@ -36,13 +45,6 @@ export declare class SystemBuilder<ComponentTypes extends Record<string, any> =
36
45
  * @returns This SystemBuilder instance for method chaining
37
46
  */
38
47
  setProcess(process: ProcessFunction<ComponentTypes, EventTypes, ResourceTypes, Queries>): this;
39
- /**
40
- * Set the onAttach lifecycle hook
41
- * Called when the system is attached to the ECS
42
- * @param onAttach Function to run when this system is attached to the ECS
43
- * @returns This SystemBuilder instance for method chaining
44
- */
45
- setOnAttach(onAttach: LifecycleFunction<ComponentTypes, EventTypes, ResourceTypes>): this;
46
48
  /**
47
49
  * Set the onDetach lifecycle hook
48
50
  * Called when the system is removed from the ECS
@@ -50,6 +52,13 @@ export declare class SystemBuilder<ComponentTypes extends Record<string, any> =
50
52
  * @returns This SystemBuilder instance for method chaining
51
53
  */
52
54
  setOnDetach(onDetach: LifecycleFunction<ComponentTypes, EventTypes, ResourceTypes>): this;
55
+ /**
56
+ * Set the onInitialize lifecycle hook
57
+ * Called when the system is initialized via ECSpresso.initialize() method
58
+ * @param onInitialize Function to run when this system is initialized
59
+ * @returns This SystemBuilder instance for method chaining
60
+ */
61
+ setOnInitialize(onInitialize: LifecycleFunction<ComponentTypes, EventTypes, ResourceTypes>): this;
53
62
  /**
54
63
  * Set event handlers for the system
55
64
  * These handlers will be automatically subscribed when the system is attached
@@ -87,10 +96,10 @@ type QueryResults<ComponentTypes, Queries extends Record<string, QueryDefinition
87
96
  */
88
97
  type ProcessFunction<ComponentTypes, EventTypes extends Record<string, any>, ResourceTypes extends Record<string, any>, Queries extends Record<string, QueryDefinition<ComponentTypes>>> = (queries: QueryResults<ComponentTypes, Queries>, deltaTime: number, ecs: ECSpresso<ComponentTypes & Record<string, any>, EventTypes, ResourceTypes>) => void;
89
98
  /**
90
- * Function signature for system lifecycle hooks (onAttach and onDetach)
91
- * @param ecs The ECSpresso instance providing access to all ECS functionality
99
+ * Type for system initialization functions
100
+ * These can be asynchronous
92
101
  */
93
- type LifecycleFunction<ComponentTypes, EventTypes extends Record<string, any>, ResourceTypes extends Record<string, any>> = (ecs: ECSpresso<ComponentTypes & Record<string, any>, EventTypes, ResourceTypes>) => void;
102
+ type LifecycleFunction<ComponentTypes, EventTypes extends Record<string, any>, ResourceTypes extends Record<string, any>> = (ecs: ECSpresso<ComponentTypes & Record<string, any>, EventTypes, ResourceTypes>) => void | Promise<void>;
94
103
  /**
95
104
  * Create a SystemBuilder attached to an ECSpresso instance
96
105
  * Helper function used by ECSpresso.addSystem
package/dist/types.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import ECSpresso from "./ecspresso";
1
2
  export interface Entity<ComponentTypes> {
2
3
  id: number;
3
4
  components: Partial<ComponentTypes>;
@@ -18,6 +19,11 @@ export interface QueryConfig<ComponentTypes, WithComponents extends keyof Compon
18
19
  }
19
20
  export interface System<ComponentTypes, WithComponents extends keyof ComponentTypes = never, WithoutComponents extends keyof ComponentTypes = never, EventTypes extends Record<string, any> = Record<string, any>, ResourceTypes extends Record<string, any> = Record<string, any>> {
20
21
  label: string;
22
+ /**
23
+ * System priority - higher values execute first (default: 0)
24
+ * When systems have the same priority, they execute in registration order
25
+ */
26
+ priority?: number;
21
27
  entityQueries?: {
22
28
  [queryName: string]: QueryConfig<ComponentTypes, WithComponents, WithoutComponents>;
23
29
  };
@@ -29,12 +35,14 @@ export interface System<ComponentTypes, WithComponents extends keyof ComponentTy
29
35
  */
30
36
  process?(queries: {
31
37
  [queryName: string]: Array<FilteredEntity<ComponentTypes, WithComponents, WithoutComponents>>;
32
- } | Array<FilteredEntity<ComponentTypes, WithComponents, WithoutComponents>>, deltaTime: number, ecs: import("./ecspresso").default<ComponentTypes & Record<string, any>, EventTypes, ResourceTypes>): void;
38
+ } | Array<FilteredEntity<ComponentTypes, WithComponents, WithoutComponents>>, deltaTime: number, ecs: ECSpresso<ComponentTypes & Record<string, any>, EventTypes, ResourceTypes>): void;
33
39
  /**
34
- * Lifecycle hook called when the system is attached to the ECS
40
+ * Lifecycle hook called when the system is initialized
41
+ * This is called when ECSpresso.initialize() is invoked, after resources are initialized
42
+ * Use this for one-time initialization that depends on resources
35
43
  * @param ecs The ECSpresso instance providing access to all ECS functionality
36
44
  */
37
- onAttach?(ecs: import("./ecspresso").default<ComponentTypes & Record<string, any>, EventTypes, ResourceTypes>): void;
45
+ onInitialize?(ecs: ECSpresso<ComponentTypes & Record<string, any>, EventTypes, ResourceTypes>): void | Promise<void>;
38
46
  /**
39
47
  * Lifecycle hook called when the system is detached from the ECS
40
48
  * @param ecs The ECSpresso instance providing access to all ECS functionality
@@ -50,7 +58,7 @@ export interface System<ComponentTypes, WithComponents extends keyof ComponentTy
50
58
  * @param data The event data specific to this event type
51
59
  * @param ecs The ECSpresso instance providing access to all ECS functionality
52
60
  */
53
- handler(data: EventTypes[EventName], ecs: import("./ecspresso").default<ComponentTypes & Record<string, any>, EventTypes, ResourceTypes>): void;
61
+ handler(data: EventTypes[EventName], ecs: ECSpresso<ComponentTypes & Record<string, any>, EventTypes, ResourceTypes>): void;
54
62
  };
55
63
  };
56
64
  }
package/package.json CHANGED
@@ -1,10 +1,14 @@
1
1
  {
2
2
  "name": "ecspresso",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "description": "A minimal Entity-Component-System library for typescript and javascript.",
8
+ "sideEffects": false,
9
+ "publishConfig": {
10
+ "registry": "https://npm.pkg.github.com/"
11
+ },
8
12
  "repository": {
9
13
  "type": "git",
10
14
  "url": "https://github.com/david0178418/ecspresso"
@@ -21,10 +25,12 @@
21
25
  ],
22
26
  "devDependencies": {
23
27
  "@types/bun": "latest",
24
- "pixi.js": "^8.9.0"
28
+ "@types/three": "^0.175.0",
29
+ "pixi.js": "^8.9.1",
30
+ "three": "^0.175.0"
25
31
  },
26
32
  "peerDependencies": {
27
- "typescript": "^5.8.2"
33
+ "typescript": "^5.8.3"
28
34
  },
29
35
  "files": [
30
36
  "dist"