@jael-ecs/core 1.0.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/LICENSE +21 -0
- package/README.md +493 -0
- package/dist/ComponentManager.d.ts +23 -0
- package/dist/EntityManager.d.ts +47 -0
- package/dist/EventRegistry.d.ts +11 -0
- package/dist/Query.d.ts +20 -0
- package/dist/SparseSet.d.ts +19 -0
- package/dist/SystemManager.d.ts +12 -0
- package/dist/Time.d.ts +18 -0
- package/dist/World.d.ts +43 -0
- package/dist/index.d.ts +9 -0
- package/dist/jael-build.cjs +1 -0
- package/dist/jael-build.js +332 -0
- package/package.json +38 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jonathan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# Jael (Just Another ECS Library)
|
|
4
|
+
|
|
5
|
+
[](https://badge.fury.io/js/%40jael%2Fcore)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](https://www.typescriptlang.org/)
|
|
8
|
+
|
|
9
|
+
_A modern, performant, and user-friendly Entity Component System library written in TypeScript_
|
|
10
|
+
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
## Table of contents
|
|
14
|
+
|
|
15
|
+
- [Installation](#installation)
|
|
16
|
+
- [Quick Start](#quick-start)
|
|
17
|
+
- [Architecture](#architecture)
|
|
18
|
+
- [Api Reference](#-api-reference)
|
|
19
|
+
- [World](#world)
|
|
20
|
+
- [Entity](#entity)
|
|
21
|
+
- [System](#system)
|
|
22
|
+
- [Query](#query)
|
|
23
|
+
- [SparseSet](#sparseset)
|
|
24
|
+
- [Time](#time)
|
|
25
|
+
- [EventRegistry](#event-registry)
|
|
26
|
+
- [Best Practices](#best-practices)
|
|
27
|
+
- [Advanced Usage](#advanced-usage)
|
|
28
|
+
- [Contributing](#contributing)
|
|
29
|
+
- [Acknowledgments](#acknowledgments)
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
- **User Friendly API** - Clean, fluent api that's easy to learn
|
|
34
|
+
- **High Performance** - Optimized SparseSet implementation for fast entity lookups
|
|
35
|
+
- **Minimal Bundle size** - Compact bundle size without dependencies.
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install @jael/core
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Quick Start
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
import { World, System } from "@jael/core";
|
|
47
|
+
|
|
48
|
+
// Create your world
|
|
49
|
+
const world = new World();
|
|
50
|
+
|
|
51
|
+
// Define components
|
|
52
|
+
interface Position {
|
|
53
|
+
x: number;
|
|
54
|
+
y: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface Velocity {
|
|
58
|
+
dx: number;
|
|
59
|
+
dy: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Create entities
|
|
63
|
+
const player = world.create();
|
|
64
|
+
world.addComponent(player, "position", { x: 0, y: 0 });
|
|
65
|
+
world.addComponent(player, "velocity", { dx: 1, dy: 1 });
|
|
66
|
+
|
|
67
|
+
const enemy = world.create();
|
|
68
|
+
world.addComponent(enemy, "position", { x: 10, y: 10 });
|
|
69
|
+
world.addComponent(enemy, "velocity", { dx: -1, dy: 0 });
|
|
70
|
+
|
|
71
|
+
// Create a system
|
|
72
|
+
const movementSystem: System = {
|
|
73
|
+
priority: 0,
|
|
74
|
+
update(dt) {
|
|
75
|
+
const query = world.include("position", "velocity");
|
|
76
|
+
|
|
77
|
+
for (const entity of query.entities) {
|
|
78
|
+
const position = entity.get<Position>("position");
|
|
79
|
+
const velocity = entity.get<Velocity>("velocity");
|
|
80
|
+
|
|
81
|
+
position.x += velocity.dx * (dt || 0.016);
|
|
82
|
+
position.y += velocity.dy * (dt || 0.016);
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Add system to world
|
|
88
|
+
world.addSystem(movementSystem);
|
|
89
|
+
|
|
90
|
+
// Game loop
|
|
91
|
+
function gameLoop(dt: number) {
|
|
92
|
+
world.update(dt);
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Architecture
|
|
97
|
+
|
|
98
|
+
Jael follows the classic Entity Component System pattern:
|
|
99
|
+
|
|
100
|
+
- **Entities**: Unique identifiers (just IDs) - no data attached
|
|
101
|
+
- **Components**: Pure data containers (no logic)
|
|
102
|
+
- **Systems**: Process entities with specific component combinations
|
|
103
|
+
|
|
104
|
+
## API Reference
|
|
105
|
+
|
|
106
|
+
### World
|
|
107
|
+
|
|
108
|
+
The central hub that manages entities, components, and systems.
|
|
109
|
+
|
|
110
|
+
#### Entity Management
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
// Create a new entity
|
|
114
|
+
const entity = world.create();
|
|
115
|
+
|
|
116
|
+
// Destroy an entity
|
|
117
|
+
world.destroy(entity);
|
|
118
|
+
|
|
119
|
+
// Check if entity exists
|
|
120
|
+
const exists = world.exist(entity);
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
#### Component Management
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
// Add component
|
|
127
|
+
world.addComponent(entity, "position", { x: 0, y: 0 });
|
|
128
|
+
|
|
129
|
+
// Remove component
|
|
130
|
+
world.removeComponent(entity, "position");
|
|
131
|
+
|
|
132
|
+
// Get component
|
|
133
|
+
const position = entity.get<Position>("position");
|
|
134
|
+
|
|
135
|
+
// Check if entity has component
|
|
136
|
+
const hasPosition = entity.has("position");
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
#### System Management
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
// Add system
|
|
143
|
+
world.addSystem(yourSystem);
|
|
144
|
+
|
|
145
|
+
// Remove system
|
|
146
|
+
world.removeSystem(yourSystem);
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
#### Events
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
// Listen to world events
|
|
153
|
+
world.on("entityCreated", ({ entity }) => {
|
|
154
|
+
console.log("Entity created:", entity);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
world.on("entityDestroyed", ({ entity }) => {
|
|
158
|
+
console.log("Entity destroyed:", entity);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
world.on("componentAdded", ({ entity, component }) => {
|
|
162
|
+
console.log(`Component ${component} added to entity`);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
world.on("componentRemoved", ({ entity, component }) => {
|
|
166
|
+
console.log(`Component ${component} removed from entity`);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
world.on("updated", () => {
|
|
170
|
+
console.log("World updated");
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Entity
|
|
175
|
+
|
|
176
|
+
Base entity class for intuitive component management
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
// Create entity
|
|
180
|
+
const entity = world.create();
|
|
181
|
+
|
|
182
|
+
// Add component
|
|
183
|
+
entity.add("position", { x: 0, y: 0 });
|
|
184
|
+
|
|
185
|
+
// Remove component
|
|
186
|
+
entity.remove("position");
|
|
187
|
+
|
|
188
|
+
// Check if component exist
|
|
189
|
+
const posExist = entity.has("position");
|
|
190
|
+
|
|
191
|
+
// Get curren value of the component
|
|
192
|
+
const compSchema = entity.get("position");
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### System
|
|
196
|
+
|
|
197
|
+
Systems contain the game logic that processes entities with specific components.
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
interface System {
|
|
201
|
+
priority: number; // Execution order (lower = earlier)
|
|
202
|
+
exit?(): void; // Cleanup when removed
|
|
203
|
+
update(dt?: number): void; // Main update logic
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
#### Example System
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
const renderSystem: System = {
|
|
211
|
+
priority: 100, // Render after all other systems
|
|
212
|
+
|
|
213
|
+
update(dt) {
|
|
214
|
+
const renderableQuery = world.include("position", "sprite");
|
|
215
|
+
|
|
216
|
+
for (const entity of renderableQuery.entities) {
|
|
217
|
+
const position = entity.get<Position>("position");
|
|
218
|
+
const sprite = entity.get<Sprite>("sprite");
|
|
219
|
+
|
|
220
|
+
// Render entity
|
|
221
|
+
drawSprite(sprite, position.x, position.y);
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
|
|
225
|
+
exit() {
|
|
226
|
+
console.log("Render system cleanup");
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Query
|
|
232
|
+
|
|
233
|
+
Queries provide efficient, cached access to entities matching specific component patterns.
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
interface QueryConfig {
|
|
237
|
+
include: string[]; // Required components
|
|
238
|
+
exclude: string[]; // Components to exclude
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
#### Creating Queries
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
// Simple include query
|
|
246
|
+
const movingEntities = world.include("position", "velocity");
|
|
247
|
+
|
|
248
|
+
// Simple exclude query
|
|
249
|
+
const nonStatic = world.exclude("static");
|
|
250
|
+
|
|
251
|
+
// Complex query
|
|
252
|
+
const complex = world.query({
|
|
253
|
+
include: ["position", "velocity", "health"],
|
|
254
|
+
exclude: ["dead", "paused"],
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// Can be use with builder pattern creating a hash for every include/exclude
|
|
258
|
+
const complexQuery2 = world.include("position", "health").exclude("static");
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
#### Accessing Results
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
// Iterate through entities
|
|
265
|
+
for (const entity of query.entities) {
|
|
266
|
+
// Process entity
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Get the first value of the query
|
|
270
|
+
const first = query.entities.first();
|
|
271
|
+
|
|
272
|
+
// Check query size
|
|
273
|
+
const count = query.entities.size();
|
|
274
|
+
|
|
275
|
+
// Check if query has any entities
|
|
276
|
+
const isEmpty = query.entities.size() === 0;
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### SparseSet
|
|
280
|
+
|
|
281
|
+
High-performance data structure used internally for entity and component storage.
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
const sparseSet = new SparseSet<Entity>();
|
|
285
|
+
|
|
286
|
+
// Add items
|
|
287
|
+
sparseSet.add(entity1);
|
|
288
|
+
sparseSet.add(entity2);
|
|
289
|
+
|
|
290
|
+
// Remove items
|
|
291
|
+
sparseSet.remove(entity1);
|
|
292
|
+
|
|
293
|
+
// Check existence
|
|
294
|
+
const exists = sparseSet.has(entity2);
|
|
295
|
+
|
|
296
|
+
// Iterate
|
|
297
|
+
for (const entity of sparseSet) {
|
|
298
|
+
// Process entity
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Get size
|
|
302
|
+
const size = sparseSet.size;
|
|
303
|
+
|
|
304
|
+
// Clear all
|
|
305
|
+
sparseSet.clear();
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Time
|
|
309
|
+
|
|
310
|
+
Utility singleton class for managing time and delta time calculations.
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
import { Time } from "@jael/core";
|
|
314
|
+
|
|
315
|
+
Time.start();
|
|
316
|
+
|
|
317
|
+
// Access time properties
|
|
318
|
+
const dt = Time.delta; // Delta time
|
|
319
|
+
const elapsed = Time.elapsed; // Total elapsed time
|
|
320
|
+
|
|
321
|
+
// Events
|
|
322
|
+
time.on("update", () => {
|
|
323
|
+
console.log(`Frame: ${dt}ms, Total: ${total}ms`);
|
|
324
|
+
});
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Event Registry
|
|
328
|
+
|
|
329
|
+
Base class providing event emission and listening capabilities.
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
interface WorldEvents {
|
|
333
|
+
entityCreated: { entity: Entity };
|
|
334
|
+
entityDestroyed: { entity: Entity };
|
|
335
|
+
componentAdded: { entity: Entity; component: string };
|
|
336
|
+
componentRemoved: { entity: Entity; component: string };
|
|
337
|
+
updated: void;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Listen to events
|
|
341
|
+
world.on("entityCreated", (data) => {
|
|
342
|
+
// Handle event
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
// Emit events (handled internally by World)
|
|
346
|
+
world.emit("entityCreated", { entity: someEntity });
|
|
347
|
+
|
|
348
|
+
// Remove listeners
|
|
349
|
+
world.off("entityCreated", handler);
|
|
350
|
+
|
|
351
|
+
// Romeve all listeners of a type
|
|
352
|
+
world.clearEvent('type')
|
|
353
|
+
|
|
354
|
+
// Remove all listeners
|
|
355
|
+
world.clearAllEvents();
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
## Best Practices
|
|
359
|
+
|
|
360
|
+
### 1. Component Design
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
// ✅ Good: Simple data containers
|
|
364
|
+
interface Position {
|
|
365
|
+
x: number;
|
|
366
|
+
y: number;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
interface Health {
|
|
370
|
+
current: number;
|
|
371
|
+
max: number;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// ❌ Avoid: Methods in components
|
|
375
|
+
interface BadComponent {
|
|
376
|
+
x: number;
|
|
377
|
+
move(): void; // Put this in a system!
|
|
378
|
+
}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### 2. System Organization
|
|
382
|
+
|
|
383
|
+
```typescript
|
|
384
|
+
// Organize systems by functionality and priority
|
|
385
|
+
const INPUT_PRIORITY = 0;
|
|
386
|
+
const PHYSICS_PRIORITY = 50;
|
|
387
|
+
const LOGIC_PRIORITY = 100;
|
|
388
|
+
const RENDER_PRIORITY = 200;
|
|
389
|
+
|
|
390
|
+
const inputSystem = { priority: INPUT_PRIORITY /* ... */ };
|
|
391
|
+
const physicsSystem = { priority: PHYSICS_PRIORITY /* ... */ };
|
|
392
|
+
const renderSystem = { priority: RENDER_PRIORITY /* ... */ };
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### 3. Query Optimization
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
// ✅ Good: Cache queries when possible
|
|
399
|
+
class MovementSystem implements System {
|
|
400
|
+
private movementQuery: Query;
|
|
401
|
+
priority: number = 1
|
|
402
|
+
|
|
403
|
+
constructor(private world: World) {
|
|
404
|
+
this.movementQuery = world.include('position', 'velocity');
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
update(dt?: number) {
|
|
408
|
+
for (const entity of this.movementQuery.entities) {
|
|
409
|
+
// Process movement
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// ✅ Also good: Use world.include/exclude for simple cases
|
|
415
|
+
update(dt?: number) {
|
|
416
|
+
const entities = this.world.include('position', 'velocity');
|
|
417
|
+
// ...
|
|
418
|
+
}
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### 4. Memory Management
|
|
422
|
+
|
|
423
|
+
```typescript
|
|
424
|
+
// Remember to clean up when removing entities
|
|
425
|
+
world.destroy(entity); // Automatically removes all components
|
|
426
|
+
|
|
427
|
+
// Clean up systems if they have resources
|
|
428
|
+
system.exit?.(); // Called automatically when removed from world
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
## Advanced Usage
|
|
432
|
+
|
|
433
|
+
### Custom Events
|
|
434
|
+
|
|
435
|
+
```typescript
|
|
436
|
+
// Extend world with custom events
|
|
437
|
+
interface CustomWorldEvents extends WorldEvents {
|
|
438
|
+
playerScored: { points: number };
|
|
439
|
+
gameOver: void;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const world = new World() as any as EventRegistry<CustomWorldEvents>;
|
|
443
|
+
|
|
444
|
+
// Emit custom events
|
|
445
|
+
world.emit("playerScored", { points: 100 });
|
|
446
|
+
|
|
447
|
+
// Listen to custom events
|
|
448
|
+
world.on("playerScored", ({ points }) => {
|
|
449
|
+
updateScore(points);
|
|
450
|
+
});
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
## Contributing
|
|
454
|
+
|
|
455
|
+
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
|
|
456
|
+
|
|
457
|
+
### Development Setup
|
|
458
|
+
|
|
459
|
+
```bash
|
|
460
|
+
# Clone the repository
|
|
461
|
+
git clone https://github.com/cammyb1/jael.git
|
|
462
|
+
cd jael
|
|
463
|
+
|
|
464
|
+
# Install dependencies
|
|
465
|
+
npm install
|
|
466
|
+
|
|
467
|
+
# Start development
|
|
468
|
+
npm run dev
|
|
469
|
+
|
|
470
|
+
# Build
|
|
471
|
+
npm run build
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
## License
|
|
475
|
+
|
|
476
|
+
[MIT](https://choosealicense.com/licenses/mit/) - see the [LICENSE](LICENSE) file for details.
|
|
477
|
+
|
|
478
|
+
## Acknowledgments
|
|
479
|
+
|
|
480
|
+
- Inspiration from ECS frameworks like [ECSY](https://github.com/ecsyjs/ecsy) and [Bevy](https://github.com/bevyengine/bevy)
|
|
481
|
+
- TypeScript for providing excellent type safety and developer experience
|
|
482
|
+
|
|
483
|
+
---
|
|
484
|
+
|
|
485
|
+
<div align="center">
|
|
486
|
+
|
|
487
|
+
[⭐ Star this repo if it helped you!](https://github.com/cammyb1/jael)
|
|
488
|
+
|
|
489
|
+
[☕ You can buy me a coffee :)](https://buymeacoffee.com/jonathanva5)
|
|
490
|
+
|
|
491
|
+
**Built with ❤️ by [cammyb1](https://github.com/cammyb1)**
|
|
492
|
+
|
|
493
|
+
</div>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Entity } from './EntityManager';
|
|
2
|
+
import { default as EventRegistry } from './EventRegistry';
|
|
3
|
+
import { default as World } from './World';
|
|
4
|
+
export type ComponentSchema = Record<string, any>;
|
|
5
|
+
export interface ComponentManagerEvents {
|
|
6
|
+
add: {
|
|
7
|
+
entity: Entity;
|
|
8
|
+
component: keyof ComponentSchema;
|
|
9
|
+
};
|
|
10
|
+
remove: {
|
|
11
|
+
entity: Entity;
|
|
12
|
+
component: keyof ComponentSchema;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export declare class ComponentManager extends EventRegistry<ComponentManagerEvents> {
|
|
16
|
+
componentSet: Map<number, ComponentSchema>;
|
|
17
|
+
world: World;
|
|
18
|
+
constructor(world: World);
|
|
19
|
+
addComponent<K extends keyof ComponentSchema>(entity: Entity, key: K, value: ComponentSchema[K]): void;
|
|
20
|
+
getComponent<K extends keyof ComponentSchema>(entity: Entity, key: K): ComponentSchema[K] | undefined;
|
|
21
|
+
hasComponent<K extends keyof ComponentSchema>(entity: Entity, key: K): boolean;
|
|
22
|
+
removeComponent<K extends keyof ComponentSchema>(entity: Entity, key: K): void;
|
|
23
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { default as EventRegistry } from './EventRegistry';
|
|
2
|
+
import { SparseSet } from './SparseSet';
|
|
3
|
+
import { default as World } from './World';
|
|
4
|
+
declare class Entity {
|
|
5
|
+
readonly id: number;
|
|
6
|
+
private _world;
|
|
7
|
+
constructor(world: World, id: number);
|
|
8
|
+
/**
|
|
9
|
+
* Add component to current entity.
|
|
10
|
+
* @param compType Component name
|
|
11
|
+
* @param compValue Component value
|
|
12
|
+
*/
|
|
13
|
+
add(compType: string, compValue: any): void;
|
|
14
|
+
/**
|
|
15
|
+
* Remove component of current entity.
|
|
16
|
+
* @param compType Component name
|
|
17
|
+
*/
|
|
18
|
+
remove(compType: string): void;
|
|
19
|
+
/**
|
|
20
|
+
* Check if current entity has a component.
|
|
21
|
+
* @param compType Component name
|
|
22
|
+
* @returns boolean
|
|
23
|
+
*/
|
|
24
|
+
has(compKey: string): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Get passed component schema of current entity.
|
|
27
|
+
* @param compType Component name
|
|
28
|
+
* @returns Return component schema with T(any as default) as type
|
|
29
|
+
*/
|
|
30
|
+
get<T = any>(compType: string): T;
|
|
31
|
+
}
|
|
32
|
+
export declare class EntityManager extends EventRegistry<EntityManagerEvents> {
|
|
33
|
+
entityMap: SparseSet<Entity>;
|
|
34
|
+
nextId: number;
|
|
35
|
+
_world: World;
|
|
36
|
+
constructor(world: World);
|
|
37
|
+
get entities(): SparseSet<Entity>;
|
|
38
|
+
create(): Entity;
|
|
39
|
+
exist(entity: Entity): boolean;
|
|
40
|
+
size(): number;
|
|
41
|
+
destroy(entity: Entity): Entity;
|
|
42
|
+
}
|
|
43
|
+
export interface EntityManagerEvents {
|
|
44
|
+
create: Entity;
|
|
45
|
+
destroy: Entity;
|
|
46
|
+
}
|
|
47
|
+
export { type Entity };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type Event<E> = (e: E[keyof E]) => void;
|
|
2
|
+
export default class EventRegistry<E extends Record<string, any> = {}> {
|
|
3
|
+
private _listeners;
|
|
4
|
+
on(type: keyof E, callback: Event<E>): void;
|
|
5
|
+
off(type: keyof E, callback: Event<E>): void;
|
|
6
|
+
once(type: keyof E, callback: Event<E>): void;
|
|
7
|
+
clearEvent(type: keyof E): void;
|
|
8
|
+
clearAllEvents(): void;
|
|
9
|
+
contains(type: keyof E, callback: Event<E>): boolean;
|
|
10
|
+
emit(type: keyof E, data: E[keyof E]): void;
|
|
11
|
+
}
|
package/dist/Query.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Entity } from './EntityManager';
|
|
2
|
+
import { SparseSet } from './SparseSet';
|
|
3
|
+
import { default as World } from './World';
|
|
4
|
+
export interface QueryConfig {
|
|
5
|
+
include: string[];
|
|
6
|
+
exclude: string[];
|
|
7
|
+
}
|
|
8
|
+
export declare class Query {
|
|
9
|
+
config: QueryConfig;
|
|
10
|
+
entityMap: SparseSet<Entity>;
|
|
11
|
+
world: World;
|
|
12
|
+
constructor(config: QueryConfig, world: World);
|
|
13
|
+
hasComponents(entity: Entity): boolean;
|
|
14
|
+
get entities(): SparseSet<Entity>;
|
|
15
|
+
include(...comps: string[]): Query;
|
|
16
|
+
exclude(...comps: string[]): Query;
|
|
17
|
+
private _checkExistingEntities;
|
|
18
|
+
checkEntities(): void;
|
|
19
|
+
static getHash(config: QueryConfig): number;
|
|
20
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare class SparseSet<V> {
|
|
2
|
+
denseValues: V[];
|
|
3
|
+
sparse: Map<V, number>;
|
|
4
|
+
[Symbol.iterator](): {
|
|
5
|
+
next: () => {
|
|
6
|
+
value: V;
|
|
7
|
+
done: boolean;
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
get values(): V[];
|
|
11
|
+
first(): V;
|
|
12
|
+
add(item: V): void;
|
|
13
|
+
indexOf(item: V): number;
|
|
14
|
+
remove(item: V): void;
|
|
15
|
+
forEach(predicate: (item: V) => void): void;
|
|
16
|
+
size(): number;
|
|
17
|
+
clear(): void;
|
|
18
|
+
has(item: V): boolean;
|
|
19
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface System {
|
|
2
|
+
priority: number;
|
|
3
|
+
exit?(): void;
|
|
4
|
+
update(dt?: number): void;
|
|
5
|
+
}
|
|
6
|
+
export declare class SystemManager {
|
|
7
|
+
systemList: System[];
|
|
8
|
+
addSystem(system: System): void;
|
|
9
|
+
reorder(): void;
|
|
10
|
+
has(system: System): boolean;
|
|
11
|
+
removeSystem(system: System): void;
|
|
12
|
+
}
|
package/dist/Time.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { default as EventRegistry } from './EventRegistry';
|
|
2
|
+
export interface TimeEvents {
|
|
3
|
+
update: void;
|
|
4
|
+
}
|
|
5
|
+
declare class TimeSingleton extends EventRegistry<TimeEvents> {
|
|
6
|
+
private _startTime;
|
|
7
|
+
private _oldTime;
|
|
8
|
+
private _requestId;
|
|
9
|
+
running: boolean;
|
|
10
|
+
delta: number;
|
|
11
|
+
elapsed: number;
|
|
12
|
+
constructor();
|
|
13
|
+
private _loop;
|
|
14
|
+
start(): void;
|
|
15
|
+
stop(): void;
|
|
16
|
+
}
|
|
17
|
+
export declare let Time: TimeSingleton;
|
|
18
|
+
export {};
|
package/dist/World.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ComponentManager, ComponentSchema } from './ComponentManager';
|
|
2
|
+
import { EntityManager, Entity } from './EntityManager';
|
|
3
|
+
import { default as EventRegistry } from './EventRegistry';
|
|
4
|
+
import { Query, QueryConfig } from './Query';
|
|
5
|
+
import { SparseSet } from './SparseSet';
|
|
6
|
+
import { SystemManager, System } from './SystemManager';
|
|
7
|
+
export interface WorldEvents {
|
|
8
|
+
entityCreated: {
|
|
9
|
+
entity: Entity;
|
|
10
|
+
};
|
|
11
|
+
entityDestroyed: {
|
|
12
|
+
entity: Entity;
|
|
13
|
+
};
|
|
14
|
+
componentAdded: {
|
|
15
|
+
entity: Entity;
|
|
16
|
+
component: keyof ComponentSchema;
|
|
17
|
+
};
|
|
18
|
+
componentRemoved: {
|
|
19
|
+
entity: Entity;
|
|
20
|
+
component: keyof ComponentSchema;
|
|
21
|
+
};
|
|
22
|
+
updated: void;
|
|
23
|
+
}
|
|
24
|
+
export default class World extends EventRegistry<WorldEvents> {
|
|
25
|
+
entityManager: EntityManager;
|
|
26
|
+
componentManager: ComponentManager;
|
|
27
|
+
systemManager: SystemManager;
|
|
28
|
+
queries: Map<number, Query>;
|
|
29
|
+
constructor();
|
|
30
|
+
get entities(): SparseSet<Entity>;
|
|
31
|
+
query(config: QueryConfig): Query;
|
|
32
|
+
private _updateQueries;
|
|
33
|
+
exist(entity: Entity): boolean;
|
|
34
|
+
include(...comps: string[]): Query;
|
|
35
|
+
exclude(...comps: string[]): Query;
|
|
36
|
+
create(): Entity;
|
|
37
|
+
destroy(entity: Entity): void;
|
|
38
|
+
addSystem(sys: System): void;
|
|
39
|
+
removeSystem(sys: System): void;
|
|
40
|
+
addComponent(entity: Entity, compKey: string, compValue: any): void;
|
|
41
|
+
removeComponent(entity: Entity, compKey: string): void;
|
|
42
|
+
update(): void;
|
|
43
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { default as EventRegistry } from './EventRegistry';
|
|
2
|
+
import { System, SystemManager } from './SystemManager';
|
|
3
|
+
import { Entity, EntityManager, EntityManagerEvents } from './EntityManager';
|
|
4
|
+
import { ComponentSchema, ComponentManager, ComponentManagerEvents } from './ComponentManager';
|
|
5
|
+
import { Query, QueryConfig } from './Query';
|
|
6
|
+
import { SparseSet } from './SparseSet';
|
|
7
|
+
import { Time, TimeEvents } from './Time';
|
|
8
|
+
import { default as World, WorldEvents } from './World';
|
|
9
|
+
export { type System, type Entity, type EntityManagerEvents, type ComponentManagerEvents, type ComponentSchema, type QueryConfig, type TimeEvents, type WorldEvents, Query, World, Time, SparseSet, EventRegistry, SystemManager, EntityManager, ComponentManager, };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class h{_listeners=new Map;on(e,t){if(this.contains(e,t))return;const s=this._listeners.get(e);s?s.add(t):this._listeners.set(e,new Set([t]))}off(e,t){if(!this.contains(e,t))return;const s=this._listeners.get(e);s&&s.delete(t)}once(e,t){const s=(i=>{t(i),this.off(e,s)});this.on(e,s)}clearEvent(e){this._listeners.get(e)&&this._listeners.get(e)?.clear()}clearAllEvents(){this._listeners.forEach(e=>e.clear()),this._listeners.clear()}contains(e,t){return this._listeners.get(e)?this._listeners.get(e).has(t):!1}emit(e,t){this._listeners.get(e)&&this._listeners.get(e)?.forEach(s=>{s(t)})}}class u{systemList=[];addSystem(e){this.systemList.push(e),this.reorder()}reorder(){this.systemList.sort((e,t)=>e.priority-t.priority)}has(e){return this.systemList.indexOf(e)>0}removeSystem(e){if(!this.has(e))return;const t=this.systemList.indexOf(e);t>=0&&(this.systemList.splice(t,1),e.exit?.(),this.reorder())}}class d{denseValues=[];sparse=new Map;[Symbol.iterator](){let e=this.values.length;const t={value:void 0,done:!1};return{next:()=>(t.value=this.values[--e],t.done=e<0,t)}}get values(){return this.denseValues}first(){return this.denseValues[0]}add(e){this.has(e)||(this.denseValues.push(e),this.sparse.set(e,this.denseValues.length-1))}indexOf(e){return this.sparse.get(e)?this.sparse.get(e):-1}remove(e){if(!this.has(e))return;const t=this.sparse.get(e);this.sparse.delete(e);const s=this.denseValues[this.denseValues.length-1];s!==e&&(this.denseValues[t]=s,this.sparse.set(s,t)),this.denseValues.pop()}forEach(e){for(let t of this)e(t)}size(){return this.denseValues.length}clear(){for(let e of this)this.remove(e)}has(e){return this.sparse.has(e)}}class m{id;_world;constructor(e,t){this.id=t,this._world=e}add(e,t){this._world.addComponent(this,e,t)}remove(e){this._world.removeComponent(this,e)}has(e){return this._world.componentManager.hasComponent(this,e)}get(e){return this._world.componentManager.getComponent(this,e)}}class c extends h{entityMap=new d;nextId=0;_world;constructor(e){super(),this._world=e}get entities(){return this.entityMap}create(){const e=this.nextId++,t=new m(this._world,e);return this.entities.add(t),this.emit("create",t),t}exist(e){return this.entities.has(e)}size(){return this.entities.size()}destroy(e){return this.entities.remove(e),this.emit("destroy",e),e}}class l extends h{componentSet=new Map;world;constructor(e){super(),this.world=e,this.world.on("entityDestroyed",t=>{t&&t.entity&&this.componentSet.get(t.entity.id)&&this.componentSet.delete(t.entity.id)})}addComponent(e,t,s){const i=this.componentSet.get(e.id);i?i[t]=s:this.componentSet.set(e.id,{[t]:s}),this.emit("add",{entity:e,component:t})}getComponent(e,t){return this.hasComponent(e,t)?this.componentSet.get(e.id)[t]:void 0}hasComponent(e,t){const s=this.componentSet.get(e.id);return s?t in s:!1}removeComponent(e,t){if(!this.componentSet.get(e.id))return;const s=this.componentSet.get(e.id);s&&s[t]!==void 0&&(delete s[t],Object.keys(s).length===0&&this.componentSet.delete(e.id),this.emit("remove",{entity:e,component:t}))}}class a{config;entityMap;world;constructor(e,t){this.config=e,this.world=t,this.entityMap=new d}hasComponents(e){return this.config.include?.every(t=>e.has(t))&&this.config.exclude?.every(t=>!e.has(t))}get entities(){return this.entityMap}include(...e){return this.world.include(...e)}exclude(...e){return this.world.exclude(...e)}_checkExistingEntities(){for(let e of this.entities)this.world.exist(e)||this.entityMap.remove(e)}checkEntities(){for(let e of this.world.entities)e&&this.hasComponents(e)&&this.entityMap.add(e);this._checkExistingEntities()}static getHash(e){const t=e.include?.map(n=>n.trim()).filter(n=>n).join("_"),s=e.exclude?.map(n=>n.trim()).filter(n=>n).join("_"),i="in_"+t+"_out_"+s;let o=0;for(const n of i)o=(o<<5)-o+n.charCodeAt(0),o|=0;return o}}class p extends h{_startTime=0;_oldTime=0;_requestId=0;running=!1;delta=0;elapsed=0;constructor(){super()}_loop(){let e=0;if(this.running){const t=performance.now();e=(t-this._oldTime)/1e3,this._oldTime=t,this.elapsed+=e}this.delta=e,this.emit("update"),this._requestId=requestAnimationFrame(this._loop.bind(this))}start(){this._startTime=performance.now(),this._oldTime=this._startTime,this.elapsed=0,this.delta=0,this.running=!0,this._loop()}stop(){this.running=!1,cancelAnimationFrame(this._requestId),this._requestId=0}}let g=new p;class f extends h{entityManager;componentManager;systemManager;queries;constructor(){super(),this.entityManager=new c(this),this.componentManager=new l(this),this.systemManager=new u,this.entityManager.on("create",e=>{e&&this.emit("entityCreated",{entity:e}),this._updateQueries()}),this.entityManager.on("destroy",e=>{e&&this.emit("entityDestroyed",{entity:e}),this._updateQueries()}),this.componentManager.on("add",e=>{this.emit("componentAdded",{entity:e.entity,component:e.component}),this._updateQueries()}),this.componentManager.on("remove",e=>{this.emit("componentRemoved",{entity:e.entity,component:e.component}),this._updateQueries()}),this.queries=new Map}get entities(){return this.entityManager.entities}query(e){const t=a.getHash(e);let i=this.queries.get(t);return i||(i=new a(e,this),this.queries.set(t,i),this._updateQueries()),i}_updateQueries(){this.queries.forEach(e=>e.checkEntities())}exist(e){return this.entityManager.exist(e)}include(...e){return this.query({include:e,exclude:[]})}exclude(...e){return this.query({include:[],exclude:e})}create(){return this.entityManager.create()}destroy(e){this.entityManager.destroy(e)}addSystem(e){this.systemManager.addSystem(e)}removeSystem(e){this.systemManager.removeSystem(e)}addComponent(e,t,s){this.componentManager.addComponent(e,t,s)}removeComponent(e,t){this.componentManager.removeComponent(e,t)}update(){this.systemManager.systemList.forEach(e=>{e.update()}),this.emit("updated")}}exports.ComponentManager=l;exports.EntityManager=c;exports.EventRegistry=h;exports.Query=a;exports.SparseSet=d;exports.SystemManager=u;exports.Time=g;exports.World=f;
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
class h {
|
|
2
|
+
_listeners = /* @__PURE__ */ new Map();
|
|
3
|
+
on(e, t) {
|
|
4
|
+
if (this.contains(e, t))
|
|
5
|
+
return;
|
|
6
|
+
const s = this._listeners.get(e);
|
|
7
|
+
s ? s.add(t) : this._listeners.set(e, /* @__PURE__ */ new Set([t]));
|
|
8
|
+
}
|
|
9
|
+
off(e, t) {
|
|
10
|
+
if (!this.contains(e, t))
|
|
11
|
+
return;
|
|
12
|
+
const s = this._listeners.get(e);
|
|
13
|
+
s && s.delete(t);
|
|
14
|
+
}
|
|
15
|
+
once(e, t) {
|
|
16
|
+
const s = ((i) => {
|
|
17
|
+
t(i), this.off(e, s);
|
|
18
|
+
});
|
|
19
|
+
this.on(e, s);
|
|
20
|
+
}
|
|
21
|
+
clearEvent(e) {
|
|
22
|
+
this._listeners.get(e) && this._listeners.get(e)?.clear();
|
|
23
|
+
}
|
|
24
|
+
clearAllEvents() {
|
|
25
|
+
this._listeners.forEach((e) => e.clear()), this._listeners.clear();
|
|
26
|
+
}
|
|
27
|
+
contains(e, t) {
|
|
28
|
+
return this._listeners.get(e) ? this._listeners.get(e).has(t) : !1;
|
|
29
|
+
}
|
|
30
|
+
emit(e, t) {
|
|
31
|
+
this._listeners.get(e) && this._listeners.get(e)?.forEach((s) => {
|
|
32
|
+
s(t);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
class u {
|
|
37
|
+
systemList = [];
|
|
38
|
+
addSystem(e) {
|
|
39
|
+
this.systemList.push(e), this.reorder();
|
|
40
|
+
}
|
|
41
|
+
reorder() {
|
|
42
|
+
this.systemList.sort((e, t) => e.priority - t.priority);
|
|
43
|
+
}
|
|
44
|
+
has(e) {
|
|
45
|
+
return this.systemList.indexOf(e) > 0;
|
|
46
|
+
}
|
|
47
|
+
removeSystem(e) {
|
|
48
|
+
if (!this.has(e)) return;
|
|
49
|
+
const t = this.systemList.indexOf(e);
|
|
50
|
+
t >= 0 && (this.systemList.splice(t, 1), e.exit?.(), this.reorder());
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
class d {
|
|
54
|
+
denseValues = [];
|
|
55
|
+
sparse = /* @__PURE__ */ new Map();
|
|
56
|
+
[Symbol.iterator]() {
|
|
57
|
+
let e = this.values.length;
|
|
58
|
+
const t = {
|
|
59
|
+
value: void 0,
|
|
60
|
+
done: !1
|
|
61
|
+
};
|
|
62
|
+
return {
|
|
63
|
+
next: () => (t.value = this.values[--e], t.done = e < 0, t)
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
get values() {
|
|
67
|
+
return this.denseValues;
|
|
68
|
+
}
|
|
69
|
+
first() {
|
|
70
|
+
return this.denseValues[0];
|
|
71
|
+
}
|
|
72
|
+
add(e) {
|
|
73
|
+
this.has(e) || (this.denseValues.push(e), this.sparse.set(e, this.denseValues.length - 1));
|
|
74
|
+
}
|
|
75
|
+
indexOf(e) {
|
|
76
|
+
return this.sparse.get(e) ? this.sparse.get(e) : -1;
|
|
77
|
+
}
|
|
78
|
+
remove(e) {
|
|
79
|
+
if (!this.has(e)) return;
|
|
80
|
+
const t = this.sparse.get(e);
|
|
81
|
+
this.sparse.delete(e);
|
|
82
|
+
const s = this.denseValues[this.denseValues.length - 1];
|
|
83
|
+
s !== e && (this.denseValues[t] = s, this.sparse.set(s, t)), this.denseValues.pop();
|
|
84
|
+
}
|
|
85
|
+
forEach(e) {
|
|
86
|
+
for (let t of this)
|
|
87
|
+
e(t);
|
|
88
|
+
}
|
|
89
|
+
size() {
|
|
90
|
+
return this.denseValues.length;
|
|
91
|
+
}
|
|
92
|
+
clear() {
|
|
93
|
+
for (let e of this)
|
|
94
|
+
this.remove(e);
|
|
95
|
+
}
|
|
96
|
+
has(e) {
|
|
97
|
+
return this.sparse.has(e);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
class c {
|
|
101
|
+
id;
|
|
102
|
+
_world;
|
|
103
|
+
constructor(e, t) {
|
|
104
|
+
this.id = t, this._world = e;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Add component to current entity.
|
|
108
|
+
* @param compType Component name
|
|
109
|
+
* @param compValue Component value
|
|
110
|
+
*/
|
|
111
|
+
add(e, t) {
|
|
112
|
+
this._world.addComponent(this, e, t);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Remove component of current entity.
|
|
116
|
+
* @param compType Component name
|
|
117
|
+
*/
|
|
118
|
+
remove(e) {
|
|
119
|
+
this._world.removeComponent(this, e);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Check if current entity has a component.
|
|
123
|
+
* @param compType Component name
|
|
124
|
+
* @returns boolean
|
|
125
|
+
*/
|
|
126
|
+
has(e) {
|
|
127
|
+
return this._world.componentManager.hasComponent(this, e);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Get passed component schema of current entity.
|
|
131
|
+
* @param compType Component name
|
|
132
|
+
* @returns Return component schema with T(any as default) as type
|
|
133
|
+
*/
|
|
134
|
+
get(e) {
|
|
135
|
+
return this._world.componentManager.getComponent(this, e);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
class l extends h {
|
|
139
|
+
entityMap = new d();
|
|
140
|
+
nextId = 0;
|
|
141
|
+
_world;
|
|
142
|
+
constructor(e) {
|
|
143
|
+
super(), this._world = e;
|
|
144
|
+
}
|
|
145
|
+
get entities() {
|
|
146
|
+
return this.entityMap;
|
|
147
|
+
}
|
|
148
|
+
create() {
|
|
149
|
+
const e = this.nextId++, t = new c(this._world, e);
|
|
150
|
+
return this.entities.add(t), this.emit("create", t), t;
|
|
151
|
+
}
|
|
152
|
+
exist(e) {
|
|
153
|
+
return this.entities.has(e);
|
|
154
|
+
}
|
|
155
|
+
size() {
|
|
156
|
+
return this.entities.size();
|
|
157
|
+
}
|
|
158
|
+
destroy(e) {
|
|
159
|
+
return this.entities.remove(e), this.emit("destroy", e), e;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
class m extends h {
|
|
163
|
+
componentSet = /* @__PURE__ */ new Map();
|
|
164
|
+
world;
|
|
165
|
+
constructor(e) {
|
|
166
|
+
super(), this.world = e, this.world.on("entityDestroyed", (t) => {
|
|
167
|
+
t && t.entity && this.componentSet.get(t.entity.id) && this.componentSet.delete(t.entity.id);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
addComponent(e, t, s) {
|
|
171
|
+
const i = this.componentSet.get(
|
|
172
|
+
e.id
|
|
173
|
+
);
|
|
174
|
+
i ? i[t] = s : this.componentSet.set(e.id, { [t]: s }), this.emit("add", { entity: e, component: t });
|
|
175
|
+
}
|
|
176
|
+
getComponent(e, t) {
|
|
177
|
+
return this.hasComponent(e, t) ? this.componentSet.get(e.id)[t] : void 0;
|
|
178
|
+
}
|
|
179
|
+
hasComponent(e, t) {
|
|
180
|
+
const s = this.componentSet.get(e.id);
|
|
181
|
+
return s ? t in s : !1;
|
|
182
|
+
}
|
|
183
|
+
removeComponent(e, t) {
|
|
184
|
+
if (!this.componentSet.get(e.id)) return;
|
|
185
|
+
const s = this.componentSet.get(e.id);
|
|
186
|
+
s && s[t] !== void 0 && (delete s[t], Object.keys(s).length === 0 && this.componentSet.delete(e.id), this.emit("remove", { entity: e, component: t }));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
class a {
|
|
190
|
+
config;
|
|
191
|
+
entityMap;
|
|
192
|
+
world;
|
|
193
|
+
constructor(e, t) {
|
|
194
|
+
this.config = e, this.world = t, this.entityMap = new d();
|
|
195
|
+
}
|
|
196
|
+
hasComponents(e) {
|
|
197
|
+
return this.config.include?.every((t) => e.has(t)) && this.config.exclude?.every((t) => !e.has(t));
|
|
198
|
+
}
|
|
199
|
+
get entities() {
|
|
200
|
+
return this.entityMap;
|
|
201
|
+
}
|
|
202
|
+
include(...e) {
|
|
203
|
+
return this.world.include(...e);
|
|
204
|
+
}
|
|
205
|
+
exclude(...e) {
|
|
206
|
+
return this.world.exclude(...e);
|
|
207
|
+
}
|
|
208
|
+
_checkExistingEntities() {
|
|
209
|
+
for (let e of this.entities)
|
|
210
|
+
this.world.exist(e) || this.entityMap.remove(e);
|
|
211
|
+
}
|
|
212
|
+
checkEntities() {
|
|
213
|
+
for (let e of this.world.entities)
|
|
214
|
+
e && this.hasComponents(e) && this.entityMap.add(e);
|
|
215
|
+
this._checkExistingEntities();
|
|
216
|
+
}
|
|
217
|
+
static getHash(e) {
|
|
218
|
+
const t = e.include?.map((n) => n.trim()).filter((n) => n).join("_"), s = e.exclude?.map((n) => n.trim()).filter((n) => n).join("_"), i = "in_" + t + "_out_" + s;
|
|
219
|
+
let o = 0;
|
|
220
|
+
for (const n of i)
|
|
221
|
+
o = (o << 5) - o + n.charCodeAt(0), o |= 0;
|
|
222
|
+
return o;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
class p extends h {
|
|
226
|
+
_startTime = 0;
|
|
227
|
+
_oldTime = 0;
|
|
228
|
+
_requestId = 0;
|
|
229
|
+
running = !1;
|
|
230
|
+
delta = 0;
|
|
231
|
+
elapsed = 0;
|
|
232
|
+
constructor() {
|
|
233
|
+
super();
|
|
234
|
+
}
|
|
235
|
+
_loop() {
|
|
236
|
+
let e = 0;
|
|
237
|
+
if (this.running) {
|
|
238
|
+
const t = performance.now();
|
|
239
|
+
e = (t - this._oldTime) / 1e3, this._oldTime = t, this.elapsed += e;
|
|
240
|
+
}
|
|
241
|
+
this.delta = e, this.emit("update"), this._requestId = requestAnimationFrame(this._loop.bind(this));
|
|
242
|
+
}
|
|
243
|
+
start() {
|
|
244
|
+
this._startTime = performance.now(), this._oldTime = this._startTime, this.elapsed = 0, this.delta = 0, this.running = !0, this._loop();
|
|
245
|
+
}
|
|
246
|
+
stop() {
|
|
247
|
+
this.running = !1, cancelAnimationFrame(this._requestId), this._requestId = 0;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
let g = new p();
|
|
251
|
+
class f extends h {
|
|
252
|
+
entityManager;
|
|
253
|
+
componentManager;
|
|
254
|
+
systemManager;
|
|
255
|
+
queries;
|
|
256
|
+
constructor() {
|
|
257
|
+
super(), this.entityManager = new l(this), this.componentManager = new m(this), this.systemManager = new u(), this.entityManager.on("create", (e) => {
|
|
258
|
+
e && this.emit("entityCreated", { entity: e }), this._updateQueries();
|
|
259
|
+
}), this.entityManager.on("destroy", (e) => {
|
|
260
|
+
e && this.emit("entityDestroyed", { entity: e }), this._updateQueries();
|
|
261
|
+
}), this.componentManager.on(
|
|
262
|
+
"add",
|
|
263
|
+
(e) => {
|
|
264
|
+
this.emit("componentAdded", {
|
|
265
|
+
entity: e.entity,
|
|
266
|
+
component: e.component
|
|
267
|
+
}), this._updateQueries();
|
|
268
|
+
}
|
|
269
|
+
), this.componentManager.on(
|
|
270
|
+
"remove",
|
|
271
|
+
(e) => {
|
|
272
|
+
this.emit("componentRemoved", {
|
|
273
|
+
entity: e.entity,
|
|
274
|
+
component: e.component
|
|
275
|
+
}), this._updateQueries();
|
|
276
|
+
}
|
|
277
|
+
), this.queries = /* @__PURE__ */ new Map();
|
|
278
|
+
}
|
|
279
|
+
get entities() {
|
|
280
|
+
return this.entityManager.entities;
|
|
281
|
+
}
|
|
282
|
+
query(e) {
|
|
283
|
+
const t = a.getHash(e);
|
|
284
|
+
let i = this.queries.get(t);
|
|
285
|
+
return i || (i = new a(e, this), this.queries.set(t, i), this._updateQueries()), i;
|
|
286
|
+
}
|
|
287
|
+
_updateQueries() {
|
|
288
|
+
this.queries.forEach((e) => e.checkEntities());
|
|
289
|
+
}
|
|
290
|
+
exist(e) {
|
|
291
|
+
return this.entityManager.exist(e);
|
|
292
|
+
}
|
|
293
|
+
include(...e) {
|
|
294
|
+
return this.query({ include: e, exclude: [] });
|
|
295
|
+
}
|
|
296
|
+
exclude(...e) {
|
|
297
|
+
return this.query({ include: [], exclude: e });
|
|
298
|
+
}
|
|
299
|
+
create() {
|
|
300
|
+
return this.entityManager.create();
|
|
301
|
+
}
|
|
302
|
+
destroy(e) {
|
|
303
|
+
this.entityManager.destroy(e);
|
|
304
|
+
}
|
|
305
|
+
addSystem(e) {
|
|
306
|
+
this.systemManager.addSystem(e);
|
|
307
|
+
}
|
|
308
|
+
removeSystem(e) {
|
|
309
|
+
this.systemManager.removeSystem(e);
|
|
310
|
+
}
|
|
311
|
+
addComponent(e, t, s) {
|
|
312
|
+
this.componentManager.addComponent(e, t, s);
|
|
313
|
+
}
|
|
314
|
+
removeComponent(e, t) {
|
|
315
|
+
this.componentManager.removeComponent(e, t);
|
|
316
|
+
}
|
|
317
|
+
update() {
|
|
318
|
+
this.systemManager.systemList.forEach((e) => {
|
|
319
|
+
e.update();
|
|
320
|
+
}), this.emit("updated");
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
export {
|
|
324
|
+
m as ComponentManager,
|
|
325
|
+
l as EntityManager,
|
|
326
|
+
h as EventRegistry,
|
|
327
|
+
a as Query,
|
|
328
|
+
d as SparseSet,
|
|
329
|
+
u as SystemManager,
|
|
330
|
+
g as Time,
|
|
331
|
+
f as World
|
|
332
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jael-ecs/core",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Entity Component System library with typescript",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"ecs",
|
|
7
|
+
"ts"
|
|
8
|
+
],
|
|
9
|
+
"homepage": "https://github.com/cammyb1/jael#readme",
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/cammyb1/jael/issues"
|
|
12
|
+
},
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/cammyb1/jael.git"
|
|
16
|
+
},
|
|
17
|
+
"main": "./dist/jael-build.cjs",
|
|
18
|
+
"module": "./dist/jael-build.js",
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"author": "cammyb1",
|
|
22
|
+
"type": "module",
|
|
23
|
+
"files": [
|
|
24
|
+
"dist/**",
|
|
25
|
+
"LICENSE",
|
|
26
|
+
"README.md"
|
|
27
|
+
],
|
|
28
|
+
"scripts": {
|
|
29
|
+
"dev": "vite",
|
|
30
|
+
"build": "tsc && vite build",
|
|
31
|
+
"preview": "vite preview"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"typescript": "~5.9.3",
|
|
35
|
+
"unplugin-dts": "^1.0.0-beta.6",
|
|
36
|
+
"vite": "^7.2.4"
|
|
37
|
+
}
|
|
38
|
+
}
|