@ste_tisci/ecs 0.1.5 → 0.1.7

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
@@ -1,84 +1,89 @@
1
- > [!IMPORTANT]
2
- > Work in progress.
3
-
4
- # ECS in TypeScript
5
-
6
- A minimalistic game engine with zero dependencies based on the **Entity Component System (ECS)** architecture implemented in **TypeScript**
7
-
8
- ## Features
9
-
10
- - **ECS architecture** with single responsability principles (SRP)
11
-
12
- - **SparSet** for ultra-fast entities lookup
13
-
14
- - **Structure of Arrays(SoA)** for optimized memory storage and access
15
-
16
- - **Bitmask** for efficient component queries
17
-
18
- ## Core Components
19
-
20
- - **EntityManager**: Manage creation, destruction and entity recycle. Uses a bitmask to track components of every entity.
21
-
22
- - **ComponentStore**: Generic store that maintains components data in SoA format to maximise cache locality.
23
-
24
- - **ECS**: Central Orchestator that coordinates entity and components, expose an API for queries and CRUD operations
25
-
26
- - **SparseSet**: Data structure that grants O(1) for insertion, removal and lookups of entities.
27
-
28
- ## Technologies
29
-
30
- - TypeScript for type safety
31
- - Data oriented architecture for optimal performance
32
- - Functional pattern without classes
33
-
34
- ```typescript
35
- import { ECS } from '@ste_tisci/ecs';
36
-
37
- // Define the type of the components
38
- const World = ECS<{
39
- Position: { x: number; y: number };
40
- Velocity: { x: number; y: number };
41
- Size: { width: number; height: number };
42
- Sprite: { src: HTMLImageElement };
43
- }>();
44
-
45
- // Define the structure of components that will be converted in SoA Arrays with the types previously defined
46
- // Position = { x: number[], y: number[] }
47
- // Size = { src: HTMLImageElement[] }
48
- // etc.
49
- World.defineComponents({
50
- Position: {},
51
- Size: {},
52
- Velocity: {},
53
- Sprite: {},
54
- });
55
-
56
- const ent = World.createEntity();
57
-
58
- World.addComponent(ent, 'Position', { x: 100, y: 20 });
59
- World.addComponent(ent, 'Velocity', { x: 2, y: 1 });
60
- World.addComponent(ent, 'Size', { width: 20, height: 20 });
61
-
62
- function movementSystem(World) {
63
- const { Position, Velocity } = World.components;
64
-
65
- for (const entity of World.query('Position', 'Velocity')) {
66
- const posID = entity.Position.id;
67
- const velID = entity.Velocity.id;
68
-
69
- Position.x[posID] += Velocity.x[velID];
70
- Position.y[posID] += Velocity.y[velID];
71
- }
72
- }
73
-
74
- function gameLoop() {
75
- movementSystem(World);
76
- requestAnimationFrame(gameLoop);
77
- }
78
-
79
- gameLoop();
80
- ```
81
-
82
- ## ⚖️ License
83
-
84
- MIT © 2025 SteTisci
1
+ > [!IMPORTANT]
2
+ > Work in progress.
3
+
4
+ # ECS in TypeScript
5
+
6
+ A minimalistic game engine with zero dependencies based on the **Entity Component System (ECS)** architecture implemented in **TypeScript**
7
+
8
+ ## Features
9
+
10
+ - **ECS architecture** with single responsability principles (SRP)
11
+
12
+ - **SparSet** for ultra-fast entities lookup
13
+
14
+ - **Structure of Arrays(SoA)** for optimized memory storage and access — numeric fields are backed by `Float32Array`, non-numeric fields by plain arrays
15
+
16
+ - **Bitmask** for efficient component queries
17
+
18
+ ## Core Components
19
+
20
+ - **EntityManager**: Manage creation, destruction and entity recycle. Uses a bitmask to track components of every entity.
21
+
22
+ - **ComponentStore**: Generic store that maintains component data in SoA format to maximise cache locality — numeric fields in a growable `Float32Array`, non-numeric fields (e.g. `HTMLImageElement`) in a plain array.
23
+
24
+ - **ECS**: Central Orchestator that coordinates entity and components, expose an API for queries and CRUD operations
25
+
26
+ - **SparseSet**: Data structure that grants O(1) for insertion, removal and lookups of entities.
27
+
28
+ ## Technologies
29
+
30
+ - TypeScript for type safety
31
+ - Data oriented architecture for optimal performance
32
+ - Functional pattern without classes
33
+
34
+ ```typescript
35
+ import { ECS } from '@ste_tisci/ecs';
36
+
37
+ // Define the type of the components
38
+ const World = ECS<{
39
+ Position: { x: number; y: number };
40
+ Velocity: { x: number; y: number };
41
+ Size: { width: number; height: number };
42
+ Sprite: { src: HTMLImageElement };
43
+ }>();
44
+
45
+ // Define the structure of components that will be converted to SoA storage.
46
+ // Numeric fields become Float32Array, everything else stays a plain array.
47
+ // Position = { x: Float32Array, y: Float32Array }
48
+ // Sprite = { src: HTMLImageElement[] }
49
+ // etc.
50
+ World.defineComponents('Position', 'Velocity', 'Size', 'Sprite');
51
+
52
+ const ent = World.createEntity();
53
+
54
+ World.addComponent(ent, 'Position', { x: 100, y: 20 });
55
+ World.addComponent(ent, 'Velocity', { x: 2, y: 1 });
56
+ World.addComponent(ent, 'Size', { width: 20, height: 20 });
57
+
58
+ function movementSystem(World) {
59
+ const { Position, Velocity } = World.components;
60
+
61
+ for (const entity of World.query('Position', 'Velocity')) {
62
+ const posID = entity.Position.id;
63
+ const velID = entity.Velocity.id;
64
+
65
+ Position.x[posID] += Velocity.x[velID];
66
+ Position.y[posID] += Velocity.y[velID];
67
+ }
68
+ }
69
+
70
+ function gameLoop() {
71
+ movementSystem(World);
72
+ requestAnimationFrame(gameLoop);
73
+ }
74
+
75
+ gameLoop();
76
+ ```
77
+
78
+ ## Error Handling
79
+
80
+ Every operation validates its inputs and throws a descriptive `Error` instead of failing silently or corrupting state:
81
+
82
+ - `addComponent` / `removeComponent` / `destroyEntity` throw if the entity does not exist
83
+ - `addComponent` throws if the entity already has the component; `removeComponent` throws if it doesn't
84
+ - `query`, `addComponent` and `removeComponent` throw if the component name was never registered via `defineComponents`
85
+ - `defineComponents` throws if a component name is already defined
86
+
87
+ ## ⚖️ License
88
+
89
+ MIT © 2025 SteTisci
package/dist/index.cjs CHANGED
@@ -64,18 +64,32 @@ function SparseSet() {
64
64
  }
65
65
 
66
66
  // src/ComponentStore.ts
67
+ var INITIAL_CAPACITY = 8;
68
+ function grow(buffer, requiredIndex) {
69
+ let capacity = buffer.length;
70
+ while (capacity <= requiredIndex) capacity *= 2;
71
+ const grown = new Float32Array(capacity);
72
+ grown.set(buffer);
73
+ return grown;
74
+ }
67
75
  function ComponentStore() {
68
76
  const componentSet = SparseSet();
69
77
  const componentData = {};
78
+ const isNumeric = {};
70
79
  function add(eid, data) {
71
80
  if (componentSet.has(eid)) throw new Error(`Entity ${eid} already has this component`);
72
81
  const ID = componentSet.getSize();
73
82
  componentSet.add(eid);
74
83
  for (const key in data) {
75
- if (!componentData[key]) {
76
- componentData[key] = [];
84
+ const value = data[key];
85
+ if (!(key in componentData)) {
86
+ isNumeric[key] = typeof value === "number";
87
+ componentData[key] = isNumeric[key] ? new Float32Array(INITIAL_CAPACITY) : [];
88
+ }
89
+ if (isNumeric[key] && ID >= componentData[key].length) {
90
+ componentData[key] = grow(componentData[key], ID);
77
91
  }
78
- componentData[key][ID] = data[key];
92
+ componentData[key][ID] = value;
79
93
  }
80
94
  }
81
95
  function remove(eid) {
@@ -89,7 +103,9 @@ function ComponentStore() {
89
103
  }
90
104
  componentSet.remove(eid);
91
105
  for (const key in componentData) {
92
- componentData[key].pop();
106
+ if (!isNumeric[key]) {
107
+ componentData[key].pop();
108
+ }
93
109
  }
94
110
  }
95
111
  function getDense() {
@@ -145,6 +161,7 @@ function EntityManager(registry) {
145
161
  bitMasks[eid] &= ~(1n << BigInt(cid));
146
162
  }
147
163
  function getMask(eid) {
164
+ if (!exists(eid)) throw new Error(`Entity ${eid} does not exist`);
148
165
  return bitMasks[eid];
149
166
  }
150
167
  return { exists, hasComponent, create, remove, addComponent, removeComponent, getMask };
@@ -183,6 +200,7 @@ function ECS() {
183
200
  const components = {};
184
201
  function defineComponents(...names) {
185
202
  for (const name of names) {
203
+ if (name in internalStores) throw new Error(`Component ${String(name)} is already defined`);
186
204
  ComponentRegistry.register(name);
187
205
  internalStores[name] = ComponentStore();
188
206
  components[name] = internalStores[name].getData();
@@ -215,6 +233,10 @@ function ECS() {
215
233
  }
216
234
  function* query(...componentName) {
217
235
  if (componentName.length === 0) return;
236
+ let targetMask = 0n;
237
+ for (const name of componentName) {
238
+ targetMask |= 1n << BigInt(ComponentRegistry.getID(name));
239
+ }
218
240
  let smallestStore = internalStores[componentName[0]];
219
241
  let smallestSize = smallestStore.getSize();
220
242
  for (let i = 1; i < componentName.length; i++) {
@@ -225,10 +247,6 @@ function ECS() {
225
247
  smallestSize = size;
226
248
  }
227
249
  }
228
- let targetMask = 0n;
229
- for (const name of componentName) {
230
- targetMask |= 1n << BigInt(ComponentRegistry.getID(name));
231
- }
232
250
  for (const eid of smallestStore.getDense()) {
233
251
  if ((Entities.getMask(eid) & targetMask) !== targetMask) continue;
234
252
  const result = {};
package/dist/index.d.cts CHANGED
@@ -1,95 +1,97 @@
1
- /**
2
- * Represents component data in Structure of Arrays (SoA) format.
3
- * Each property of the component is stored in a separate array,
4
- * enabling cache-efficient iteration and SIMD operations.
5
- * @template T - The component data type
6
- * @example
7
- * // For Position component: { x: number, y: number }
8
- * // ComponentDataArrays would be: { x: number[], y: number[] }
9
- */
10
- type ComponentDataArrays<T> = {
11
- [K in keyof T]: T[K][];
1
+ /**
2
+ * Represents component data in Structure of Arrays (SoA) format.
3
+ * Each property of the component is stored in a separate array,
4
+ * enabling cache-efficient iteration and SIMD operations.
5
+ * Numeric properties are backed by a Float32Array for a compact, homogeneous
6
+ * memory layout; non-numeric properties fall back to a plain JS array.
7
+ * @template T - The component data type
8
+ * @example
9
+ * // For Position component: { x: number, y: number }
10
+ * // ComponentDataArrays would be: { x: Float32Array, y: Float32Array }
11
+ */
12
+ type ComponentDataArrays<T> = {
13
+ [K in keyof T]: T[K] extends number ? Float32Array : T[K][];
14
+ };
15
+
16
+ /**
17
+ * Maps component names to their data arrays for direct access.
18
+ * Used to expose component data to the user without store methods.
19
+ * @template T - Record type defining all component types
20
+ */
21
+ type StoreDataMap<T> = {
22
+ [K in keyof T]: ComponentDataArrays<T[K]>;
23
+ };
24
+
25
+ /**
26
+ * Type helper for the query result.
27
+ * Creates an object with an entry for each requested component, holding its store index.
28
+ * @template C - Array of the component names requested in the query.
29
+ */
30
+ type QueryResult<C extends readonly PropertyKey[]> = {
31
+ [K in C[number]]: { id: number };
12
32
  };
13
33
 
14
- /**
15
- * Maps component names to their data arrays for direct access.
16
- * Used to expose component data to the user without store methods.
17
- * @template T - Record type defining all component types
18
- */
19
- type StoreDataMap<T> = {
20
- [K in keyof T]: ComponentDataArrays<T[K]>;
21
- };
22
-
23
- /**
24
- * Type helper for the query result.
25
- * Creates an object with eid and all required components typed correctly.
26
- * @template T - Record type with all component types.
27
- * @template C - Array of the component names requested in the query.
28
- */
29
- type QueryResult<T, C extends (keyof T)[]> = {
30
- [K in C[number]]: { id: number };
31
- };
32
-
33
- /**
34
- * Interface for the main ECS registry.
35
- * Provides high-level API for entity-component management,
36
- * coordinating between EntityManager and ComponentStores.
37
- * @template T - Record type defining all component types in the ECS
38
- */
39
- interface IECS<T> {
40
- /**
41
- * Initializes the ECS with component definitions.
42
- * Registers all components and creates their corresponding stores.
43
- * Must be called before any other operations.
44
- * @param names -component names
45
- */
46
- defineComponents<K extends readonly (keyof T)[]>(...names: K): void;
47
-
48
- /**
49
- * Creates a new entity in the ECS.
50
- * @returns The ID of the newly created entity
51
- */
52
- createEntity: () => number;
53
-
54
- /**
55
- * Completely removes an entity and all its components from the ECS.
56
- * Cleans up all component data and entity records.
57
- * @param eid - The entity ID to destroy
58
- */
59
- destroyEntity: (eid: number) => void;
60
-
61
- /**
62
- * Adds a component to an entity with the provided data.
63
- * Updates both the entity's bitmask and the component store.
64
- * @template K - The component name type (key of T)
65
- * @param eid - The entity ID
66
- * @param name - The component name
67
- * @param data - The component data to store
68
- */
69
- addComponent: <K extends keyof T>(eid: number, name: K, data: T[K]) => void;
70
-
71
- /**
72
- * Removes a component from an entity.
73
- * Updates both the entity's bitmask and the component store.
74
- * @template K - The component name type (key of T)
75
- * @param eid - The entity ID
76
- * @param name - The component name
77
- */
78
- removeComponent: <K extends keyof T>(eid: number, name: K) => void;
79
-
80
- /**
81
- * Retrieves all entities that contain the specified set of components.
82
- * Performs a filtered search across component stores and yields each matching entity.
83
- * The returned generator allows efficient iteration without allocating large arrays.
84
- *
85
- * @template C - Array of component names to query for
86
- * @param components - List of component names that the entity must include
87
- * @returns A generator that yields an object for each matching entity,
88
- * containing the entity ID and the related component data
89
- */
90
- query: <C extends (keyof T)[]>(...componentName: C) => Generator<QueryResult<T, C>>;
91
-
92
- components: StoreDataMap<T>;
34
+ /**
35
+ * Interface for the main ECS registry.
36
+ * Provides high-level API for entity-component management,
37
+ * coordinating between EntityManager and ComponentStores.
38
+ * @template T - Record type defining all component types in the ECS
39
+ */
40
+ interface IECS<T> {
41
+ /**
42
+ * Initializes the ECS with component definitions.
43
+ * Registers all components and creates their corresponding stores.
44
+ * Must be called before any other operations.
45
+ * @param names -component names
46
+ * @throws Error if a component name is already defined
47
+ */
48
+ defineComponents<K extends readonly (keyof T)[]>(...names: K): void;
49
+
50
+ /**
51
+ * Creates a new entity in the ECS.
52
+ * @returns The ID of the newly created entity
53
+ */
54
+ createEntity: () => number;
55
+
56
+ /**
57
+ * Completely removes an entity and all its components from the ECS.
58
+ * Cleans up all component data and entity records.
59
+ * @param eid - The entity ID to destroy
60
+ */
61
+ destroyEntity: (eid: number) => void;
62
+
63
+ /**
64
+ * Adds a component to an entity with the provided data.
65
+ * Updates both the entity's bitmask and the component store.
66
+ * @template K - The component name type (key of T)
67
+ * @param eid - The entity ID
68
+ * @param name - The component name
69
+ * @param data - The component data to store
70
+ */
71
+ addComponent: <K extends keyof T>(eid: number, name: K, data: T[K]) => void;
72
+
73
+ /**
74
+ * Removes a component from an entity.
75
+ * Updates both the entity's bitmask and the component store.
76
+ * @template K - The component name type (key of T)
77
+ * @param eid - The entity ID
78
+ * @param name - The component name
79
+ */
80
+ removeComponent: <K extends keyof T>(eid: number, name: K) => void;
81
+
82
+ /**
83
+ * Retrieves all entities that contain the specified set of components.
84
+ * Performs a filtered search across component stores and yields each matching entity.
85
+ * The returned generator allows efficient iteration without allocating large arrays.
86
+ *
87
+ * @template C - Array of component names to query for
88
+ * @param components - List of component names that the entity must include
89
+ * @returns A generator that yields an object for each matching entity,
90
+ * containing the entity ID and the related component data
91
+ */
92
+ query: <C extends (keyof T)[]>(...componentName: C) => Generator<QueryResult<C>>;
93
+
94
+ components: StoreDataMap<T>;
93
95
  }
94
96
 
95
97
  declare function ECS<T extends Record<string, Record<string, any>>>(): IECS<T>;
package/dist/index.d.ts CHANGED
@@ -1,95 +1,97 @@
1
- /**
2
- * Represents component data in Structure of Arrays (SoA) format.
3
- * Each property of the component is stored in a separate array,
4
- * enabling cache-efficient iteration and SIMD operations.
5
- * @template T - The component data type
6
- * @example
7
- * // For Position component: { x: number, y: number }
8
- * // ComponentDataArrays would be: { x: number[], y: number[] }
9
- */
10
- type ComponentDataArrays<T> = {
11
- [K in keyof T]: T[K][];
1
+ /**
2
+ * Represents component data in Structure of Arrays (SoA) format.
3
+ * Each property of the component is stored in a separate array,
4
+ * enabling cache-efficient iteration and SIMD operations.
5
+ * Numeric properties are backed by a Float32Array for a compact, homogeneous
6
+ * memory layout; non-numeric properties fall back to a plain JS array.
7
+ * @template T - The component data type
8
+ * @example
9
+ * // For Position component: { x: number, y: number }
10
+ * // ComponentDataArrays would be: { x: Float32Array, y: Float32Array }
11
+ */
12
+ type ComponentDataArrays<T> = {
13
+ [K in keyof T]: T[K] extends number ? Float32Array : T[K][];
14
+ };
15
+
16
+ /**
17
+ * Maps component names to their data arrays for direct access.
18
+ * Used to expose component data to the user without store methods.
19
+ * @template T - Record type defining all component types
20
+ */
21
+ type StoreDataMap<T> = {
22
+ [K in keyof T]: ComponentDataArrays<T[K]>;
23
+ };
24
+
25
+ /**
26
+ * Type helper for the query result.
27
+ * Creates an object with an entry for each requested component, holding its store index.
28
+ * @template C - Array of the component names requested in the query.
29
+ */
30
+ type QueryResult<C extends readonly PropertyKey[]> = {
31
+ [K in C[number]]: { id: number };
12
32
  };
13
33
 
14
- /**
15
- * Maps component names to their data arrays for direct access.
16
- * Used to expose component data to the user without store methods.
17
- * @template T - Record type defining all component types
18
- */
19
- type StoreDataMap<T> = {
20
- [K in keyof T]: ComponentDataArrays<T[K]>;
21
- };
22
-
23
- /**
24
- * Type helper for the query result.
25
- * Creates an object with eid and all required components typed correctly.
26
- * @template T - Record type with all component types.
27
- * @template C - Array of the component names requested in the query.
28
- */
29
- type QueryResult<T, C extends (keyof T)[]> = {
30
- [K in C[number]]: { id: number };
31
- };
32
-
33
- /**
34
- * Interface for the main ECS registry.
35
- * Provides high-level API for entity-component management,
36
- * coordinating between EntityManager and ComponentStores.
37
- * @template T - Record type defining all component types in the ECS
38
- */
39
- interface IECS<T> {
40
- /**
41
- * Initializes the ECS with component definitions.
42
- * Registers all components and creates their corresponding stores.
43
- * Must be called before any other operations.
44
- * @param names -component names
45
- */
46
- defineComponents<K extends readonly (keyof T)[]>(...names: K): void;
47
-
48
- /**
49
- * Creates a new entity in the ECS.
50
- * @returns The ID of the newly created entity
51
- */
52
- createEntity: () => number;
53
-
54
- /**
55
- * Completely removes an entity and all its components from the ECS.
56
- * Cleans up all component data and entity records.
57
- * @param eid - The entity ID to destroy
58
- */
59
- destroyEntity: (eid: number) => void;
60
-
61
- /**
62
- * Adds a component to an entity with the provided data.
63
- * Updates both the entity's bitmask and the component store.
64
- * @template K - The component name type (key of T)
65
- * @param eid - The entity ID
66
- * @param name - The component name
67
- * @param data - The component data to store
68
- */
69
- addComponent: <K extends keyof T>(eid: number, name: K, data: T[K]) => void;
70
-
71
- /**
72
- * Removes a component from an entity.
73
- * Updates both the entity's bitmask and the component store.
74
- * @template K - The component name type (key of T)
75
- * @param eid - The entity ID
76
- * @param name - The component name
77
- */
78
- removeComponent: <K extends keyof T>(eid: number, name: K) => void;
79
-
80
- /**
81
- * Retrieves all entities that contain the specified set of components.
82
- * Performs a filtered search across component stores and yields each matching entity.
83
- * The returned generator allows efficient iteration without allocating large arrays.
84
- *
85
- * @template C - Array of component names to query for
86
- * @param components - List of component names that the entity must include
87
- * @returns A generator that yields an object for each matching entity,
88
- * containing the entity ID and the related component data
89
- */
90
- query: <C extends (keyof T)[]>(...componentName: C) => Generator<QueryResult<T, C>>;
91
-
92
- components: StoreDataMap<T>;
34
+ /**
35
+ * Interface for the main ECS registry.
36
+ * Provides high-level API for entity-component management,
37
+ * coordinating between EntityManager and ComponentStores.
38
+ * @template T - Record type defining all component types in the ECS
39
+ */
40
+ interface IECS<T> {
41
+ /**
42
+ * Initializes the ECS with component definitions.
43
+ * Registers all components and creates their corresponding stores.
44
+ * Must be called before any other operations.
45
+ * @param names -component names
46
+ * @throws Error if a component name is already defined
47
+ */
48
+ defineComponents<K extends readonly (keyof T)[]>(...names: K): void;
49
+
50
+ /**
51
+ * Creates a new entity in the ECS.
52
+ * @returns The ID of the newly created entity
53
+ */
54
+ createEntity: () => number;
55
+
56
+ /**
57
+ * Completely removes an entity and all its components from the ECS.
58
+ * Cleans up all component data and entity records.
59
+ * @param eid - The entity ID to destroy
60
+ */
61
+ destroyEntity: (eid: number) => void;
62
+
63
+ /**
64
+ * Adds a component to an entity with the provided data.
65
+ * Updates both the entity's bitmask and the component store.
66
+ * @template K - The component name type (key of T)
67
+ * @param eid - The entity ID
68
+ * @param name - The component name
69
+ * @param data - The component data to store
70
+ */
71
+ addComponent: <K extends keyof T>(eid: number, name: K, data: T[K]) => void;
72
+
73
+ /**
74
+ * Removes a component from an entity.
75
+ * Updates both the entity's bitmask and the component store.
76
+ * @template K - The component name type (key of T)
77
+ * @param eid - The entity ID
78
+ * @param name - The component name
79
+ */
80
+ removeComponent: <K extends keyof T>(eid: number, name: K) => void;
81
+
82
+ /**
83
+ * Retrieves all entities that contain the specified set of components.
84
+ * Performs a filtered search across component stores and yields each matching entity.
85
+ * The returned generator allows efficient iteration without allocating large arrays.
86
+ *
87
+ * @template C - Array of component names to query for
88
+ * @param components - List of component names that the entity must include
89
+ * @returns A generator that yields an object for each matching entity,
90
+ * containing the entity ID and the related component data
91
+ */
92
+ query: <C extends (keyof T)[]>(...componentName: C) => Generator<QueryResult<C>>;
93
+
94
+ components: StoreDataMap<T>;
93
95
  }
94
96
 
95
97
  declare function ECS<T extends Record<string, Record<string, any>>>(): IECS<T>;
package/dist/index.js CHANGED
@@ -38,18 +38,32 @@ function SparseSet() {
38
38
  }
39
39
 
40
40
  // src/ComponentStore.ts
41
+ var INITIAL_CAPACITY = 8;
42
+ function grow(buffer, requiredIndex) {
43
+ let capacity = buffer.length;
44
+ while (capacity <= requiredIndex) capacity *= 2;
45
+ const grown = new Float32Array(capacity);
46
+ grown.set(buffer);
47
+ return grown;
48
+ }
41
49
  function ComponentStore() {
42
50
  const componentSet = SparseSet();
43
51
  const componentData = {};
52
+ const isNumeric = {};
44
53
  function add(eid, data) {
45
54
  if (componentSet.has(eid)) throw new Error(`Entity ${eid} already has this component`);
46
55
  const ID = componentSet.getSize();
47
56
  componentSet.add(eid);
48
57
  for (const key in data) {
49
- if (!componentData[key]) {
50
- componentData[key] = [];
58
+ const value = data[key];
59
+ if (!(key in componentData)) {
60
+ isNumeric[key] = typeof value === "number";
61
+ componentData[key] = isNumeric[key] ? new Float32Array(INITIAL_CAPACITY) : [];
62
+ }
63
+ if (isNumeric[key] && ID >= componentData[key].length) {
64
+ componentData[key] = grow(componentData[key], ID);
51
65
  }
52
- componentData[key][ID] = data[key];
66
+ componentData[key][ID] = value;
53
67
  }
54
68
  }
55
69
  function remove(eid) {
@@ -63,7 +77,9 @@ function ComponentStore() {
63
77
  }
64
78
  componentSet.remove(eid);
65
79
  for (const key in componentData) {
66
- componentData[key].pop();
80
+ if (!isNumeric[key]) {
81
+ componentData[key].pop();
82
+ }
67
83
  }
68
84
  }
69
85
  function getDense() {
@@ -119,6 +135,7 @@ function EntityManager(registry) {
119
135
  bitMasks[eid] &= ~(1n << BigInt(cid));
120
136
  }
121
137
  function getMask(eid) {
138
+ if (!exists(eid)) throw new Error(`Entity ${eid} does not exist`);
122
139
  return bitMasks[eid];
123
140
  }
124
141
  return { exists, hasComponent, create, remove, addComponent, removeComponent, getMask };
@@ -157,6 +174,7 @@ function ECS() {
157
174
  const components = {};
158
175
  function defineComponents(...names) {
159
176
  for (const name of names) {
177
+ if (name in internalStores) throw new Error(`Component ${String(name)} is already defined`);
160
178
  ComponentRegistry.register(name);
161
179
  internalStores[name] = ComponentStore();
162
180
  components[name] = internalStores[name].getData();
@@ -189,6 +207,10 @@ function ECS() {
189
207
  }
190
208
  function* query(...componentName) {
191
209
  if (componentName.length === 0) return;
210
+ let targetMask = 0n;
211
+ for (const name of componentName) {
212
+ targetMask |= 1n << BigInt(ComponentRegistry.getID(name));
213
+ }
192
214
  let smallestStore = internalStores[componentName[0]];
193
215
  let smallestSize = smallestStore.getSize();
194
216
  for (let i = 1; i < componentName.length; i++) {
@@ -199,10 +221,6 @@ function ECS() {
199
221
  smallestSize = size;
200
222
  }
201
223
  }
202
- let targetMask = 0n;
203
- for (const name of componentName) {
204
- targetMask |= 1n << BigInt(ComponentRegistry.getID(name));
205
- }
206
224
  for (const eid of smallestStore.getDense()) {
207
225
  if ((Entities.getMask(eid) & targetMask) !== targetMask) continue;
208
226
  const result = {};
package/package.json CHANGED
@@ -1,23 +1,27 @@
1
- {
2
- "name": "@ste_tisci/ecs",
3
- "version": "0.1.5",
4
- "description": "A minimalistic game engine with zero dependencies based on the Entity Component System (ECS) architecture implemented in TypeScript",
5
- "main": "./dist/index.cjs",
6
- "module": "./dist/index.js",
7
- "types": "./dist/index.d.ts",
8
- "scripts": {
9
- "build": "tsup"
10
- },
11
- "keywords": [],
12
- "author": "",
13
- "license": "MIT",
14
- "repository": {
15
- "type": "git",
16
- "url": "https://github.com/SteTisci/ecs"
17
- },
18
- "type": "module",
19
- "devDependencies": {
20
- "tsup": "^8.5.1",
21
- "typescript": "^5.9.3"
22
- }
23
- }
1
+ {
2
+ "name": "@ste_tisci/ecs",
3
+ "version": "0.1.7",
4
+ "description": "A minimalistic game engine with zero dependencies based on the Entity Component System (ECS) architecture implemented in TypeScript",
5
+ "main": "./dist/index.cjs",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "scripts": {
12
+ "build": "tsup",
13
+ "prepublishOnly": "npm run build"
14
+ },
15
+ "keywords": [],
16
+ "author": "",
17
+ "license": "MIT",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/SteTisci/ecs"
21
+ },
22
+ "type": "module",
23
+ "devDependencies": {
24
+ "tsup": "^8.5.1",
25
+ "typescript": "^5.9.3"
26
+ }
27
+ }
package/.prettierrc DELETED
@@ -1,12 +0,0 @@
1
- {
2
- "singleQuote": true,
3
- "printWidth": 150,
4
- "proseWrap": "always",
5
- "tabWidth": 2,
6
- "useTabs": false,
7
- "trailingComma": "all",
8
- "bracketSpacing": true,
9
- "arrowParens": "avoid",
10
- "singleAttributePerLine": false,
11
- "semi": true
12
- }