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 +552 -0
- package/dist/browser/entt.js +4004 -0
- package/dist/browser/entt.min.js +1 -0
- package/dist/browser/index.js +3929 -0
- package/dist/browser/index.min.js +1 -0
- package/dist/entt.d.ts +1069 -0
- package/dist/entt.js +3944 -0
- package/dist/entt.min.js +1 -0
- package/package.json +77 -0
package/README.md
ADDED
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
# entt-js
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/entt-js)
|
|
4
|
+
[](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)
|