@ste_tisci/ecs 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 ADDED
@@ -0,0 +1,36 @@
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
+ ## ⚖️ License
35
+
36
+ MIT © 2025 SteTisci
package/dist/index.cjs ADDED
@@ -0,0 +1,247 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ ECS: () => ECS
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/utils/SparseSet.ts
28
+ function SparseSet() {
29
+ const sparse = [];
30
+ const dense = [];
31
+ let size = 0;
32
+ function has(eid) {
33
+ const ID = getIndex(eid);
34
+ const exist = ID !== void 0 && dense[ID] === eid;
35
+ const isWhitinBounds = ID >= 0 && ID < size;
36
+ return exist && isWhitinBounds;
37
+ }
38
+ function add(eid) {
39
+ if (has(eid)) throw new Error(`ID ${eid} already present in the set`);
40
+ sparse[eid] = size;
41
+ dense[size] = eid;
42
+ size++;
43
+ }
44
+ function remove(eid) {
45
+ if (!has(eid)) throw new Error(`Cannot remove ID ${eid} because does not exists`);
46
+ const ID = getIndex(eid);
47
+ const last = dense[size - 1];
48
+ dense[ID] = last;
49
+ sparse[last] = ID;
50
+ dense.pop();
51
+ size--;
52
+ delete sparse[eid];
53
+ }
54
+ function getIndex(eid) {
55
+ return sparse[eid];
56
+ }
57
+ function getDense() {
58
+ return dense;
59
+ }
60
+ function getSize() {
61
+ return size;
62
+ }
63
+ return { has, add, remove, getIndex, getDense, getSize };
64
+ }
65
+
66
+ // src/ComponentStore.ts
67
+ function ComponentStore() {
68
+ const componentSet = SparseSet();
69
+ const componentData = {};
70
+ function add(eid, data) {
71
+ if (componentSet.has(eid)) throw new Error(`Entity ${eid} already has this component`);
72
+ const ID = componentSet.getSize();
73
+ componentSet.add(eid);
74
+ for (const key in data) {
75
+ if (!componentData[key]) {
76
+ componentData[key] = [];
77
+ }
78
+ componentData[key][ID] = data[key];
79
+ }
80
+ }
81
+ function remove(eid) {
82
+ if (!componentSet.has(eid)) throw new Error(`Entity ${eid} does not have this component`);
83
+ const ID = componentSet.getIndex(eid);
84
+ const lastID = componentSet.getSize() - 1;
85
+ if (ID !== lastID) {
86
+ for (const key of Object.keys(componentData)) {
87
+ componentData[key][ID] = componentData[key][lastID];
88
+ }
89
+ }
90
+ componentSet.remove(eid);
91
+ for (const key in componentData) {
92
+ componentData[key].pop();
93
+ }
94
+ }
95
+ function getDense() {
96
+ return componentSet.getDense();
97
+ }
98
+ function getData() {
99
+ return componentData;
100
+ }
101
+ function getIndex(eid) {
102
+ return componentSet.getIndex(eid);
103
+ }
104
+ function getSize() {
105
+ return componentSet.getSize();
106
+ }
107
+ return { add, remove, getIndex, getDense, getData, getSize };
108
+ }
109
+
110
+ // src/EntityManager.ts
111
+ function EntityManager(registry) {
112
+ let nextID = 0;
113
+ const recycledIDs = [];
114
+ const bitMasks = [];
115
+ function exists(eid) {
116
+ const isWithinBounds = eid >= 0 && eid < nextID;
117
+ const hasMask = bitMasks[eid] !== void 0;
118
+ return isWithinBounds && hasMask;
119
+ }
120
+ function hasComponent(eid, name) {
121
+ if (!exists(eid)) throw new Error(`Entity ${eid} does not exist`);
122
+ const cid = registry.getID(name);
123
+ return (bitMasks[eid] & 1n << BigInt(cid)) !== 0n;
124
+ }
125
+ function create() {
126
+ const ID = recycledIDs.length > 0 ? recycledIDs.pop() : nextID++;
127
+ bitMasks[ID] = 0n;
128
+ return ID;
129
+ }
130
+ function remove(eid) {
131
+ if (!exists(eid)) throw new Error(`Entity ${eid} does not exist`);
132
+ bitMasks[eid] = void 0;
133
+ recycledIDs.push(eid);
134
+ }
135
+ function addComponent(eid, name) {
136
+ if (!exists(eid)) throw new Error(`Entity ${eid} does not exists`);
137
+ if (hasComponent(eid, name)) throw new Error(`Entity ${eid} already has component ${name}`);
138
+ const cid = registry.getID(name);
139
+ bitMasks[eid] |= 1n << BigInt(cid);
140
+ }
141
+ function removeComponent(eid, name) {
142
+ if (!exists(eid)) throw new Error(`Entity ${eid} does not exists`);
143
+ if (!hasComponent(eid, name)) throw new Error(`Entity ${eid} does not have component ${name}`);
144
+ const cid = registry.getID(name);
145
+ bitMasks[eid] &= ~(1n << BigInt(cid));
146
+ }
147
+ function getMask(eid) {
148
+ return bitMasks[eid];
149
+ }
150
+ return { exists, hasComponent, create, remove, addComponent, removeComponent, getMask };
151
+ }
152
+
153
+ // src/utils/ComponentRegistry.ts
154
+ function createComponentRegistry() {
155
+ const nameToID = /* @__PURE__ */ new Map();
156
+ const IDtoName = /* @__PURE__ */ new Map();
157
+ let nextID = 0;
158
+ function register(name) {
159
+ if (!nameToID.has(name)) {
160
+ const eid = nextID++;
161
+ nameToID.set(name, eid);
162
+ IDtoName.set(eid, name);
163
+ }
164
+ }
165
+ function getID(name) {
166
+ const eid = nameToID.get(name);
167
+ if (eid === void 0) throw new Error(`Component ${name} not registered`);
168
+ return eid;
169
+ }
170
+ function getName(cid) {
171
+ const name = IDtoName.get(cid);
172
+ if (!name) throw new Error(`Component ID ${cid} not registered`);
173
+ return name;
174
+ }
175
+ return { register, getID, getName };
176
+ }
177
+
178
+ // src/Ecs.ts
179
+ function ECS() {
180
+ const ComponentRegistry = createComponentRegistry();
181
+ const Entities = EntityManager(ComponentRegistry);
182
+ const internalStores = {};
183
+ const components = {};
184
+ function defineComponents(data) {
185
+ for (const key in data) {
186
+ ComponentRegistry.register(key);
187
+ internalStores[key] = ComponentStore();
188
+ components[key] = internalStores[key].getData();
189
+ }
190
+ }
191
+ function createEntity() {
192
+ return Entities.create();
193
+ }
194
+ function destroyEntity(eid) {
195
+ const entityMask = Entities.getMask(eid);
196
+ let cid = 0;
197
+ let tempMask = entityMask;
198
+ while (tempMask !== 0n) {
199
+ if ((tempMask & 1n) !== 0n) {
200
+ const name = ComponentRegistry.getName(cid);
201
+ internalStores[name].remove(eid);
202
+ }
203
+ tempMask >>= 1n;
204
+ cid++;
205
+ }
206
+ Entities.remove(eid);
207
+ }
208
+ function addComponent(eid, name, data) {
209
+ Entities.addComponent(eid, name);
210
+ internalStores[name].add(eid, data);
211
+ }
212
+ function removeComponent(eid, name) {
213
+ Entities.removeComponent(eid, name);
214
+ internalStores[name].remove(eid);
215
+ }
216
+ function* query(...componentName) {
217
+ if (componentName.length === 0) return;
218
+ let smallestStore = internalStores[componentName[0]];
219
+ let smallestSize = smallestStore.getSize();
220
+ for (let i = 1; i < componentName.length; i++) {
221
+ const store = internalStores[componentName[i]];
222
+ const size = store.getSize();
223
+ if (size < smallestSize) {
224
+ smallestStore = store;
225
+ smallestSize = size;
226
+ }
227
+ }
228
+ let targetMask = 0n;
229
+ for (const name of componentName) {
230
+ targetMask |= 1n << BigInt(ComponentRegistry.getID(name));
231
+ }
232
+ for (const eid of smallestStore.getDense()) {
233
+ if ((Entities.getMask(eid) & targetMask) !== targetMask) continue;
234
+ const result = {};
235
+ for (const name of componentName) {
236
+ const id = internalStores[name].getIndex(eid);
237
+ result[name] = { id };
238
+ }
239
+ yield result;
240
+ }
241
+ }
242
+ return { defineComponents, createEntity, destroyEntity, addComponent, removeComponent, query, components };
243
+ }
244
+ // Annotate the CommonJS export names for ESM import in node:
245
+ 0 && (module.exports = {
246
+ ECS
247
+ });
@@ -0,0 +1,103 @@
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][];
12
+ };
13
+
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
+ type StoreInit<T> = {
34
+ [K in keyof T]: {
35
+ [P in keyof T[K]]: T[K][P][];
36
+ };
37
+ };
38
+
39
+ /**
40
+ * Interface for the main ECS registry.
41
+ * Provides high-level API for entity-component management,
42
+ * coordinating between EntityManager and ComponentStores.
43
+ * @template T - Record type defining all component types in the ECS
44
+ */
45
+ interface IECS<T> {
46
+ /**
47
+ * Initializes the ECS with component definitions.
48
+ * Registers all components and creates their corresponding stores.
49
+ * Must be called before any other operations.
50
+ * @param components - Object mapping component names to their structure
51
+ */
52
+ defineComponents: (data: StoreInit<T>) => void;
53
+
54
+ /**
55
+ * Creates a new entity in the ECS.
56
+ * @returns The ID of the newly created entity
57
+ */
58
+ createEntity: () => number;
59
+
60
+ /**
61
+ * Completely removes an entity and all its components from the ECS.
62
+ * Cleans up all component data and entity records.
63
+ * @param eid - The entity ID to destroy
64
+ */
65
+ destroyEntity: (eid: number) => void;
66
+
67
+ /**
68
+ * Adds a component to an entity with the provided data.
69
+ * Updates both the entity's bitmask and the component store.
70
+ * @template K - The component name type (key of T)
71
+ * @param eid - The entity ID
72
+ * @param name - The component name
73
+ * @param data - The component data to store
74
+ */
75
+ addComponent: <K extends keyof T>(eid: number, name: K, data: T[K]) => void;
76
+
77
+ /**
78
+ * Removes a component from an entity.
79
+ * Updates both the entity's bitmask and the component store.
80
+ * @template K - The component name type (key of T)
81
+ * @param eid - The entity ID
82
+ * @param name - The component name
83
+ */
84
+ removeComponent: <K extends keyof T>(eid: number, name: K) => void;
85
+
86
+ /**
87
+ * Retrieves all entities that contain the specified set of components.
88
+ * Performs a filtered search across component stores and yields each matching entity.
89
+ * The returned generator allows efficient iteration without allocating large arrays.
90
+ *
91
+ * @template C - Array of component names to query for
92
+ * @param components - List of component names that the entity must include
93
+ * @returns A generator that yields an object for each matching entity,
94
+ * containing the entity ID and the related component data
95
+ */
96
+ query: <C extends (keyof T)[]>(...componentName: C) => Generator<QueryResult<T, C>>;
97
+
98
+ components: StoreDataMap<T>;
99
+ }
100
+
101
+ declare function ECS<T extends Record<string, Record<string, any>>>(): IECS<T>;
102
+
103
+ export { ECS };
@@ -0,0 +1,103 @@
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][];
12
+ };
13
+
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
+ type StoreInit<T> = {
34
+ [K in keyof T]: {
35
+ [P in keyof T[K]]: T[K][P][];
36
+ };
37
+ };
38
+
39
+ /**
40
+ * Interface for the main ECS registry.
41
+ * Provides high-level API for entity-component management,
42
+ * coordinating between EntityManager and ComponentStores.
43
+ * @template T - Record type defining all component types in the ECS
44
+ */
45
+ interface IECS<T> {
46
+ /**
47
+ * Initializes the ECS with component definitions.
48
+ * Registers all components and creates their corresponding stores.
49
+ * Must be called before any other operations.
50
+ * @param components - Object mapping component names to their structure
51
+ */
52
+ defineComponents: (data: StoreInit<T>) => void;
53
+
54
+ /**
55
+ * Creates a new entity in the ECS.
56
+ * @returns The ID of the newly created entity
57
+ */
58
+ createEntity: () => number;
59
+
60
+ /**
61
+ * Completely removes an entity and all its components from the ECS.
62
+ * Cleans up all component data and entity records.
63
+ * @param eid - The entity ID to destroy
64
+ */
65
+ destroyEntity: (eid: number) => void;
66
+
67
+ /**
68
+ * Adds a component to an entity with the provided data.
69
+ * Updates both the entity's bitmask and the component store.
70
+ * @template K - The component name type (key of T)
71
+ * @param eid - The entity ID
72
+ * @param name - The component name
73
+ * @param data - The component data to store
74
+ */
75
+ addComponent: <K extends keyof T>(eid: number, name: K, data: T[K]) => void;
76
+
77
+ /**
78
+ * Removes a component from an entity.
79
+ * Updates both the entity's bitmask and the component store.
80
+ * @template K - The component name type (key of T)
81
+ * @param eid - The entity ID
82
+ * @param name - The component name
83
+ */
84
+ removeComponent: <K extends keyof T>(eid: number, name: K) => void;
85
+
86
+ /**
87
+ * Retrieves all entities that contain the specified set of components.
88
+ * Performs a filtered search across component stores and yields each matching entity.
89
+ * The returned generator allows efficient iteration without allocating large arrays.
90
+ *
91
+ * @template C - Array of component names to query for
92
+ * @param components - List of component names that the entity must include
93
+ * @returns A generator that yields an object for each matching entity,
94
+ * containing the entity ID and the related component data
95
+ */
96
+ query: <C extends (keyof T)[]>(...componentName: C) => Generator<QueryResult<T, C>>;
97
+
98
+ components: StoreDataMap<T>;
99
+ }
100
+
101
+ declare function ECS<T extends Record<string, Record<string, any>>>(): IECS<T>;
102
+
103
+ export { ECS };
package/dist/index.js ADDED
@@ -0,0 +1,220 @@
1
+ // src/utils/SparseSet.ts
2
+ function SparseSet() {
3
+ const sparse = [];
4
+ const dense = [];
5
+ let size = 0;
6
+ function has(eid) {
7
+ const ID = getIndex(eid);
8
+ const exist = ID !== void 0 && dense[ID] === eid;
9
+ const isWhitinBounds = ID >= 0 && ID < size;
10
+ return exist && isWhitinBounds;
11
+ }
12
+ function add(eid) {
13
+ if (has(eid)) throw new Error(`ID ${eid} already present in the set`);
14
+ sparse[eid] = size;
15
+ dense[size] = eid;
16
+ size++;
17
+ }
18
+ function remove(eid) {
19
+ if (!has(eid)) throw new Error(`Cannot remove ID ${eid} because does not exists`);
20
+ const ID = getIndex(eid);
21
+ const last = dense[size - 1];
22
+ dense[ID] = last;
23
+ sparse[last] = ID;
24
+ dense.pop();
25
+ size--;
26
+ delete sparse[eid];
27
+ }
28
+ function getIndex(eid) {
29
+ return sparse[eid];
30
+ }
31
+ function getDense() {
32
+ return dense;
33
+ }
34
+ function getSize() {
35
+ return size;
36
+ }
37
+ return { has, add, remove, getIndex, getDense, getSize };
38
+ }
39
+
40
+ // src/ComponentStore.ts
41
+ function ComponentStore() {
42
+ const componentSet = SparseSet();
43
+ const componentData = {};
44
+ function add(eid, data) {
45
+ if (componentSet.has(eid)) throw new Error(`Entity ${eid} already has this component`);
46
+ const ID = componentSet.getSize();
47
+ componentSet.add(eid);
48
+ for (const key in data) {
49
+ if (!componentData[key]) {
50
+ componentData[key] = [];
51
+ }
52
+ componentData[key][ID] = data[key];
53
+ }
54
+ }
55
+ function remove(eid) {
56
+ if (!componentSet.has(eid)) throw new Error(`Entity ${eid} does not have this component`);
57
+ const ID = componentSet.getIndex(eid);
58
+ const lastID = componentSet.getSize() - 1;
59
+ if (ID !== lastID) {
60
+ for (const key of Object.keys(componentData)) {
61
+ componentData[key][ID] = componentData[key][lastID];
62
+ }
63
+ }
64
+ componentSet.remove(eid);
65
+ for (const key in componentData) {
66
+ componentData[key].pop();
67
+ }
68
+ }
69
+ function getDense() {
70
+ return componentSet.getDense();
71
+ }
72
+ function getData() {
73
+ return componentData;
74
+ }
75
+ function getIndex(eid) {
76
+ return componentSet.getIndex(eid);
77
+ }
78
+ function getSize() {
79
+ return componentSet.getSize();
80
+ }
81
+ return { add, remove, getIndex, getDense, getData, getSize };
82
+ }
83
+
84
+ // src/EntityManager.ts
85
+ function EntityManager(registry) {
86
+ let nextID = 0;
87
+ const recycledIDs = [];
88
+ const bitMasks = [];
89
+ function exists(eid) {
90
+ const isWithinBounds = eid >= 0 && eid < nextID;
91
+ const hasMask = bitMasks[eid] !== void 0;
92
+ return isWithinBounds && hasMask;
93
+ }
94
+ function hasComponent(eid, name) {
95
+ if (!exists(eid)) throw new Error(`Entity ${eid} does not exist`);
96
+ const cid = registry.getID(name);
97
+ return (bitMasks[eid] & 1n << BigInt(cid)) !== 0n;
98
+ }
99
+ function create() {
100
+ const ID = recycledIDs.length > 0 ? recycledIDs.pop() : nextID++;
101
+ bitMasks[ID] = 0n;
102
+ return ID;
103
+ }
104
+ function remove(eid) {
105
+ if (!exists(eid)) throw new Error(`Entity ${eid} does not exist`);
106
+ bitMasks[eid] = void 0;
107
+ recycledIDs.push(eid);
108
+ }
109
+ function addComponent(eid, name) {
110
+ if (!exists(eid)) throw new Error(`Entity ${eid} does not exists`);
111
+ if (hasComponent(eid, name)) throw new Error(`Entity ${eid} already has component ${name}`);
112
+ const cid = registry.getID(name);
113
+ bitMasks[eid] |= 1n << BigInt(cid);
114
+ }
115
+ function removeComponent(eid, name) {
116
+ if (!exists(eid)) throw new Error(`Entity ${eid} does not exists`);
117
+ if (!hasComponent(eid, name)) throw new Error(`Entity ${eid} does not have component ${name}`);
118
+ const cid = registry.getID(name);
119
+ bitMasks[eid] &= ~(1n << BigInt(cid));
120
+ }
121
+ function getMask(eid) {
122
+ return bitMasks[eid];
123
+ }
124
+ return { exists, hasComponent, create, remove, addComponent, removeComponent, getMask };
125
+ }
126
+
127
+ // src/utils/ComponentRegistry.ts
128
+ function createComponentRegistry() {
129
+ const nameToID = /* @__PURE__ */ new Map();
130
+ const IDtoName = /* @__PURE__ */ new Map();
131
+ let nextID = 0;
132
+ function register(name) {
133
+ if (!nameToID.has(name)) {
134
+ const eid = nextID++;
135
+ nameToID.set(name, eid);
136
+ IDtoName.set(eid, name);
137
+ }
138
+ }
139
+ function getID(name) {
140
+ const eid = nameToID.get(name);
141
+ if (eid === void 0) throw new Error(`Component ${name} not registered`);
142
+ return eid;
143
+ }
144
+ function getName(cid) {
145
+ const name = IDtoName.get(cid);
146
+ if (!name) throw new Error(`Component ID ${cid} not registered`);
147
+ return name;
148
+ }
149
+ return { register, getID, getName };
150
+ }
151
+
152
+ // src/Ecs.ts
153
+ function ECS() {
154
+ const ComponentRegistry = createComponentRegistry();
155
+ const Entities = EntityManager(ComponentRegistry);
156
+ const internalStores = {};
157
+ const components = {};
158
+ function defineComponents(data) {
159
+ for (const key in data) {
160
+ ComponentRegistry.register(key);
161
+ internalStores[key] = ComponentStore();
162
+ components[key] = internalStores[key].getData();
163
+ }
164
+ }
165
+ function createEntity() {
166
+ return Entities.create();
167
+ }
168
+ function destroyEntity(eid) {
169
+ const entityMask = Entities.getMask(eid);
170
+ let cid = 0;
171
+ let tempMask = entityMask;
172
+ while (tempMask !== 0n) {
173
+ if ((tempMask & 1n) !== 0n) {
174
+ const name = ComponentRegistry.getName(cid);
175
+ internalStores[name].remove(eid);
176
+ }
177
+ tempMask >>= 1n;
178
+ cid++;
179
+ }
180
+ Entities.remove(eid);
181
+ }
182
+ function addComponent(eid, name, data) {
183
+ Entities.addComponent(eid, name);
184
+ internalStores[name].add(eid, data);
185
+ }
186
+ function removeComponent(eid, name) {
187
+ Entities.removeComponent(eid, name);
188
+ internalStores[name].remove(eid);
189
+ }
190
+ function* query(...componentName) {
191
+ if (componentName.length === 0) return;
192
+ let smallestStore = internalStores[componentName[0]];
193
+ let smallestSize = smallestStore.getSize();
194
+ for (let i = 1; i < componentName.length; i++) {
195
+ const store = internalStores[componentName[i]];
196
+ const size = store.getSize();
197
+ if (size < smallestSize) {
198
+ smallestStore = store;
199
+ smallestSize = size;
200
+ }
201
+ }
202
+ let targetMask = 0n;
203
+ for (const name of componentName) {
204
+ targetMask |= 1n << BigInt(ComponentRegistry.getID(name));
205
+ }
206
+ for (const eid of smallestStore.getDense()) {
207
+ if ((Entities.getMask(eid) & targetMask) !== targetMask) continue;
208
+ const result = {};
209
+ for (const name of componentName) {
210
+ const id = internalStores[name].getIndex(eid);
211
+ result[name] = { id };
212
+ }
213
+ yield result;
214
+ }
215
+ }
216
+ return { defineComponents, createEntity, destroyEntity, addComponent, removeComponent, query, components };
217
+ }
218
+ export {
219
+ ECS
220
+ };
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@ste_tisci/ecs",
3
+ "version": "0.1.0",
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
+ }