ecspresso 0.0.7 → 0.1.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,372 +2,263 @@
2
2
 
3
3
  *(pronounced "ex-presso")*
4
4
 
5
- __Note: This is a VERY early work in progress. The documention is being autogenerated while this ECSpresso being iterated on.__
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 this ECSpresso being iterated on.__
6
6
 
7
- A lightweight, type-safe Entity Component System (ECS) library for TypeScript applications and games.
7
+ A type-safe, modular, and extensible Entity Component System (ECS) framework for TypeScript.
8
8
 
9
- ## Overview
9
+ ## Features
10
10
 
11
- ECSpresso is a flexible and efficient Entity Component System implementation that provides:
12
-
13
- - **Type-safe API**: Fully leverages TypeScript's type system for component, event, and resource definitions
14
- - **Event System**: Built-in pub/sub event system for communication between systems
15
- - **Resource Management**: Global state management through a dedicated resource manager
16
- - **Bundle System**: Modular and reusable collections of components, resources, and systems
17
- - **Query System**: Efficient entity filtering based on component presence/absence
18
- - **Flexible System Creation**: Create systems directly or through bundles
19
-
20
- ## Core Concepts
21
-
22
- ### Entity Component System
23
-
24
- The ECS pattern separates data (Components) from behavior (Systems) through Entities:
25
-
26
- - **Entities**: Unique identifiers that components can be attached to
27
- - **Components**: Plain data objects that hold state but no behavior
28
- - **Systems**: Logic that processes entities with specific components
29
- - **Resources**: Global state shared between systems
30
- - **Events**: Messages passed between systems
31
-
32
- ### Key Features
33
-
34
- - Type-safe queries to filter entities based on component combinations
35
- - Fluent builder API for creating systems directly or through bundles
36
- - Event handling with lifecycle hooks for systems
37
- - Resource management for global state
38
- - Simplified system API - all system methods receive the ECSpresso instance
39
-
40
- ### System API Design
41
-
42
- All system methods (process, onAttach, onDetach, and event handlers) receive the ECSpresso instance as a parameter, which provides:
43
-
44
- - Access to entity management via `ecspresso.entityManager`
45
- - Access to resources via `ecspresso.resourceManager`
46
- - Access to events via `ecspresso.eventBus`
47
-
48
- This design simplifies the API and allows systems access to all ECS functionality through a single reference. Benefits of this approach include:
49
-
50
- - **Simpler method signatures**: Systems only need to deal with one additional parameter (the ECS instance) rather than multiple managers
51
- - **Future extensibility**: New functionality added to the ECSpresso class is automatically available to all systems without changing method signatures
52
- - **Consistency**: All system methods (process, lifecycle hooks, event handlers) use the same parameter pattern
53
- - **Reduced verbosity**: Systems can be written more concisely while still having access to all ECS functionality
11
+ - 🔒 **Type-Safe**: Full TypeScript support with type inference for components, events, and resources
12
+ - 🧩 **Modular**: Bundle-based architecture for modular gameplay systems and features
13
+ - 💡 **Flexible**: Easily create entities, add components, and build systems with a clean, fluent API
14
+ - 🔄 **Event-Driven**: Integrated event bus for communication between systems
15
+ - 🗄️ **Resource Management**: Global resources for sharing state across systems
54
16
 
55
17
  ## Installation
56
18
 
57
- ```bash
58
- # Add the library to your project
19
+ ```sh
59
20
  npm install ecspresso
60
21
  ```
61
22
 
62
- ## Usage
63
-
64
- ### Basic Setup
23
+ ## Quick Start
65
24
 
66
25
  ```typescript
67
- import ECSpresso, { Bundle } from 'ecspresso';
26
+ import { ECSpresso } from 'ecspresso';
68
27
 
69
28
  // Define your component types
70
- interface MyComponents {
29
+ interface Components {
71
30
  position: { x: number; y: number };
72
- velocity: { dx: number; dy: number };
73
- sprite: { url: string; width: number; height: number };
31
+ velocity: { x: number; y: number };
32
+ sprite: { url: string };
74
33
  }
75
34
 
76
35
  // Define your event types
77
- interface MyEvents {
78
- collision: { entityA: number; entityB: number };
79
- playerSpawn: { id: number; position: { x: number; y: number } };
36
+ interface Events {
37
+ collision: { entity1: number; entity2: number };
38
+ scoreChange: { amount: number };
80
39
  }
81
40
 
82
41
  // Define your resource types
83
- interface MyResources {
84
- gameState: { score: number; level: number };
85
- config: { debug: boolean; maxEntities: number };
42
+ interface Resources {
43
+ score: { value: number };
44
+ gameState: 'playing' | 'paused' | 'gameOver';
86
45
  }
87
46
 
88
- // Create an ECS instance with your types
89
- const world = new ECSpresso<MyComponents, MyEvents, MyResources>();
47
+ // Create a world instance directly
48
+ const world = new ECSpresso<Components, Events, Resources>();
90
49
 
91
- // Add resources
92
- world.addResource('gameState', { score: 0, level: 1 });
93
- world.addResource('config', { debug: true, maxEntities: 1000 });
50
+ // Add a movement system directly to the world
51
+ world.addSystem('movement')
52
+ .addQuery('movingEntities', {
53
+ with: ['position', 'velocity']
54
+ })
55
+ .setProcess((queries, deltaTime) => {
56
+ for (const entity of queries.movingEntities) {
57
+ entity.components.position.x += entity.components.velocity.x * deltaTime;
58
+ entity.components.position.y += entity.components.velocity.y * deltaTime;
59
+ }
60
+ })
61
+ .build(); // Don't forget to call build() to finalize the system
94
62
 
95
- // Create an entity
63
+ // Create an entity with position and velocity components
96
64
  const entity = world.entityManager.createEntity();
97
-
98
- // Add components to the entity
99
65
  world.entityManager.addComponent(entity.id, 'position', { x: 0, y: 0 });
100
- world.entityManager.addComponent(entity.id, 'velocity', { dx: 1, dy: 2 });
66
+ world.entityManager.addComponent(entity.id, 'velocity', { x: 10, y: 5 });
101
67
 
102
- // Run the simulation
103
- world.update(16.67); // Pass delta time in ms
104
- ```
68
+ // Run a single update
69
+ world.update(1/60);
105
70
 
106
- ### Creating Systems Directly
107
-
108
- ECSpresso allows you to create systems directly on the ECSpresso instance:
109
-
110
- ```typescript
111
- // Create a movement system directly on the ECSpresso instance
112
- world
113
- .addSystem('movement')
114
- .addQuery('movable', {
115
- with: ['position', 'velocity']
116
- })
117
- .setProcess((queries, deltaTime, ecs) => {
118
- // Process entities with both position and velocity
119
- for (const entity of queries.movable) {
120
- const { position, velocity } = entity.components;
121
- position.x += velocity.dx * (deltaTime / 1000);
122
- position.y += velocity.dy * (deltaTime / 1000);
123
- }
124
- })
125
- .build(); // Important: Call build() to finalize the system
71
+ // Check new position
72
+ const position = world.entityManager.getComponent(entity.id, 'position');
73
+ console.log(position); // { x: 0.16666..., y: 0.08333... }
126
74
  ```
127
75
 
128
- The `build()` method finalizes the system and registers it with the ECSpresso instance. Any lifecycle hooks like `onAttach` will be called immediately when the system is built.
129
-
130
- ### System Lifecycle Hooks
76
+ ## Building Modular Systems with Bundles
131
77
 
132
- You can add lifecycle hooks to systems that will be called when the system is attached or detached:
78
+ Bundles are a powerful way to organize game features:
133
79
 
134
80
  ```typescript
135
- // Create a rendering system with lifecycle hooks
136
- world
81
+ // Create a player input bundle
82
+ const inputBundle = new Bundle<Components, Events, Resources>()
83
+ .addSystem('playerInput')
84
+ .setProcess((_queries, _deltaTime, ecs) => {
85
+ // Handle keyboard input and modify player velocity
86
+ // ...
87
+ });
88
+
89
+ // Create a rendering bundle
90
+ const renderBundle = new Bundle<Components, Events, Resources>()
137
91
  .addSystem('renderer')
138
- .addQuery('sprites', {
139
- with: ['position', 'sprite']
140
- })
141
- .setOnAttach((ecs) => {
142
- console.log('Render system attached');
143
- // Initialize rendering resources
144
- })
145
- .setOnDetach((ecs) => {
146
- console.log('Render system detached');
147
- // Clean up rendering resources
148
- })
149
- .setProcess((queries, deltaTime, ecs) => {
150
- // Render all sprites at their positions
92
+ .addQuery('sprites', { with: ['position', 'sprite'] })
93
+ .setProcess((queries) => {
94
+ // Render all sprites
151
95
  for (const entity of queries.sprites) {
152
- const { position, sprite } = entity.components;
153
- // Render sprite at position
96
+ // Draw entities at their positions
97
+ // ...
154
98
  }
155
- })
156
- .build();
157
- ```
158
-
159
- ### Event Handling
160
-
161
- Systems can handle events emitted by other systems:
99
+ });
162
100
 
163
- ```typescript
164
- // Create a system that handles collision events
165
- world
166
- .addSystem('score')
101
+ // Create a scoring bundle that adds a resource and listens for events
102
+ const scoringBundle = new Bundle<Components, Events, Resources>()
103
+ .addResource('score', { value: 0 })
104
+ .addSystem('scoreKeeper')
167
105
  .setEventHandlers({
168
- collision: {
169
- handler: (event, ecs) => {
170
- // Handle collision event
171
- const gameState = ecs.getResourceOrThrow('gameState');
172
- gameState.score += 10;
173
-
174
- console.log(`Score: ${gameState.score}`);
106
+ scoreChange: {
107
+ handler: (data, ecs) => {
108
+ const score = ecs.getResource('score');
109
+ score.value += data.amount;
110
+ console.log(`Score: ${score.value}`);
175
111
  }
176
112
  }
177
- })
178
- .build();
113
+ });
179
114
 
180
- // Emit an event from another system
181
- world.eventBus.publish('collision', { entityA: entity.id, entityB: 2 });
115
+ // Create the game world with all features using the builder pattern
116
+ const game = ECSpresso.create<Components, Events, Resources>()
117
+ .withBundle(physicsBundle)
118
+ .withBundle(inputBundle)
119
+ .withBundle(renderBundle)
120
+ .withBundle(scoringBundle)
121
+ .build();
182
122
  ```
183
123
 
184
- ### Using Bundles for System Organization
124
+ ## Type Safety with the Builder Pattern
185
125
 
186
- Bundles allow you to group related systems, resources, and components:
126
+ ECSpresso uses a builder pattern to provide strong type checking for bundle compatibility:
187
127
 
188
128
  ```typescript
189
- // Create a physics bundle
190
- const physicsBundle = new Bundle<MyComponents, MyEvents, MyResources>('physics');
191
-
192
- // Add a system to the bundle
193
- const collisionSystem = physicsBundle
194
- .addSystem('collision')
195
- .addQuery('collidable', {
196
- with: ['position']
197
- })
198
- .setProcess((queries, deltaTime, ecs) => {
199
- // Check for collisions between entities
200
- for (const entity of queries.collidable) {
201
- // Collision detection logic
202
- // ...
203
-
204
- // Emit collision events when detected
205
- ecs.eventBus.publish('collision', {
206
- entityA: entity.id,
207
- entityB: otherEntity.id
208
- });
209
- }
210
- });
129
+ // 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}}, {}, {}>();
132
+
133
+ // Create a world with both bundles - TypeScript will allow this
134
+ const world = ECSpresso.create()
135
+ .withBundle(bundle1)
136
+ .withBundle(bundle2)
137
+ .build();
211
138
 
212
- // Access the bundle for chaining
213
- physicsBundle
214
- .addSystem('movement')
215
- .addQuery('movable', {
216
- with: ['position', 'velocity']
217
- })
218
- .setProcess((queries, deltaTime, ecs) => {
219
- for (const entity of queries.movable) {
220
- const { position, velocity } = entity.components;
221
- position.x += velocity.dx * (deltaTime / 1000);
222
- position.y += velocity.dy * (deltaTime / 1000);
223
- }
224
- })
225
- .bundle // Access bundle for chaining to add another system
226
- .addSystem('friction')
227
- .addQuery('moving', {
228
- with: ['velocity']
229
- })
230
- .setProcess((queries, deltaTime, ecs) => {
231
- for (const entity of queries.moving) {
232
- const { velocity } = entity.components;
233
- // Apply friction
234
- velocity.dx *= 0.99;
235
- velocity.dy *= 0.99;
236
- }
237
- });
139
+ // These bundles have conflicting component types
140
+ const bundle3 = new Bundle<{position: {x: number, y: number}}, {}, {}>();
141
+ const bundle4 = new Bundle<{position: string}, {}, {}>();
238
142
 
239
- // Install the bundle to add all systems to the ECSpresso instance
240
- world.install(physicsBundle);
143
+ // TypeScript will show an error because bundles have conflicting types
144
+ const world2 = ECSpresso.create()
145
+ .withBundle(bundle3)
146
+ // @ts-expect-error - TypeScript will flag this because the position types conflict
147
+ .withBundle(bundle4)
148
+ .build();
241
149
  ```
242
150
 
243
- With bundles, you don't need to call `build()` on the systems. The systems are built when the bundle is installed.
244
-
245
- ### System Chaining with ECSpresso
246
-
247
- When adding systems directly to ECSpresso, you can access the ECSpresso instance for chaining:
151
+ ## Working with Entities and Components
248
152
 
249
153
  ```typescript
250
- // Create a system and access the ECSpresso instance for chaining
251
- world
252
- .addSystem('playerSystem')
253
- .addQuery('players', {
254
- with: ['position', 'velocity']
255
- })
256
- .setProcess((queries, deltaTime, ecs) => {
257
- // Process player entities
258
- })
259
- .build()
260
- .ecspresso // Access the ECSpresso instance after building
261
- .addSystem('enemySystem')
262
- .addQuery('enemies', {
263
- with: ['position', 'velocity']
264
- })
265
- .setProcess((queries, deltaTime, ecs) => {
266
- // Process enemy entities
267
- })
154
+ const world = ECSpresso.create<Components, Events, Resources>()
155
+ .withBundle(/* your bundle */)
268
156
  .build();
269
- ```
270
157
 
271
- ### Merging Bundles
158
+ // Create an entity
159
+ const entity = world.entityManager.createEntity();
272
160
 
273
- For larger applications, you can organize systems into multiple bundles and merge them:
161
+ // Add components individually
162
+ world.entityManager.addComponent(entity.id, 'position', { x: 0, y: 0 });
163
+ world.entityManager.addComponent(entity.id, 'velocity', { x: 0, y: 0 });
274
164
 
275
- ```typescript
276
- import { mergeBundles } from 'ecspresso';
277
-
278
- // Create individual feature bundles
279
- const physicsBundle = new Bundle<MyComponents, MyEvents, MyResources>('physics');
280
- const renderBundle = new Bundle<MyComponents, MyEvents, MyResources>('render');
281
- const aiBundle = new Bundle<MyComponents, MyEvents, MyResources>('ai');
282
-
283
- // Add systems to each bundle
284
- // ...
285
-
286
- // Merge bundles into a game bundle
287
- const gameBundle = mergeBundles(
288
- 'game',
289
- physicsBundle,
290
- renderBundle,
291
- aiBundle
292
- );
293
-
294
- // Install the merged bundle
295
- world.install(gameBundle);
296
- ```
165
+ // Add multiple components at once
166
+ world.entityManager.addComponents(entity, {
167
+ position: { x: 10, y: 20 },
168
+ velocity: { x: 5, y: -2 }
169
+ });
297
170
 
298
- ## Advanced Features
171
+ // Get component data
172
+ const position = world.entityManager.getComponent(entity.id, 'position');
299
173
 
300
- ### Removing Systems
174
+ // Check if an entity has a component
175
+ const hasPosition = world.entityManager.hasComponent(entity.id, 'position');
301
176
 
302
- You can remove systems by their label:
177
+ // Remove a component
178
+ world.entityManager.removeComponent(entity.id, 'velocity');
303
179
 
304
- ```typescript
305
- // Remove a system
306
- const removed = world.removeSystem('movement');
307
- console.log(`System removed: ${removed}`); // true if system was found and removed
180
+ // Remove an entity (and all its components)
181
+ world.entityManager.removeEntity(entity.id);
308
182
  ```
309
183
 
310
- When a system is removed, its `onDetach` lifecycle hook is called if defined.
311
-
312
- ### Adding Resources through Bundles
184
+ ## Working with Systems
313
185
 
314
- Bundles can also contain resources:
186
+ Systems can be added directly to an ECSpresso instance:
315
187
 
316
188
  ```typescript
317
- // Create a bundle with resources
318
- const uiBundle = new Bundle<MyComponents, MyEvents, MyResources>('ui');
319
-
320
- // Add resources to the bundle
321
- uiBundle.addResource('uiState', {
322
- menuOpen: false,
323
- currentScreen: 'main'
324
- });
325
-
326
- // Add systems to the bundle
327
- uiBundle
328
- .addSystem('uiRenderer')
329
- // ...
189
+ const world = ECSpresso.create<Components, Events, Resources>()
190
+ .build();
330
191
 
331
- // Install the bundle to add both resources and systems
332
- world.install(uiBundle);
192
+ world.addSystem('physicsSystem')
193
+ .addQuery('movingEntities', {
194
+ with: ['position', 'velocity']
195
+ })
196
+ .setProcess((queries, deltaTime) => {
197
+ for (const entity of queries.movingEntities) {
198
+ // Update positions based on velocity
199
+ entity.components.position.x += entity.components.velocity.x * deltaTime;
200
+ entity.components.position.y += entity.components.velocity.y * deltaTime;
201
+ }
202
+ })
203
+ .build(); // Finalizes and adds the system to the world
333
204
  ```
334
205
 
335
- ## TypeScript Integration
206
+ ## Event System
336
207
 
337
- ECSpresso is designed to provide strong type safety. The type parameters for components, events, and resources flow through the entire API, ensuring type correctness at compile time.
208
+ The event system allows communication between systems:
338
209
 
339
210
  ```typescript
340
- // Properly typed queries and component access
341
- world
342
- .addSystem('typeSafeSystem')
343
- .addQuery('entities', {
344
- with: ['position', 'velocity']
345
- })
346
- .setProcess((queries, deltaTime, ecs) => {
347
- for (const entity of queries.entities) {
348
- // TypeScript knows these components exist and their types
349
- const position = entity.components.position; // { x: number, y: number }
350
- const velocity = entity.components.velocity; // { dx: number, dy: number }
351
-
352
- // This would cause a TypeScript error - sprite not guaranteed to exist
353
- // const sprite = entity.components.sprite;
211
+ // Define an event handler in a system
212
+ const collisionBundle = new Bundle<Components, Events, Resources>()
213
+ .addSystem('collisionResponse')
214
+ .setEventHandlers({
215
+ collision: {
216
+ handler: (data, ecs) => {
217
+ // Handle collision event
218
+ // data contains entity1 and entity2 from the event
219
+ }
354
220
  }
355
- })
221
+ });
222
+
223
+ const world = ECSpresso.create<Components, Events, Resources>()
224
+ .withBundle(collisionBundle)
356
225
  .build();
357
- ```
358
226
 
359
- ## Development
227
+ // Publish an event from anywhere
228
+ world.eventBus.publish('collision', {
229
+ entity1: 1,
230
+ entity2: 2
231
+ });
360
232
 
361
- To install dependencies:
233
+ // Subscribe to events manually (outside of systems)
234
+ const unsubscribe = world.eventBus.subscribe('collision', (data) => {
235
+ console.log(`Collision between entities ${data.entity1} and ${data.entity2}`);
236
+ });
362
237
 
363
- ```bash
364
- bun install
238
+ // Stop listening
239
+ unsubscribe();
365
240
  ```
366
241
 
367
- To run tests:
242
+ ## Resources
243
+
244
+ Resources provide global state accessible to all systems:
368
245
 
369
- ```bash
370
- bun test
246
+ ```typescript
247
+ // Add a resource
248
+ world.addResource('score', { value: 0 });
249
+
250
+ // Get a resource
251
+ const score = world.getResource('score');
252
+ score.value += 10;
253
+
254
+ // Check if a resource exists
255
+ const hasScore = world.hasResource('score');
371
256
  ```
372
257
 
373
- This project was created using `bun init` in bun v1.2.4. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
258
+ ## API Documentation
259
+
260
+ For detailed API documentation, please see the [API Reference](./docs/api.md).
261
+
262
+ ## License
263
+
264
+ MIT
package/dist/bundle.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { SystemBuilder } from './system-builder';
2
+ import type ECSpresso from './ecspresso';
2
3
  /**
3
4
  * Bundle class that encapsulates a set of components, resources, events, and systems
4
5
  * that can be merged into a ECSpresso instance
@@ -31,7 +32,12 @@ export default class Bundle<ComponentTypes extends Record<string, any> = Record<
31
32
  * Get all systems defined in this bundle
32
33
  * Returns built System objects instead of SystemBuilders
33
34
  */
34
- getSystems(): import("./types").System<ComponentTypes, any, any, EventTypes, ResourceTypes>[];
35
+ getSystems(): SystemBuilder<ComponentTypes, EventTypes, ResourceTypes, any>[];
36
+ /**
37
+ * Register all systems in this bundle with an ECSpresso instance
38
+ * @internal Used by ECSpresso when adding a bundle
39
+ */
40
+ registerSystemsWithEcspresso(ecspresso: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>): void;
35
41
  /**
36
42
  * Get all resources defined in this bundle
37
43
  */
@@ -53,15 +59,17 @@ export default class Bundle<ComponentTypes extends Record<string, any> = Record<
53
59
  */
54
60
  hasResource<K extends keyof ResourceTypes>(key: K): boolean;
55
61
  }
62
+ type Exactly<T, U> = T extends U ? U extends T ? true : false : false;
63
+ type IncompatibleBundles<C1 extends Record<string, any>, C2 extends Record<string, any>, E1 extends Record<string, any>, E2 extends Record<string, any>, R1 extends Record<string, any>, R2 extends Record<string, any>> = {
64
+ [K in keyof C1 & keyof C2]: Exactly<C1[K], C2[K]> extends false ? never : unknown;
65
+ } & {
66
+ [K in keyof E1 & keyof E2]: Exactly<E1[K], E2[K]> extends false ? never : unknown;
67
+ } & {
68
+ [K in keyof R1 & keyof R2]: Exactly<R1[K], R2[K]> extends false ? never : unknown;
69
+ };
56
70
  /**
57
- * Utility type for merging two types
71
+ * Function that merges multiple bundles into a single bundle
58
72
  */
59
- export type Merge<T1, T2> = T1 & T2;
60
- export type MergeAll<T extends any[]> = T extends [infer First, ...infer Rest] ? Rest extends [] ? First : Merge<First, MergeAll<Rest>> : {};
61
- export declare function mergeBundles<Bundles extends Array<Bundle<any, any, any>>>(id: string, ...bundles: Bundles): Bundle<MergeAll<{
62
- [K in keyof Bundles]: Bundles[K] extends Bundle<infer C, any, any> ? C : never;
63
- }>, MergeAll<{
64
- [K in keyof Bundles]: Bundles[K] extends Bundle<any, infer E, any> ? E : never;
65
- }>, MergeAll<{
66
- [K in keyof Bundles]: Bundles[K] extends Bundle<any, any, infer R> ? R : never;
67
- }>>;
73
+ export declare function mergeBundles<C1 extends Record<string, any>, E1 extends Record<string, any>, R1 extends Record<string, any>, C2 extends Record<string, any>, E2 extends Record<string, any>, R2 extends Record<string, any>>(id: string, bundle1: Bundle<C1, E1, R1>, bundle2: Bundle<C2, E2, R2> & IncompatibleBundles<C1, C2, E1, E2, R1, R2>): Bundle<C1 & C2, E1 & E2, R1 & R2>;
74
+ export declare function mergeBundles<ComponentTypes extends Record<string, any>, EventTypes extends Record<string, any>, ResourceTypes extends Record<string, any>>(id: string, ...bundles: Array<Bundle<ComponentTypes, EventTypes, ResourceTypes>>): Bundle<ComponentTypes, EventTypes, ResourceTypes>;
75
+ export {};
@@ -3,33 +3,104 @@ import EventBus from "./event-bus";
3
3
  import ResourceManager from "./resource-manager";
4
4
  import type Bundle from "./bundle";
5
5
  /**
6
- * The main ECS (Entity Component System) container class
7
- *
8
- * This class manages entities, components, systems, resources, and events.
9
- *
10
- * Systems interact with the ECS through a single parameter that provides access
11
- * to the entire ECSpresso instance. This provides a simplified API for systems
12
- * and allows them access to all ECS functionality through a single reference.
13
- *
14
- * @template ComponentTypes Record of component types used in this ECS instance
15
- * @template EventTypes Record of event types used in this ECS instance
16
- * @template ResourceTypes Record of resource types used in this ECS instance
6
+ * Type helper to detect conflicting types between two record types.
7
+ * Returns a union of keys that exist in both T and U but have incompatible types.
8
+ */
9
+ type GetConflictingKeys<T, U> = {
10
+ [K in keyof T & keyof U]: T[K] extends U[K] ? U[K] extends T[K] ? never : K : K;
11
+ }[keyof T & keyof U];
12
+ /**
13
+ * Simplified type helper to check bundle type compatibility.
14
+ * Returns true if bundles can be merged without type conflicts.
15
+ */
16
+ type BundlesAreCompatible<C1 extends Record<string, any>, C2 extends Record<string, any>, E1 extends Record<string, any>, E2 extends Record<string, any>, R1 extends Record<string, any>, R2 extends Record<string, any>> = [
17
+ keyof C1
18
+ ] extends [never] ? [keyof E1] extends [never] ? [keyof R1] extends [never] ? true : GetConflictingKeys<R1, R2> extends never ? true : false : GetConflictingKeys<E1, E2> extends never ? GetConflictingKeys<R1, R2> extends never ? true : false : false : GetConflictingKeys<C1, C2> extends never ? GetConflictingKeys<E1, E2> extends never ? GetConflictingKeys<R1, R2> extends never ? true : false : false : false;
19
+ /**
20
+ * Interface declaration for ECSpresso constructor to ensure type augmentation works properly.
21
+ * This merges with the class declaration below.
22
+ */
23
+ export default interface ECSpresso<ComponentTypes extends Record<string, any> = Record<string, any>, EventTypes extends Record<string, any> = Record<string, any>, ResourceTypes extends Record<string, any> = Record<string, any>> {
24
+ /**
25
+ * Default constructor
26
+ */
27
+ new (): ECSpresso<ComponentTypes, EventTypes, ResourceTypes>;
28
+ }
29
+ /**
30
+ * Static methods on the ECSpresso class
31
+ */
32
+ export default interface ECSpresso {
33
+ /**
34
+ * Create a new ECSpresso builder with type-safe bundle installation.
35
+ * This is the preferred way to create an ECSpresso instance with bundles.
36
+ *
37
+ * Example:
38
+ * ```typescript
39
+ * const ecs = ECSpresso.create<MyComponents, MyEvents, MyResources>()
40
+ * .withBundle(bundle1)
41
+ * .withBundle(bundle2)
42
+ * .build();
43
+ * ```
44
+ */
45
+ create(): ECSpressoBuilder<{}, {}, {}>;
46
+ /**
47
+ * Create a new ECSpresso builder with type-safe bundle installation and explicit starting types.
48
+ */
49
+ create<BaseC extends Record<string, any>, BaseE extends Record<string, any>, BaseR extends Record<string, any>>(): ECSpressoBuilder<BaseC, BaseE, BaseR>;
50
+ }
51
+ /**
52
+ * ECSpresso is the central ECS framework class that connects all features.
53
+ * It handles creation and management of entities, components, and systems, and provides lifecycle hooks.
17
54
  */
18
55
  export default class ECSpresso<ComponentTypes extends Record<string, any> = Record<string, any>, EventTypes extends Record<string, any> = Record<string, any>, ResourceTypes extends Record<string, any> = Record<string, any>> {
56
+ /** Library version */
19
57
  static readonly VERSION: string;
58
+ /** Access/modify stored components and entities */
20
59
  private _entityManager;
21
- private _systems;
60
+ /** Publish/subscribe to events */
22
61
  private _eventBus;
62
+ /** Access/modify registered resources */
23
63
  private _resourceManager;
64
+ /** Registered systems that will be updated in order */
65
+ private _systems;
66
+ /** Track installed bundles to prevent duplicates */
24
67
  private _installedBundles;
68
+ /**
69
+ * Creates a new ECSpresso instance.
70
+ */
25
71
  constructor();
26
72
  /**
27
- * Install one or more bundles into this ECS instance
28
- * Systems in the bundle will have their onAttach method called with this ECSpresso instance
29
- * @param bundles One or more bundles to install
30
- * @returns This ECSpresso instance for method chaining
73
+ * Creates a new ECSpresso builder for type-safe bundle installation.
74
+ * This is the preferred way to create an ECSpresso instance with bundles.
75
+ *
76
+ * @returns A builder instance for fluent method chaining
77
+ *
78
+ * @example
79
+ * ```typescript
80
+ * const ecs = ECSpresso.create<BaseComponents, BaseEvents, BaseResources>()
81
+ * .withBundle(bundle1)
82
+ * .withBundle(bundle2)
83
+ * .build();
84
+ * ```
85
+ */
86
+ static create<C extends Record<string, any> = {}, E extends Record<string, any> = {}, R extends Record<string, any> = {}>(): ECSpressoBuilder<C, E, R>;
87
+ /**
88
+ * Adds a system directly to this ECSpresso instance
89
+ * @param label Unique name to identify the system
90
+ * @returns A SystemBuilder instance for method chaining
91
+ */
92
+ addSystem(label: string): import("./system-builder").SystemBuilderWithEcspresso<ComponentTypes, EventTypes, ResourceTypes, {}>;
93
+ /**
94
+ * Update all systems, passing deltaTime and query results to each system's process function
95
+ * @param deltaTime Time elapsed since the last update (in seconds)
31
96
  */
32
- install<Bundles extends Array<Bundle<any, any, any> | null>>(...bundles: Bundles): this;
97
+ update(deltaTime: number): void;
98
+ /**
99
+ * Internal method to install a bundle into this ECSpresso instance.
100
+ * Called by the ECSpressoBuilder during the build process.
101
+ * The type safety is guaranteed by the builder's type system.
102
+ */
103
+ _installBundle<C extends Record<string, any>, E extends Record<string, any>, R extends Record<string, any>>(bundle: Bundle<C, E, R>): this;
33
104
  /**
34
105
  * Remove a system by its label
35
106
  * Calls the system's onDetach method with this ECSpresso instance if defined
@@ -44,7 +115,7 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = Reco
44
115
  /**
45
116
  * Get a resource if it exists, or undefined if not
46
117
  */
47
- getResource<K extends keyof ResourceTypes>(key: K): ResourceTypes[K] | undefined;
118
+ getResource<K extends keyof ResourceTypes>(key: K): ResourceTypes[K];
48
119
  /**
49
120
  * Get a resource, throws error if not found
50
121
  */
@@ -62,22 +133,34 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = Reco
62
133
  */
63
134
  getEntitiesWithComponents(withComponents: (keyof ComponentTypes)[], withoutComponents?: (keyof ComponentTypes)[]): import("./types").FilteredEntity<ComponentTypes, keyof ComponentTypes extends infer T ? T extends keyof ComponentTypes ? T extends never ? never : T : never : never, keyof ComponentTypes extends infer T_1 ? T_1 extends keyof ComponentTypes ? T_1 extends never ? never : T_1 : never : never>[];
64
135
  /**
65
- * Update all systems
66
- * Calls each system's process method with this ECSpresso instance
67
- * @param deltaTime Time elapsed since the last update in seconds
136
+ * Get all installed bundle IDs
68
137
  */
69
- update(deltaTime: number): void;
138
+ get installedBundles(): string[];
70
139
  get entityManager(): EntityManager<ComponentTypes>;
71
140
  get eventBus(): EventBus<EventTypes>;
72
141
  get resourceManager(): ResourceManager<ResourceTypes>;
142
+ }
143
+ /**
144
+ * Builder class for ECSpresso that provides fluent type-safe bundle installation.
145
+ * Handles type checking during build process to ensure type safety.
146
+ */
147
+ export declare class ECSpressoBuilder<C extends Record<string, any> = {}, E extends Record<string, any> = {}, R extends Record<string, any> = {}> {
148
+ /** The ECSpresso instance being built */
149
+ private ecspresso;
150
+ constructor();
73
151
  /**
74
- * Get all installed bundle IDs
152
+ * Add the first bundle when starting with empty types.
153
+ * This overload allows any bundle to be added to an empty ECSpresso instance.
75
154
  */
76
- get installedBundles(): string[];
155
+ withBundle<BC extends Record<string, any>, BE extends Record<string, any>, BR extends Record<string, any>>(this: ECSpressoBuilder<{}, {}, {}>, bundle: Bundle<BC, BE, BR>): ECSpressoBuilder<BC, BE, BR>;
77
156
  /**
78
- * Add a system directly to this ECSpresso instance
79
- * @param label Unique identifier for the system
80
- * @returns A SystemBuilder instance for method chaining
157
+ * Add a subsequent bundle with type checking.
158
+ * This overload enforces bundle type compatibility.
81
159
  */
82
- addSystem(label: string): import("./system-builder").SystemBuilderWithEcspresso<ComponentTypes, EventTypes, ResourceTypes, {}>;
160
+ withBundle<BC extends Record<string, any>, BE extends Record<string, any>, BR extends Record<string, any>>(bundle: BundlesAreCompatible<C, BC, E, BE, R, BR> extends true ? Bundle<BC, BE, BR> : never): ECSpressoBuilder<C & BC, E & BE, R & BR>;
161
+ /**
162
+ * Complete the build process and return the built ECSpresso instance
163
+ */
164
+ build(): ECSpresso<C, E, R>;
83
165
  }
166
+ export {};
@@ -5,9 +5,17 @@ export default class EntityManager<ComponentTypes> {
5
5
  private componentIndices;
6
6
  createEntity(): Entity<ComponentTypes>;
7
7
  addComponent<ComponentName extends keyof ComponentTypes>(entityOrId: number | Entity<ComponentTypes>, componentName: ComponentName, data: ComponentTypes[ComponentName]): this;
8
+ /**
9
+ * Add multiple components to an entity at once
10
+ * @param entityOrId Entity or entity ID to add components to
11
+ * @param components Object with component names as keys and component data as values
12
+ */
13
+ addComponents<T extends {
14
+ [K in keyof ComponentTypes]?: ComponentTypes[K];
15
+ }>(entityOrId: number | Entity<ComponentTypes>, components: T & Record<Exclude<keyof T, keyof ComponentTypes>, never>): this;
8
16
  removeComponent<ComponentName extends keyof ComponentTypes>(entityId: number, componentName: ComponentName): void;
9
17
  getComponent<ComponentName extends keyof ComponentTypes>(entityId: number, componentName: ComponentName): ComponentTypes[ComponentName] | null;
10
18
  getEntitiesWithComponents<WithComponents extends keyof ComponentTypes = never, WithoutComponents extends keyof ComponentTypes = never>(required?: ReadonlyArray<WithComponents>, excluded?: ReadonlyArray<WithoutComponents>): Array<FilteredEntity<ComponentTypes, WithComponents extends never ? never : WithComponents, WithoutComponents extends never ? never : WithoutComponents>>;
11
- removeEntity(entityId: number): boolean;
19
+ removeEntity(entityOrId: number | Entity<ComponentTypes>): boolean;
12
20
  getEntity(entityId: number): Entity<ComponentTypes> | undefined;
13
21
  }
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- class L{nextId=1;entities=new Map;componentIndices=new Map;createEntity(){let g=this.nextId++,j={id:g,components:{}};return this.entities.set(g,j),j}addComponent(g,j,f){let J=typeof g==="number"?this.entities.get(g):g;if(!J)throw new Error(`Entity ${g} does not exist`);if(J.components[j]=f,!this.componentIndices.has(j))this.componentIndices.set(j,new Set);return this.componentIndices.get(j)?.add(J.id),this}removeComponent(g,j){let f=this.entities.get(g);if(!f)throw new Error(`Entity ${g} does not exist`);delete f.components[j],this.componentIndices.get(j)?.delete(g)}getComponent(g,j){let f=this.entities.get(g);if(!f)throw new Error(`Entity ${g} does not exist`);return f.components[j]||null}getEntitiesWithComponents(g=[],j=[]){if(g.length===0){if(j.length===0)return Array.from(this.entities.values());return Array.from(this.entities.values()).filter((D)=>{return j.every((F)=>!(F in D.components))})}let f=g.reduce((D,F)=>{let K=this.componentIndices.get(F),V=K?K.size:0,Y=this.componentIndices.get(D)?.size??1/0;return V<Y?F:D},g[0]);return Array.from(this.componentIndices.get(f)||[]).filter((D)=>{let F=this.entities.get(D);return F&&g.every((K)=>(K in F.components))&&j.every((K)=>!(K in F.components))}).map((D)=>this.entities.get(D))}removeEntity(g){let j=this.entities.get(g);if(!j)return!1;for(let f of Object.keys(j.components))this.componentIndices.get(f)?.delete(g);return this.entities.delete(g)}getEntity(g){return this.entities.get(g)}}class M{handlers=new Map;subscribe(g,j){return this.addHandler(g,j,!1)}once(g,j){return this.addHandler(g,j,!0)}addHandler(g,j,f){if(!this.handlers.has(g))this.handlers.set(g,[]);let J={callback:j,once:f};return this.handlers.get(g).push(J),()=>{let D=this.handlers.get(g);if(D){let F=D.indexOf(J);if(F!==-1)D.splice(F,1)}}}publish(g,j=void 0){let f=this.handlers.get(g);if(!f)return;let J=[...f],D=[];for(let F of J)if(F.callback(j),F.once)D.push(F);if(D.length>0)for(let F of D){let K=f.indexOf(F);if(K!==-1)f.splice(K,1)}}clear(){this.handlers.clear()}clearEvent(g){this.handlers.delete(g)}}class P{resources=new Map;add(g,j){return this.resources.set(g,j),this}get(g){let j=this.resources.get(g);if(j===void 0)throw new Error(`Resource ${String(g)} not found`);return j}getOptional(g){return this.resources.get(g)}has(g){return this.resources.has(g)}remove(g){return this.resources.delete(g)}getKeys(){return Array.from(this.resources.keys())}}class W{_label;_ecspresso;_bundle;queries={};processFunction;attachFunction;detachFunction;eventHandlers;constructor(g,j=null,f=null){this._label=g;this._ecspresso=j;this._bundle=f}get label(){return this._label}get bundle(){return this._bundle}get ecspresso(){return this._ecspresso}addQuery(g,j){let f=this;return f.queries={...this.queries,[g]:j},f}setProcess(g){return this.processFunction=g,this}setOnAttach(g){return this.attachFunction=g,this}setOnDetach(g){return this.detachFunction=g,this}setEventHandlers(g){return this.eventHandlers=g,this}build(){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&&!this._bundle){let j=g;if(this._ecspresso._systems.push(j),j.onAttach)j.onAttach(this._ecspresso);if(j.eventHandlers)for(let f in j.eventHandlers){let J=j.eventHandlers[f];if(J?.handler){let D=(F)=>{J.handler(F,this._ecspresso)};this._ecspresso.eventBus.subscribe(f,D)}}}return g}}function Z(g,j){return new W(g,j)}function _(g,j){return new W(g,null,j)}var $="0.0.7";class X{static VERSION=$;_entityManager;_systems=[];_eventBus;_resourceManager;_installedBundles=new Set;constructor(){this._entityManager=new L,this._eventBus=new M,this._resourceManager=new P}install(...g){for(let j of g){if(!j)return this;if(this._installedBundles.has(j.id)){console.warn(`Bundle ${j.id} is already installed`);continue}let f=j.getSystems();if(!f.length)console.warn(`Bundle ${j.id} has no systems`);for(let D of f){let F=D;if(this._systems.push(F),F.onAttach)F.onAttach(this);if(F.eventHandlers)for(let K in F.eventHandlers){let V=F.eventHandlers[K];if(V?.handler){let Y=(G)=>{V.handler(G,this)};this._eventBus.subscribe(K,Y)}}}let J=j.getResources();for(let[D,F]of J.entries())this._resourceManager.add(D,F);this._installedBundles.add(j.id)}return this}removeSystem(g){let j=this._systems.findIndex((J)=>J.label===g);if(j===-1)return!1;let f=this._systems[j];if(!f)return!1;return f.onDetach?.(this),this._systems.splice(j,1),!0}hasResource(g){return this._resourceManager.has(g)}getResource(g){return this._resourceManager.getOptional(g)}getResourceOrThrow(g){return this._resourceManager.get(g)}addResource(g,j){return this._resourceManager.add(g,j),this}hasComponent(g,j){return this._entityManager.getComponent(g,j)!==null}getEntitiesWithComponents(g,j=[]){return this._entityManager.getEntitiesWithComponents(g,j)}update(g){for(let j of this._systems){if(!j.process)continue;let f={};if(j.entityQueries){for(let J in j.entityQueries){let D=j.entityQueries[J];if(D)f[J]=this._entityManager.getEntitiesWithComponents(D.with,D.without||[])}j.process(f,g,this)}else j.process([],g,this)}}get entityManager(){return this._entityManager}get eventBus(){return this._eventBus}get resourceManager(){return this._resourceManager}get installedBundles(){return Array.from(this._installedBundles)}addSystem(g){return Z(g,this)}}function x(){return`bundle_${Date.now().toString(36)}_${Math.random().toString(36).substring(2,9)}`}class Q{_systems=[];_resources=new Map;_id;constructor(g){this._id=g||x()}get id(){return this._id}set id(g){this._id=g}addSystem(g){let j=_(g,this);return this._systems.push(j),j}addResource(g,j){return this._resources.set(g,j),this}getSystems(){return this._systems.map((g)=>g.build())}getResources(){return new Map(this._resources)}getResource(g){return this._resources.get(g)}getSystemBuilders(){return[...this._systems]}hasResource(g){return this._resources.has(g)}}function z(g,...j){if(j.length===0)return new Q(g);let f=new Q(g);for(let J of j){for(let D of J.getSystemBuilders())f.addSystem(D);for(let[D,F]of J.getResources().entries())f.addResource(D,F)}return f}var r=X;export{z as mergeBundles,r as default,W as SystemBuilder,P as ResourceManager,M as EventBus,L as EntityManager,Q as Bundle};
1
+ class M{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 F=typeof j==="number"?this.entities.get(j):j;if(!F)throw new Error(`Entity ${j} does not exist`);if(F.components[g]=f,!this.componentIndices.has(g))this.componentIndices.set(g,new Set);return this.componentIndices.get(g)?.add(F.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 F in g)this.addComponent(f,F,g[F]);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((D)=>{return g.every((J)=>!(J in D.components))})}let f=j.reduce((D,J)=>{let L=this.componentIndices.get(J),G=L?L.size:0,H=this.componentIndices.get(D)?.size??1/0;return G<H?J:D},j[0]);return Array.from(this.componentIndices.get(f)||[]).filter((D)=>{let J=this.entities.get(D);return J&&j.every((L)=>(L in J.components))&&g.every((L)=>!(L in J.components))}).map((D)=>this.entities.get(D))}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 P{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 F={callback:g,once:f};return this.handlers.get(j).push(F),()=>{let D=this.handlers.get(j);if(D){let J=D.indexOf(F);if(J!==-1)D.splice(J,1)}}}publish(j,g){let f=this.handlers.get(j);if(!f)return;let F=[...f],D=[];for(let J of F)if(J.callback(g),J.once)D.push(J);if(D.length>0)for(let J of D){let L=f.indexOf(J);if(L!==-1)f.splice(L,1)}}clear(){this.handlers.clear()}clearEvent(j){this.handlers.delete(j)}}class Q{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 X{_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)Y(g,this._ecspresso);if(j)Y(g,j);return this}}function Y(j,g){if(g._systems.push(j),j.onAttach)j.onAttach(g);if(j.eventHandlers)for(let f in j.eventHandlers){let F=j.eventHandlers[f];if(F?.handler){let D=(J)=>{F.handler(J,g)};g.eventBus.subscribe(f,D)}}}function Z(j,g){return new X(j,g)}function _(j,g){return new X(j,null,g)}var $="0.1.0";class U{static VERSION=$;_entityManager;_eventBus;_resourceManager;_systems=[];_installedBundles=new Set;constructor(){this._entityManager=new M,this._eventBus=new P,this._resourceManager=new Q}static create(){return new x}addSystem(j){return Z(j,this)}update(j){for(let g of this._systems){if(!g.process)continue;let f={};if(g.entityQueries)for(let F in g.entityQueries){let D=g.entityQueries[F];if(D)f[F]=this._entityManager.getEntitiesWithComponents(D.with,D.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,F]of g.entries())this._resourceManager.add(f,F);return this}removeSystem(j){let g=this._systems.findIndex((F)=>F.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){return this._resourceManager.getOptional(j)}getResourceOrThrow(j){return this._resourceManager.get(j)}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 x{ecspresso;constructor(){this.ecspresso=new U}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 V{_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=_(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 V(j);let f=new V(j);for(let F of g){for(let D of F.getSystemBuilders())f.addSystem(D);for(let[D,J]of F.getResources().entries())f.addResource(D,J)}return f}var o=U;export{A as mergeBundles,o as default,X as SystemBuilder,Q as ResourceManager,P as EventBus,M as EntityManager,V as Bundle};
2
2
 
3
- //# debugId=4D710EFEE1AA387364756E2164756E21
3
+ //# debugId=6DF4D2A1DC5A0C4864756E2164756E21
4
4
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -2,15 +2,15 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/entity-manager.ts", "../src/event-bus.ts", "../src/resource-manager.ts", "../src/system-builder.ts", "../src/ecspresso.ts", "../src/bundle.ts", "../src/index.ts"],
4
4
  "sourcesContent": [
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\t\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\t\t\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\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\t\t\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> = [] as any, \n\t\texcluded: ReadonlyArray<WithoutComponents> = [] as any,\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\t\t\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\t\t\t\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(entityId: number): boolean {\n\t\tconst entity = this.entities.get(entityId);\n\t\tif (!entity) return false;\n\t\t\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(entityId);\n\t\t}\n\t\t\n\t\t// Remove the entity itself\n\t\treturn this.entities.delete(entityId);\n\t}\n\n\tgetEntity(entityId: number): Entity<ComponentTypes> | undefined {\n\t\treturn this.entities.get(entityId);\n\t}\n}\n",
6
- "import type { EventHandler } from \"./types\";\n\nexport default\nclass EventBus<EventTypes> {\n\tprivate handlers: Map<keyof EventTypes, 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)) {\n\t\t\tthis.handlers.set(eventType, []);\n\t\t}\n\n\t\tconst handler: EventHandler<EventTypes[E]> = {\n\t\t\tcallback,\n\t\t\tonce\n\t\t};\n\n\t\tthis.handlers.get(eventType)!.push(handler);\n\n\t\t// Return unsubscribe function\n\t\treturn () => {\n\t\t\tconst handlers = this.handlers.get(eventType);\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] = undefined as EventTypes[E]\n\t): void {\n\t\tconst handlers = this.handlers.get(eventType);\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\t\t\n\t\t// Call all handlers and collect handlers to remove\n\t\tconst handlersToRemove: EventHandler<any>[] = [];\n\t\t\n\t\tfor (const handler of handlersToCall) {\n\t\t\thandler.callback(data);\n\t\t\tif (handler.once) {\n\t\t\t\thandlersToRemove.push(handler);\n\t\t\t}\n\t\t}\n\t\t\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);\n\t}\n}\n",
7
- "export default\nclass ResourceManager<ResourceTypes extends Record<string, any> = Record<string, any>> {\n\tprivate resources: Map<keyof ResourceTypes, ResourceTypes[keyof ResourceTypes]> = 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>(label: K, resource: ResourceTypes[K]) {\n\t\tthis.resources.set(label, 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>(label: K): ResourceTypes[K] {\n\t\tconst resource = this.resources.get(label);\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;\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>(label: K): ResourceTypes[K] | undefined {\n\t\tconst resource = this.resources.get(label);\n\t\treturn resource as ResourceTypes[K] | 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>(label: K): boolean {\n\t\treturn this.resources.has(label);\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>(label: K): boolean {\n\t\treturn this.resources.delete(label);\n\t}\n\n\t/**\n\t * Get all resource keys\n\t * @returns Array of resource keys\n\t */\n\tgetKeys(): Array<keyof ResourceTypes> {\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<\n\t\t\t\t\t\tComponentTypes & Record<string, any>,\n\t\t\t\t\t\tEventTypes,\n\t\t\t\t\t\tResourceTypes\n\t\t\t\t\t>\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() {\n\t\tconst system: System<ComponentTypes, any, any, EventTypes, ResourceTypes> = {\n\t\t\tlabel: this._label,\n\t\t\tentityQueries: this.queries as any,\n\t\t};\n\n\t\tif (this.processFunction) {\n\t\t\tsystem.process = this.processFunction as any;\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\t// If this system is being built directly from an ECSpresso instance (not via a bundle),\n\t\t// then register it with the ECSpresso instance\n\t\tif (this._ecspresso && !this._bundle) {\n\t\t\tconst typedSystem = system as System<ComponentTypes, any, any, EventTypes, ResourceTypes>;\n\t\t\tthis._ecspresso[\"_systems\"].push(typedSystem);\n\n\t\t\t// Call onAttach lifecycle hook if defined\n\t\t\tif (typedSystem.onAttach) {\n\t\t\t\ttypedSystem.onAttach(this._ecspresso);\n\t\t\t}\n\n\t\t\t// Auto-subscribe to events if eventHandlers are defined\n\t\t\tif (typedSystem.eventHandlers) {\n\t\t\t\tfor (const eventName in typedSystem.eventHandlers) {\n\t\t\t\t\tconst handler = typedSystem.eventHandlers[eventName];\n\t\t\t\t\tif (handler?.handler) {\n\t\t\t\t\t\t// Create a wrapper that passes the additional parameters to the handler\n\t\t\t\t\t\tconst wrappedHandler = (data: any) => {\n\t\t\t\t\t\t\thandler.handler(data, this._ecspresso!);\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tthis._ecspresso.eventBus.subscribe(eventName, wrappedHandler);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn system;\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 } from \"./types\";\nimport type Bundle from \"./bundle\";\nimport { createEcspressoSystemBuilder } from \"./system-builder\";\nimport { version } from \"../package.json\";\n\n\n/**\n * The main ECS (Entity Component System) container class\n *\n * This class manages entities, components, systems, resources, and events.\n *\n * Systems interact with the ECS through a single parameter that provides access\n * to the entire ECSpresso instance. This provides a simplified API for systems\n * and allows them access to all ECS functionality through a single reference.\n *\n * @template ComponentTypes Record of component types used in this ECS instance\n * @template EventTypes Record of event types used in this ECS instance\n * @template ResourceTypes Record of resource types used in this ECS instance\n */\nexport default\nclass ECSpresso<\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\tpublic static readonly VERSION = version;\n\tprivate _entityManager: EntityManager<ComponentTypes>;\n\tprivate _systems: System<ComponentTypes, any, any, EventTypes, ResourceTypes>[] = [];\n\tprivate _eventBus: EventBus<EventTypes>;\n\tprivate _resourceManager: ResourceManager<ResourceTypes>;\n\tprivate _installedBundles: Set<string> = new Set();\n\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 * Install one or more bundles into this ECS instance\n\t * Systems in the bundle will have their onAttach method called with this ECSpresso instance\n\t * @param bundles One or more bundles to install\n\t * @returns This ECSpresso instance for method chaining\n\t */\n\tinstall<\n\t\tBundles extends Array<Bundle<any, any, any> | null>\n\t>(...bundles: Bundles): this {\n\t\tfor (const bundle of bundles) {\n\t\t\tif (!bundle) return this;\n\n\t\t\t// Check if this bundle is already installed\n\t\t\tif (this._installedBundles.has(bundle.id)) {\n\t\t\t\tconsole.warn(`Bundle ${bundle.id} is already installed`);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst systems = bundle.getSystems();\n\n\t\t\t// Check if bundle has systems\n\t\t\tif (!systems.length) {\n\t\t\t\tconsole.warn(`Bundle ${bundle.id} has no systems`);\n\t\t\t}\n\n\t\t\t// Register all systems from the bundle\n\t\t\tfor (const system of systems) {\n\t\t\t\t// Need to cast here because we can't fully type the system generics\n\t\t\t\tconst typedSystem = system as unknown as System<ComponentTypes, any, any, EventTypes, ResourceTypes>;\n\t\t\t\tthis._systems.push(typedSystem);\n\n\t\t\t\t// Call onAttach lifecycle hook if defined\n\t\t\t\tif (typedSystem.onAttach) {\n\t\t\t\t\ttypedSystem.onAttach(\n\t\t\t\t\t\tthis\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// Auto-subscribe to events if eventHandlers are defined\n\t\t\t\tif (typedSystem.eventHandlers) {\n\t\t\t\t\tfor (const eventName in typedSystem.eventHandlers) {\n\t\t\t\t\t\tconst handler = typedSystem.eventHandlers[eventName];\n\t\t\t\t\t\tif (handler?.handler) {\n\t\t\t\t\t\t\t// Create a wrapper that passes the additional parameters to the handler\n\t\t\t\t\t\t\tconst wrappedHandler = (data: any) => {\n\t\t\t\t\t\t\t\thandler.handler(\n\t\t\t\t\t\t\t\t\tdata,\n\t\t\t\t\t\t\t\t\tthis\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\tthis._eventBus.subscribe(eventName, wrappedHandler);\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// Register all resources from the bundle\n\t\t\tconst resources = bundle.getResources();\n\t\t\tfor (const [key, value] of resources.entries()) {\n\t\t\t\t// We need to cast here because TypeScript can't verify the type compatibility\n\t\t\t\t// between bundles, but we trust that the bundle's resource types are compatible\n\t\t\t\tthis._resourceManager.add(key as unknown as keyof ResourceTypes, value as any);\n\t\t\t}\n\n\t\t\t// Mark this bundle as installed\n\t\t\tthis._installedBundles.add(bundle.id);\n\t\t}\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Remove a system by its label\n\t * Calls the system's onDetach method with this ECSpresso instance if defined\n\t * @param label The unique label of the system to remove\n\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\tif (!system) return false;\n\n\t\tsystem.onDetach?.(\n\t\t\tthis\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 * 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 * Get a resource if it exists, or undefined if not\n\t */\n\tgetResource<K extends keyof ResourceTypes>(key: K): ResourceTypes[K] | undefined {\n\t\treturn this._resourceManager.getOptional(key);\n\t}\n\n\t/**\n\t * Get a resource, throws error if not found\n\t */\n\tgetResourceOrThrow<K extends keyof ResourceTypes>(key: K): ResourceTypes[K] {\n\t\treturn this._resourceManager.get(key);\n\t}\n\n\t/**\n\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 * 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 * Get all entities with specific components\n\t */\n\tgetEntitiesWithComponents(\n\t\twithComponents: (keyof ComponentTypes)[],\n\t\twithoutComponents: (keyof ComponentTypes)[] = []\n\t) {\n\t\treturn this._entityManager.getEntitiesWithComponents(\n\t\t\twithComponents,\n\t\t\twithoutComponents\n\t\t);\n\t}\n\n\t/**\n\t * Update all systems\n\t * Calls each system's process method with this ECSpresso instance\n\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\n\t\t\tconst queryResults: Record<string, any[]> = {};\n\n\t\t\t// Process entity queries if defined\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\n\t\t\t\t// Call the system's process method\n\t\t\t\tsystem.process(\n\t\t\t\t\tqueryResults,\n\t\t\t\t\tdeltaTime,\n\t\t\t\t\tthis\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\t// No queries defined, pass an empty array\n\t\t\t\tsystem.process(\n\t\t\t\t\t[],\n\t\t\t\t\tdeltaTime,\n\t\t\t\t\tthis\n\t\t\t\t);\n\t\t\t}\n\t\t}\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 * Get all installed bundle IDs\n\t */\n\tget installedBundles(): string[] {\n\t\treturn Array.from(this._installedBundles);\n\t}\n\n\t/**\n\t * Add a system directly to this ECSpresso instance\n\t * @param label Unique identifier for the system\n\t * @returns A SystemBuilder instance for method chaining\n\t */\n\taddSystem(label: string) {\n\t\tconst system = createEcspressoSystemBuilder<ComponentTypes, EventTypes, ResourceTypes>(label, this);\n\n\t\treturn system;\n\t}\n}\n",
10
- "import { createBundleSystemBuilder, SystemBuilder } from './system-builder';\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 * 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/**\n * Utility type for merging two types\n */\n// This sets props with the same name but different type to \"never\". Maybe we want this?\nexport type Merge<T1, T2> = T1 & T2;\n// This makes the later prop types override the earlier ones. Maybe we want this instead?\n// export type Merge<T1, T2> = Omit<T1, keyof T2> & T2;\n// Or maybe this, which sets props with the same name to a union of the two types\n// export type Merge<T1, T2> = {\n// \t[K in keyof T1 | keyof T2]: K extends keyof T1 & keyof T2\n// \t\t? T1[K] | T2[K]\n// \t\t: K extends keyof T1\n// \t\t\t? T1[K]\n// \t\t\t: K extends keyof T2\n// \t\t\t\t? T2[K]\n// \t\t\t\t: never;\n// };\n\nexport type MergeAll<T extends any[]> = T extends [infer First, ...infer Rest] ?\n\tRest extends [] ?\n\t\tFirst: Merge<First, MergeAll<Rest>>:\n\t{};\n\nexport function mergeBundles<\n\tBundles extends Array<Bundle<any, any, any>>\n>(\n\tid: string,\n\t...bundles: Bundles\n): Bundle<\n\tMergeAll<{ [K in keyof Bundles]: Bundles[K] extends Bundle<infer C, any, any> ? C : never }>,\n\tMergeAll<{ [K in keyof Bundles]: Bundles[K] extends Bundle<any, infer E, any> ? E : never }>,\n\tMergeAll<{ [K in keyof Bundles]: Bundles[K] extends Bundle<any, any, infer R> ? R : never }>\n> {\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 as any);\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 as any, resource);\n\t\t}\n\t}\n\n\treturn combined as any;\n}\n",
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
+ "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<\n\t\t\t\t\t\tComponentTypes & Record<string, any>,\n\t\t\t\t\t\tEventTypes,\n\t\t\t\t\t\tResourceTypes\n\t\t\t\t\t>\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\t// Call onAttach lifecycle hook if defined\n\tif (system.onAttach) {\n\t\tsystem.onAttach(ecspresso);\n\t}\n\n\t// Auto-subscribe to events if eventHandlers are defined\n\tif (system.eventHandlers) {\n\t\tfor (const eventName in system.eventHandlers) {\n\t\t\tconst handler = system.eventHandlers[eventName];\n\t\t\tif (handler?.handler) {\n\t\t\t\t// Create a wrapper that passes the additional parameters to the handler\n\t\t\t\tconst wrappedHandler = (data: any) => {\n\t\t\t\t\thandler.handler(data, ecspresso);\n\t\t\t\t};\n\n\t\t\t\tecspresso.eventBus.subscribe(eventName, wrappedHandler);\n\t\t\t}\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 } from \"./types\";\nimport type Bundle from \"./bundle\";\nimport { createEcspressoSystemBuilder } from \"./system-builder\";\nimport { version } from \"../package.json\";\n\n/**\n * Type helper to detect conflicting types between two record types.\n * Returns a union of keys that exist in both T and U but have incompatible types.\n */\ntype GetConflictingKeys<T, U> = {\n [K in keyof T & keyof U]: T[K] extends U[K]\n ? U[K] extends T[K]\n ? never\n : K\n : K\n}[keyof T & keyof U];\n\n/**\n * Simplified type helper to check bundle type compatibility.\n * Returns true if bundles can be merged without type conflicts.\n */\ntype BundlesAreCompatible<\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 // If all base types are empty, any bundle is compatible\n [keyof C1] extends [never]\n ? [keyof E1] extends [never]\n ? [keyof R1] extends [never]\n ? true\n : GetConflictingKeys<R1, R2> extends never ? true : false\n : GetConflictingKeys<E1, E2> extends never\n ? GetConflictingKeys<R1, R2> extends never ? true : false\n : false\n : GetConflictingKeys<C1, C2> extends never\n ? GetConflictingKeys<E1, E2> extends never\n ? GetConflictingKeys<R1, R2> extends never\n ? true\n : false\n : false\n : false;\n\n/**\n * Interface declaration for ECSpresso constructor to ensure type augmentation works properly.\n * This merges with the class declaration below.\n */\nexport default interface ECSpresso<\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\t/**\n\t * Default constructor\n\t */\n\tnew(): ECSpresso<ComponentTypes, EventTypes, ResourceTypes>;\n}\n\n/**\n * Static methods on the ECSpresso class\n */\nexport default interface ECSpresso {\n /**\n * Create a new ECSpresso builder with type-safe bundle installation.\n * This is the preferred way to create an ECSpresso instance with bundles.\n *\n * Example:\n * ```typescript\n * const ecs = ECSpresso.create<MyComponents, MyEvents, MyResources>()\n * .withBundle(bundle1)\n * .withBundle(bundle2)\n * .build();\n * ```\n */\n create(): ECSpressoBuilder<{}, {}, {}>; // No type parameters - returns a builder with empty types\n\n /**\n * Create a new ECSpresso builder with type-safe bundle installation and explicit starting types.\n */\n create<\n BaseC extends Record<string, any>,\n BaseE extends Record<string, any>,\n BaseR extends Record<string, any>\n >(): ECSpressoBuilder<BaseC, BaseE, BaseR>;\n}\n\n/**\n * ECSpresso is the central ECS framework class that connects all features.\n * 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> = Record<string, any>,\n\tEventTypes extends Record<string, any> = Record<string, any>,\n\tResourceTypes extends Record<string, any> = 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 * 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 * Creates a new ECSpresso builder for type-safe bundle installation.\n\t * This is the preferred way to create an ECSpresso instance with bundles.\n\t *\n\t * @returns A builder instance for fluent method chaining\n\t *\n\t * @example\n\t * ```typescript\n\t * const ecs = ECSpresso.create<BaseComponents, BaseEvents, BaseResources>()\n\t * .withBundle(bundle1)\n\t * .withBundle(bundle2)\n\t * .build();\n\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 * Adds a system directly to this ECSpresso instance\n\t * @param label Unique name to identify the system\n\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 * Update all systems, passing deltaTime and query results to each system's process function\n\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 * Internal method to install a bundle into this ECSpresso instance.\n\t * Called by the ECSpressoBuilder during the build process.\n\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 * Remove a system by its label\n\t * Calls the system's onDetach method with this ECSpresso instance if defined\n\t * @param label The unique label of the system to remove\n\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 * 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 * Get a resource if it exists, or undefined if not\n\t */\n\tgetResource<K extends keyof ResourceTypes>(key: K): ResourceTypes[K] {\n\t\treturn this._resourceManager.getOptional(key) as ResourceTypes[K];\n\t}\n\n\t/**\n\t * Get a resource, throws error if not found\n\t */\n\tgetResourceOrThrow<K extends keyof ResourceTypes>(key: K): ResourceTypes[K] {\n\t\treturn this._resourceManager.get(key) as ResourceTypes[K];\n\t}\n\n\t/**\n\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 * 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 * Get all entities with specific components\n\t */\n\tgetEntitiesWithComponents(\n\t\twithComponents: (keyof ComponentTypes)[],\n\t\twithoutComponents: (keyof ComponentTypes)[] = []\n\t) {\n\t\treturn this._entityManager.getEntitiesWithComponents(\n\t\t\twithComponents,\n\t\t\twithoutComponents\n\t\t);\n\t}\n\n\t/**\n\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 * Builder class for ECSpresso that provides fluent type-safe bundle installation.\n * Handles type checking during build process to ensure type safety.\n */\nexport class ECSpressoBuilder<\n C extends Record<string, any> = {},\n E extends Record<string, any> = {},\n R extends Record<string, any> = {}\n> {\n /** The ECSpresso instance being built */\n private ecspresso: ECSpresso<C, E, R>;\n\n constructor() {\n this.ecspresso = new ECSpresso<C, E, R>();\n }\n\n /**\n * Add the first bundle when starting with empty types.\n * This overload allows any bundle to be added to an empty ECSpresso instance.\n */\n withBundle<\n BC extends Record<string, any>,\n BE extends Record<string, any>,\n BR extends Record<string, any>\n >(\n this: ECSpressoBuilder<{}, {}, {}>,\n bundle: Bundle<BC, BE, BR>\n ): ECSpressoBuilder<BC, BE, BR>;\n\n /**\n * Add a subsequent bundle with type checking.\n * This overload enforces bundle type compatibility.\n */\n withBundle<\n BC extends Record<string, any>,\n BE extends Record<string, any>,\n BR extends Record<string, any>\n >(\n bundle: BundlesAreCompatible<C, BC, E, BE, R, BR> extends true\n ? Bundle<BC, BE, BR>\n : never\n ): ECSpressoBuilder<C & BC, E & BE, R & BR>;\n\n /**\n * Implementation of both overloads.\n * Since the type compatibility is checked in the method signature,\n * we can safely assume the bundle is compatible here.\n */\n withBundle<\n BC extends Record<string, any>,\n BE extends Record<string, any>,\n BR extends Record<string, any>\n >(\n bundle: Bundle<BC, BE, BR>\n ): ECSpressoBuilder<C & BC, E & BE, R & BR> {\n // Install the bundle\n // Type compatibility is guaranteed by method overloads\n this.ecspresso._installBundle(bundle as any);\n\n // Return a builder with the updated type parameters\n return this as unknown as ECSpressoBuilder<C & BC, E & BE, R & BR>;\n }\n\n /**\n * Complete the build process and return the built ECSpresso instance\n */\n build(): ECSpresso<C, E, R> {\n return this.ecspresso;\n }\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",
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": "4D710EFEE1AA387364756E2164756E21",
13
+ "mappings": "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",
14
+ "debugId": "6DF4D2A1DC5A0C4864756E2164756E21",
15
15
  "names": []
16
16
  }
@@ -6,35 +6,35 @@ export default class ResourceManager<ResourceTypes extends Record<string, any> =
6
6
  * @param resource The resource value
7
7
  * @returns The resource manager instance for chaining
8
8
  */
9
- add<K extends keyof ResourceTypes>(label: K, resource: ResourceTypes[K]): this;
9
+ add<K extends keyof ResourceTypes | string>(label: K, resource: K extends keyof ResourceTypes ? ResourceTypes[K] : any): this;
10
10
  /**
11
11
  * Get a resource from the manager
12
12
  * @param label The resource key
13
13
  * @returns The resource value
14
14
  * @throws Error if resource not found
15
15
  */
16
- get<K extends keyof ResourceTypes>(label: K): ResourceTypes[K];
16
+ get<K extends keyof ResourceTypes | string>(label: K): K extends keyof ResourceTypes ? ResourceTypes[K] : any;
17
17
  /**
18
18
  * Get a resource from the manager, returning undefined if not found
19
19
  * @param label The resource key
20
20
  * @returns The resource value or undefined if not found
21
21
  */
22
- getOptional<K extends keyof ResourceTypes>(label: K): ResourceTypes[K] | undefined;
22
+ getOptional<K extends keyof ResourceTypes | string>(label: K): K extends keyof ResourceTypes ? ResourceTypes[K] : any | undefined;
23
23
  /**
24
24
  * Check if a resource exists
25
25
  * @param label The resource key
26
26
  * @returns True if the resource exists
27
27
  */
28
- has<K extends keyof ResourceTypes>(label: K): boolean;
28
+ has<K extends keyof ResourceTypes | string>(label: K): boolean;
29
29
  /**
30
30
  * Remove a resource
31
31
  * @param label The resource key
32
32
  * @returns True if the resource was removed
33
33
  */
34
- remove<K extends keyof ResourceTypes>(label: K): boolean;
34
+ remove<K extends keyof ResourceTypes | string>(label: K): boolean;
35
35
  /**
36
36
  * Get all resource keys
37
37
  * @returns Array of resource keys
38
38
  */
39
- getKeys(): Array<keyof ResourceTypes>;
39
+ getKeys(): Array<string>;
40
40
  }
@@ -64,8 +64,14 @@ export declare class SystemBuilder<ComponentTypes extends Record<string, any> =
64
64
  /**
65
65
  * Build the final system object
66
66
  */
67
- build(): System<ComponentTypes, any, any, EventTypes, ResourceTypes>;
67
+ build(ecspresso?: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>): this;
68
68
  }
69
+ /**
70
+ * Helper function to register a system with an ECSpresso instance
71
+ * This handles attaching the system and setting up event handlers
72
+ * @internal Used by SystemBuilder and Bundle
73
+ */
74
+ export declare function registerSystemWithEcspresso<ComponentTypes extends Record<string, any>, EventTypes extends Record<string, any>, ResourceTypes extends Record<string, any>>(system: System<ComponentTypes, any, any, EventTypes, ResourceTypes>, ecspresso: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>): void;
69
75
  type QueryDefinition<ComponentTypes, WithComponents extends keyof ComponentTypes = any, WithoutComponents extends keyof ComponentTypes = any> = {
70
76
  with: ReadonlyArray<WithComponents>;
71
77
  without?: ReadonlyArray<WithoutComponents>;
package/dist/types.d.ts CHANGED
@@ -54,3 +54,8 @@ export interface System<ComponentTypes, WithComponents extends keyof ComponentTy
54
54
  };
55
55
  };
56
56
  }
57
+ /**
58
+ * Utility type for merging two types
59
+ */
60
+ export type Merge<T1, T2> = T1 & T2;
61
+ export type MergeAll<T extends any[]> = T extends [infer First, ...infer Rest] ? Rest extends [] ? First : Merge<First, MergeAll<Rest>> : {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ecspresso",
3
- "version": "0.0.7",
3
+ "version": "0.1.0",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -20,7 +20,8 @@
20
20
  "ecs"
21
21
  ],
22
22
  "devDependencies": {
23
- "@types/bun": "latest"
23
+ "@types/bun": "latest",
24
+ "pixi.js": "^8.9.0"
24
25
  },
25
26
  "peerDependencies": {
26
27
  "typescript": "^5"
@@ -33,7 +34,8 @@
33
34
  "build:ts": "bun tsc -p tsconfig.build.json",
34
35
  "build:js": "bun build --target=browser --sourcemap=linked --minify --outdir=dist src/index.ts",
35
36
  "build": "bun build:clean && bun build:ts && bun build:js",
36
- "prepublishOnly": "bun run build"
37
+ "examples": "bun ./examples/serve-examples.ts",
38
+ "prepublishOnly": "bun tsc && bun test && bun run build"
37
39
  },
38
40
  "type": "module"
39
41
  }