entt-js 0.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/README.md ADDED
@@ -0,0 +1,552 @@
1
+ # entt-js
2
+
3
+ [![npm version](https://img.shields.io/npm/v/entt-js.svg)](https://www.npmjs.com/package/entt-js)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ ## Sponsors
7
+
8
+ <p align="center">
9
+ <a href="https://cdn.jsdelivr.net/gh/toyobayashi/toyobayashi/sponsorkit/sponsors.svg">
10
+ <img src='https://cdn.jsdelivr.net/gh/toyobayashi/toyobayashi/sponsorkit/sponsors.svg'/>
11
+ </a>
12
+ </p>
13
+
14
+ ## Overview
15
+
16
+ A TypeScript port of [EnTT](https://github.com/skypjack/entt) - a fast and reliable Entity Component System (ECS) implementation.
17
+
18
+ `entt-js` brings the power of EnTT's battle-tested ECS architecture to TypeScript/JavaScript, offering a high-performance solution for entity management and component-based game development. This library maintains the core design philosophy of the original C++ implementation while leveraging TypeScript's type system for enhanced developer experience.
19
+
20
+ ### What is ECS?
21
+
22
+ Entity Component System (ECS) is an architectural pattern commonly used in game development that separates data (Components) from entities (Entities) and logic (Systems). This approach provides:
23
+
24
+ - **High Performance**: Cache-friendly data layouts and efficient iteration
25
+ - **Flexibility**: Easy composition of game objects from reusable components
26
+ - **Scalability**: Handles thousands to millions of entities efficiently
27
+ - **Maintainability**: Clear separation of concerns
28
+
29
+ ## Features
30
+
31
+ - 🚀 **High Performance**: Optimized sparse set implementation with cache-friendly memory layouts
32
+ - 📦 **Full TypeScript Support**: Comprehensive type definitions with advanced type inference
33
+ - 🎯 **Type-Safe API**: Leverages TypeScript's type system to catch errors at compile time
34
+ - 🔄 **Entity Lifecycle**: Complete entity creation, destruction, and recycling
35
+ - 🧩 **Component Management**: Add, remove, and query components with ease
36
+ - 👁️ **Views & Groups**: Efficient iteration over entities with specific components
37
+ - 📡 **Signals**: Event system for component lifecycle hooks
38
+ - 🔍 **Runtime Views**: Dynamic component queries without compile-time types
39
+ - 📸 **Snapshots**: Save and restore registry state
40
+ - 🌐 **Multi-Platform**: Works in Node.js and browsers
41
+
42
+ ## Installation
43
+
44
+ ```bash
45
+ npm install entt-js
46
+ ```
47
+
48
+ ## Quick Start
49
+
50
+ ```typescript
51
+ import { Registry } from 'entt-js'
52
+
53
+ // Define your components
54
+ class Position {
55
+ x: number
56
+ y: number
57
+ constructor(x = 0, y = 0) {
58
+ this.x = x
59
+ this.y = y
60
+ }
61
+ }
62
+
63
+ class Velocity {
64
+ dx: number
65
+ dy: number
66
+ constructor(dx = 0, dy = 0) {
67
+ this.dx = dx
68
+ this.dy = dy
69
+ }
70
+ }
71
+
72
+ // Create a registry
73
+ const registry = new Registry()
74
+
75
+ // Create entities and attach components
76
+ const entity = registry.create()
77
+ registry.emplace(entity, Position, 10, 20)
78
+ registry.emplace(entity, Velocity, 1, 0)
79
+
80
+ // Query entities with specific components
81
+ const view = registry.view([Position, Velocity])
82
+ view.each((entity, position, velocity) => {
83
+ position.x += velocity.dx
84
+ position.y += velocity.dy
85
+ })
86
+
87
+ // for-of iteration
88
+ for (const [entity, position, velocity] of view.each()) {
89
+ position.x += velocity.dx
90
+ position.y += velocity.dy
91
+ }
92
+ ```
93
+
94
+ ## Browser Support
95
+
96
+ The library supports both Node.js and modern browsers. For browser usage:
97
+
98
+ ### No Bundler
99
+
100
+ ```html
101
+ <!-- IIFE -->
102
+ <script src="https://cdn.jsdelivr.net/npm/entt-js/dist/browser/entt.min.js"></script>
103
+
104
+ <script>
105
+ const { Registry } = window.entt
106
+ </script>
107
+ ```
108
+
109
+ or
110
+
111
+ ```html
112
+ <!-- ESM -->
113
+ <script type="module">
114
+ import { Registry } from 'https://cdn.jsdelivr.net/npm/entt-js/dist/browser/index.min.js'
115
+ </script>
116
+ ```
117
+
118
+ ### With Bundler
119
+
120
+ ```javascript
121
+ import { Registry } from 'entt-js'
122
+ ```
123
+
124
+
125
+ ## Core Concepts
126
+
127
+ ### Registry
128
+
129
+ The `Registry` is the central hub for managing entities and components:
130
+
131
+ ```typescript
132
+ const registry = new Registry()
133
+
134
+ // Create entities
135
+ const entity1 = registry.create()
136
+ const entity2 = registry.create()
137
+
138
+ // Check if entity is valid
139
+ registry.valid(entity1) // true
140
+
141
+ // Destroy entity
142
+ registry.destroy(entity1)
143
+ ```
144
+
145
+ ### Components
146
+
147
+ Components are plain TypeScript classes or objects that hold data:
148
+
149
+ ```typescript
150
+ class Health {
151
+ value: number
152
+ constructor(value = 0) {
153
+ this.value = value
154
+ }
155
+ }
156
+
157
+ class Transform {
158
+ x: number
159
+ y: number
160
+ rotation: number
161
+
162
+ constructor(x = 0, y = 0, rotation = 0) {
163
+ this.x = x
164
+ this.y = y
165
+ this.rotation = rotation
166
+ }
167
+ }
168
+
169
+ // Attach components to entities
170
+ registry.emplace(entity, Health, 100)
171
+ registry.emplace(entity, Transform, 10, 20, 0)
172
+
173
+ // Retrieve components
174
+ const health = registry.get(entity, Health)
175
+ const transform = registry.get(entity, Transform)
176
+
177
+ // Check component existence
178
+ registry.allOf(entity, Health, Transform) // true
179
+ registry.anyOf(entity, Health) // true
180
+
181
+ // Remove components
182
+ registry.remove(entity, Health)
183
+ ```
184
+
185
+ ### Views
186
+
187
+ Views provide efficient iteration over entities with specific component sets:
188
+
189
+ ```typescript
190
+ // Create a view for entities with Position and Velocity
191
+ const view = registry.view([Position, Velocity])
192
+
193
+ // Iterate with entity and components
194
+ view.each((entity, position, velocity) => {
195
+ position.x += velocity.dx
196
+ position.y += velocity.dy
197
+ })
198
+
199
+ // Iterate with components only
200
+ view.each((position, velocity) => {
201
+ console.log(`Position: (${position.x}, ${position.y})`)
202
+ }, true)
203
+
204
+ // Exclude certain components
205
+ const viewWithExclusion = registry.view([Position], [Velocity])
206
+ ```
207
+
208
+ ### Groups
209
+
210
+ Groups offer even better performance for frequently accessed component combinations:
211
+
212
+ ```typescript
213
+ // Owning group - optimizes storage layout
214
+ const group = registry.group([Position, Velocity])
215
+
216
+ group.each((entity, position, velocity) => {
217
+ // High-performance iteration
218
+ position.x += velocity.dx
219
+ position.y += velocity.dy
220
+ })
221
+
222
+ // Non-owning group with additional components
223
+ const complexGroup = registry.group([], [Position, Velocity, Health])
224
+ ```
225
+
226
+ ### Signals
227
+
228
+ React to component lifecycle events:
229
+
230
+ ```typescript
231
+ // Listen for component creation
232
+ registry.onConstruct(Position).connect((registry, entity) => {
233
+ console.log(`Position component added to entity ${entity}`)
234
+ })
235
+
236
+ // Listen for component updates
237
+ registry.onUpdate(Health).connect((registry, entity) => {
238
+ const health = registry.get(entity, Health)
239
+ console.log(`Health updated to ${health.value}`)
240
+ })
241
+
242
+ // Listen for component destruction
243
+ registry.onDestroy(Position).connect((registry, entity) => {
244
+ console.log(`Position component removed from entity ${entity}`)
245
+ })
246
+ ```
247
+
248
+ ### Sorting
249
+
250
+ Sort entities based on component values:
251
+
252
+ ```typescript
253
+ // Sort by component property
254
+ registry.sort(Position, (a, b) => a.x - b.x)
255
+
256
+ // Sort by entity relationship
257
+ registry.sortByEntity(Position, (e1, e2) => e1 - e2)
258
+
259
+ // Sort to match another component's order
260
+ registry.sortAs(Velocity, Position)
261
+ ```
262
+
263
+ ## Advanced Features
264
+
265
+ ### Runtime Views
266
+
267
+ When component types aren't known at compile time:
268
+
269
+ ```typescript
270
+ import { RuntimeView } from 'entt-js'
271
+
272
+ const runtimeView = new RuntimeView()
273
+ runtimeView.iterate(registry.getStorage(Position))
274
+ runtimeView.iterate(registry.getStorage(Velocity))
275
+
276
+ for (const entity of runtimeView) {
277
+ // Process entities
278
+ }
279
+ ```
280
+
281
+ ### Snapshots
282
+
283
+ Save and restore registry state:
284
+
285
+ ```typescript
286
+ import { Snapshot, SnapshotLoader, Registry } from 'entt-js'
287
+
288
+ const registry = new Registry()
289
+ const snapshot = new Snapshot(registry)
290
+ const output = {
291
+ saveSize(size) { /* ... */ }
292
+ saveEntity(entity) { /* ... */ }
293
+ saveComponent(component) { /* ... */ }
294
+ }
295
+ snapshot
296
+ .get(output, Position)
297
+ .get(output, Velocity)
298
+
299
+ // Load snapshot into another registry
300
+ const newRegistry = new Registry()
301
+ const loader = new SnapshotLoader(newRegistry)
302
+ const input = {
303
+ loadSize(ref) { ref.set(/* size */) }
304
+ loadEntity(ref) { ref.set(/* entity */) }
305
+ loadComponent(ref) {
306
+ const defaultComponent = ref.get()
307
+ ref.set(/* ... */)
308
+ }
309
+ }
310
+ loader
311
+ .get(input, Position)
312
+ .get(input, Velocity)
313
+ ```
314
+
315
+ ### Custom Entity Types
316
+
317
+ Use custom entity identifiers:
318
+
319
+ ```typescript
320
+ import { basicRegistryTemplate } from 'entt-js'
321
+
322
+ // Use BigInt entities for larger capacity
323
+ const BigIntRegistry = basicRegistryTemplate.instantiate(BigInt)
324
+ const bigintRegistry = new BigIntRegistry()
325
+
326
+ // Custom entity class
327
+ class EntityObject {
328
+ // static member `EntityType` is required
329
+ static EntityType = Number
330
+
331
+ version: number
332
+ value: number
333
+
334
+ constructor(value = 0) {
335
+ this.version = ((value >>> 20) & 0xFFF) >>> 0
336
+ this.value = (value & 0xFFFFF) >>> 0
337
+ }
338
+
339
+ // required for internal implicit convertion
340
+ [Symbol.toPrimitive]() {
341
+ return ((this.value & 0xFFFFF)
342
+ | ((this.version & 0xFFF) << 20)) >>> 0
343
+ }
344
+ }
345
+
346
+ const EntityObjectRegistry = basicRegistryTemplate.instantiate(EntityObject)
347
+ const entityObjectRegistry = new EntityObjectRegistry()
348
+ ```
349
+
350
+ ### Config Flags
351
+
352
+ The library supports several compile-time configuration flags to customize behavior and optimize performance:
353
+
354
+ #### Available Flags
355
+
356
+ | Flag | Type | Default | Description |
357
+ |------|------|---------|-------------|
358
+ | `ENTT_SPARSE_PAGE` | `number` | `4096` | Size of sparse array pages (affects memory layout) |
359
+ | `ENTT_PACKED_PAGE` | `number` | `1024` | Size of packed array pages (affects memory layout) |
360
+ | `ENTT_NO_ETO` | `boolean` | `false` | Disable Empty Type Optimization (ETO) |
361
+ | `ENTT_NO_MIXIN` | `boolean` | `false` | Disable signal mixin functionality |
362
+
363
+ #### Usage in Different Environments
364
+
365
+ **Node.js (without bundler):**
366
+
367
+ Define flags as global variables before importing the library:
368
+
369
+ ```javascript
370
+ global.ENTT_SPARSE_PAGE = 8192
371
+ global.ENTT_PACKED_PAGE = 2048
372
+ global.ENTT_NO_ETO = true
373
+ global.ENTT_NO_MIXIN = false
374
+
375
+ const { Registry } = require('entt-js')
376
+ ```
377
+
378
+ **Browser (no bundler):**
379
+
380
+ Define flags on the window object before loading the script:
381
+
382
+ ```html
383
+ <script>
384
+ window.ENTT_SPARSE_PAGE = 8192
385
+ window.ENTT_PACKED_PAGE = 2048
386
+ window.ENTT_NO_ETO = true
387
+ window.ENTT_NO_MIXIN = false
388
+ </script>
389
+ <script src="https://cdn.jsdelivr.net/npm/entt-js/dist/browser/entt.min.js"></script>
390
+ ```
391
+
392
+ **With Bundler (Webpack, Vite, Rollup, etc.):**
393
+
394
+ Use bundler's define plugin to set flags at build time:
395
+
396
+ ```javascript
397
+ // vite.config.js
398
+ export default {
399
+ define: {
400
+ ENTT_SPARSE_PAGE: 8192,
401
+ ENTT_PACKED_PAGE: 2048,
402
+ ENTT_NO_ETO: true,
403
+ ENTT_NO_MIXIN: false
404
+ }
405
+ }
406
+
407
+ // webpack.config.js
408
+ module.exports = {
409
+ plugins: [
410
+ new webpack.DefinePlugin({
411
+ ENTT_SPARSE_PAGE: 8192,
412
+ ENTT_PACKED_PAGE: 2048,
413
+ ENTT_NO_ETO: true,
414
+ ENTT_NO_MIXIN: false
415
+ })
416
+ ]
417
+ }
418
+
419
+ // rollup.config.js
420
+ import replace from '@rollup/plugin-replace'
421
+
422
+ export default {
423
+ plugins: [
424
+ replace({
425
+ ENTT_SPARSE_PAGE: 8192,
426
+ ENTT_PACKED_PAGE: 2048,
427
+ ENTT_NO_ETO: true,
428
+ ENTT_NO_MIXIN: false,
429
+ preventAssignment: true
430
+ })
431
+ ]
432
+ }
433
+ ```
434
+
435
+ #### Flag Details
436
+
437
+ **ENTT_SPARSE_PAGE**: Controls the page size for sparse arrays in the sparse set implementation. Larger values use more memory but may reduce allocations. Adjust based on your entity count and memory constraints.
438
+
439
+ **ENTT_PACKED_PAGE**: Controls the page size for packed component arrays. Larger values improve cache locality for component iteration but increase memory overhead.
440
+
441
+ **ENTT_NO_ETO**: When `true`, disables Empty Type Optimization. ETO allows empty components (tag components) to avoid memory allocation. Disable if you encounter issues with empty class detection.
442
+
443
+ **ENTT_NO_MIXIN**: When `true`, disables the signal mixin system. This removes lifecycle event support (`onConstruct`, `onUpdate`, `onDestroy`) but slightly reduces memory overhead.
444
+
445
+ ## Performance
446
+
447
+ The library is designed for high performance with:
448
+
449
+ - **Sparse Set Architecture**: O(1) component access and iteration
450
+ - **Cache-Friendly Layouts**: Contiguous memory for better CPU cache utilization
451
+ - **Efficient Iteration**: Direct array access without indirection
452
+ - **Minimal Allocations**: Object pooling and reuse where possible
453
+
454
+ ### Benchmark Results
455
+
456
+ Performance benchmarks with 1,000,000 entities on Node.js v24 (Apple M2):
457
+
458
+ | Operation | Time | Description |
459
+ |-----------|------|-------------|
460
+ | Create entities | 0.55s | Creating 1M entities |
461
+ | Single component iteration | 0.013s | Iterating over 1M entities with 1 component |
462
+ | Two components iteration | 0.40s | Iterating over 1M entities with 2 components |
463
+ | Owning group iteration | 0.25s | Iterating 1M entities in full owning group (2 components) |
464
+ | Component access (registry) | 0.12s | Getting component from registry for 1M entities |
465
+ | Component access (view) | 0.11s | Getting component from view for 1M entities |
466
+
467
+ > **Note**: These are raw JavaScript performance numbers. While not matching native C++ speeds, the library provides excellent performance for JS-based applications and games.
468
+
469
+ ### Real-World Performance
470
+
471
+ For practical game development scenarios:
472
+
473
+ - **Typical games** process 1,000-50,000 entities per frame
474
+ - At 60 FPS (16.67ms budget per frame):
475
+ - Iterating 10,000 entities with 2 components: ~0.004ms
476
+ - Iterating 50,000 entities with 2 components: ~0.02ms
477
+ - **Iteration overhead is negligible** - rendering and game logic are typically the bottlenecks
478
+
479
+ **Performance characteristics:**
480
+ - ✅ Component iteration speed rivals native array performance
481
+ - ✅ Owning groups provide 1.6x speedup over regular views
482
+ - ✅ Component access is near-optimal (~0.0001ms per operation)
483
+
484
+ **Compared to alternatives:**
485
+ - More user-friendly than ArrayBuffer-based libraries (e.g., bitECS) while maintaining competitive performance
486
+ - Superior type safety and developer experience compared to other JS ECS implementations
487
+ - Excellent performance/usability balance for TypeScript projects
488
+
489
+ Run benchmarks yourself:
490
+ ```bash
491
+ npm run benchmark
492
+ ```
493
+
494
+ See `tests/benchmark` for detailed performance tests.
495
+
496
+ ## Development
497
+
498
+ Install dependencies:
499
+
500
+ Node.js v24+
501
+
502
+ ```bash
503
+ npm install
504
+ ```
505
+
506
+ Run tests:
507
+
508
+ ```bash
509
+ npm run test
510
+ ```
511
+
512
+ Run benchmarks:
513
+
514
+ ```bash
515
+ npm run benchmark
516
+ ```
517
+
518
+ Build the library:
519
+
520
+ ```bash
521
+ npm run build
522
+ ```
523
+
524
+ Type checking:
525
+
526
+ ```bash
527
+ npm run typecheck
528
+ ```
529
+
530
+ ## Differences from C++ EnTT
531
+
532
+ While this library aims to maintain API compatibility with the original EnTT, some differences exist due to TypeScript/JavaScript limitations:
533
+
534
+ - **No Template Specialization**: Uses runtime type registration instead
535
+ - **Memory Management**: Relies on JavaScript garbage collection
536
+ - **Performance**: Generally slower than C++ but highly optimized for JS
537
+ - **Type Safety**: Leverages TypeScript's type system for compile-time safety
538
+
539
+ ## Credits
540
+
541
+ This project is a TypeScript port of [EnTT](https://github.com/skypjack/entt) by [@skypjack](https://github.com/skypjack/entt).
542
+
543
+ ## Related Projects
544
+
545
+ - [EnTT](https://github.com/skypjack/entt) - The original C++ implementation
546
+ - [bitecs](https://github.com/NateTheGreatt/bitECS) - Another ECS library for JavaScript
547
+
548
+ ## Links
549
+
550
+ - [GitHub Repository](https://github.com/toyobayashi/entt-js)
551
+ - [npm Package](https://www.npmjs.com/package/entt-js)
552
+ - [Issue Tracker](https://github.com/toyobayashi/entt-js/issues)