archetype-ecs 1.2.0 → 1.3.1
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 +24 -31
- package/package.json +1 -1
- package/src/EntityManager.js +118 -2
- package/src/index.d.ts +7 -0
- package/.claude/settings.local.json +0 -32
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<br><br>
|
|
5
5
|
<strong>archetype-ecs</strong>
|
|
6
6
|
<br>
|
|
7
|
-
<sub>
|
|
7
|
+
<sub>ECS with TypedArray storage. No dependencies.</sub>
|
|
8
8
|
<br><br>
|
|
9
9
|
<a href="https://www.npmjs.com/package/archetype-ecs"><img src="https://img.shields.io/npm/v/archetype-ecs.svg?style=flat-square&color=000" alt="npm" /></a>
|
|
10
10
|
<img src="https://img.shields.io/badge/gzip-~5kb-000?style=flat-square" alt="size" />
|
|
@@ -13,16 +13,12 @@
|
|
|
13
13
|
|
|
14
14
|
---
|
|
15
15
|
|
|
16
|
-
Entities grouped
|
|
16
|
+
An Entity Component System for games and simulations in JavaScript. Entities with the same components are grouped into archetypes, and their fields are stored in TypedArrays — so iterating a million entities is a tight loop over contiguous memory, not a scatter of object lookups.
|
|
17
17
|
|
|
18
18
|
```
|
|
19
19
|
npm i archetype-ecs
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
### The full picture in 20 lines
|
|
25
|
-
|
|
26
22
|
```ts
|
|
27
23
|
import { createEntityManager, component } from 'archetype-ecs'
|
|
28
24
|
|
|
@@ -50,18 +46,15 @@ em.forEach([Position, Velocity], (arch) => {
|
|
|
50
46
|
})
|
|
51
47
|
```
|
|
52
48
|
|
|
53
|
-
Define components, spawn entities, iterate with raw TypedArrays — no allocations, no cache misses, full type safety.
|
|
54
|
-
|
|
55
49
|
---
|
|
56
50
|
|
|
57
51
|
### Why archetype-ecs?
|
|
58
52
|
|
|
59
53
|
<table>
|
|
60
|
-
<tr><td><strong>
|
|
61
|
-
<tr><td><strong>
|
|
62
|
-
<tr><td><strong>
|
|
63
|
-
<tr><td><strong>
|
|
64
|
-
<tr><td><strong>Zero dependencies</strong></td><td>~5kb gzipped. No build step. Ships as ES modules.</td></tr>
|
|
54
|
+
<tr><td><strong>Fast iteration</strong></td><td>1.7 ms/frame over 1M entities. Faster than bitecs, wolf-ecs, harmony-ecs — see <a href="#benchmarks">benchmarks</a>.</td></tr>
|
|
55
|
+
<tr><td><strong>Low memory</strong></td><td>86 MB for 1M entities. Sparse-array ECS libraries use up to 2.4x more.</td></tr>
|
|
56
|
+
<tr><td><strong>No allocations</strong></td><td><code>get</code>, <code>set</code>, and <code>forEach</code> don't allocate.</td></tr>
|
|
57
|
+
<tr><td><strong>Typed</strong></td><td>TypeScript generics throughout. Field names autocomplete, wrong fields don't compile.</td></tr>
|
|
65
58
|
</table>
|
|
66
59
|
|
|
67
60
|
---
|
|
@@ -71,12 +64,12 @@ Define components, spawn entities, iterate with raw TypedArrays — no allocatio
|
|
|
71
64
|
```ts
|
|
72
65
|
import { createEntityManager, component } from 'archetype-ecs'
|
|
73
66
|
|
|
74
|
-
// Numeric —
|
|
67
|
+
// Numeric — stored as TypedArrays
|
|
75
68
|
const Position = component('Position', 'f32', ['x', 'y'])
|
|
76
69
|
const Velocity = component('Velocity', 'f32', ['vx', 'vy'])
|
|
77
70
|
const Health = component('Health', { hp: 'i32', maxHp: 'i32' })
|
|
78
71
|
|
|
79
|
-
// Strings —
|
|
72
|
+
// Strings — stored as arrays, same API
|
|
80
73
|
const Name = component('Name', 'string', ['name', 'title'])
|
|
81
74
|
|
|
82
75
|
// Mixed — numeric and string fields in one component
|
|
@@ -100,7 +93,7 @@ em.addComponent(player, Velocity, { vx: 0, vy: 0 })
|
|
|
100
93
|
em.addComponent(player, Health, { hp: 100, maxHp: 100 })
|
|
101
94
|
em.addComponent(player, Name, { name: 'Hero', title: 'Sir' })
|
|
102
95
|
|
|
103
|
-
// Or all at once
|
|
96
|
+
// Or all at once
|
|
104
97
|
for (let i = 0; i < 10_000; i++) {
|
|
105
98
|
em.createEntityWith(
|
|
106
99
|
Position, { x: Math.random() * 800, y: Math.random() * 600 },
|
|
@@ -117,7 +110,7 @@ em.destroyEntity(player)
|
|
|
117
110
|
### Read & write
|
|
118
111
|
|
|
119
112
|
```js
|
|
120
|
-
//
|
|
113
|
+
// Access a single field (doesn't allocate)
|
|
121
114
|
em.get(player, Position.x) // 0
|
|
122
115
|
em.get(player, Name.name) // 'Hero'
|
|
123
116
|
em.set(player, Velocity.vx, 5)
|
|
@@ -131,9 +124,9 @@ em.getComponent(player, Name) // { name: 'Hero', title: 'Sir' }
|
|
|
131
124
|
|
|
132
125
|
Two ways to work with entities in bulk. Pick the right one for the job:
|
|
133
126
|
|
|
134
|
-
#### `forEach` —
|
|
127
|
+
#### `forEach` — bulk processing
|
|
135
128
|
|
|
136
|
-
|
|
129
|
+
Iterates over matching archetypes. You get the backing TypedArrays directly.
|
|
137
130
|
|
|
138
131
|
```js
|
|
139
132
|
function movementSystem(dt) {
|
|
@@ -152,7 +145,7 @@ function movementSystem(dt) {
|
|
|
152
145
|
|
|
153
146
|
#### `query` — when you need entity IDs
|
|
154
147
|
|
|
155
|
-
|
|
148
|
+
Returns entity IDs for when you need to target specific entities.
|
|
156
149
|
|
|
157
150
|
```js
|
|
158
151
|
// Find the closest enemy to the player
|
|
@@ -182,7 +175,7 @@ const total = em.count([Position])
|
|
|
182
175
|
| **Use for** | Movement, physics, rendering | Damage events, UI, spawning |
|
|
183
176
|
| **Runs** | Every frame | On demand |
|
|
184
177
|
| **Allocates** | Nothing | `number[]` of entity IDs |
|
|
185
|
-
| **Access** |
|
|
178
|
+
| **Access** | TypedArrays by field | `get` / `set` by entity ID |
|
|
186
179
|
|
|
187
180
|
### Serialize
|
|
188
181
|
|
|
@@ -200,13 +193,13 @@ const json = JSON.stringify(snapshot)
|
|
|
200
193
|
em.deserialize(JSON.parse(json), { Position, Velocity, Health })
|
|
201
194
|
```
|
|
202
195
|
|
|
203
|
-
|
|
196
|
+
Supports stripping components, skipping entities, and custom serializers.
|
|
204
197
|
|
|
205
198
|
---
|
|
206
199
|
|
|
207
200
|
## TypeScript
|
|
208
201
|
|
|
209
|
-
|
|
202
|
+
Component types are inferred from their definition. Field names autocomplete, wrong fields are compile errors.
|
|
210
203
|
|
|
211
204
|
```ts
|
|
212
205
|
// Schema is inferred — Position becomes ComponentDef<{ x: number; y: number }>
|
|
@@ -269,17 +262,17 @@ Returns an entity manager with the following methods:
|
|
|
269
262
|
| Method | Description |
|
|
270
263
|
|---|---|
|
|
271
264
|
| `createEntity()` | Create an empty entity |
|
|
272
|
-
| `createEntityWith(Comp, data, ...)` | Create entity with components
|
|
265
|
+
| `createEntityWith(Comp, data, ...)` | Create entity with components in one call |
|
|
273
266
|
| `destroyEntity(id)` | Remove entity and all its components |
|
|
274
267
|
| `addComponent(id, Comp, data)` | Add a component to an existing entity |
|
|
275
268
|
| `removeComponent(id, Comp)` | Remove a component |
|
|
276
269
|
| `hasComponent(id, Comp)` | Check if entity has a component |
|
|
277
270
|
| `getComponent(id, Comp)` | Get component data as object *(allocates)* |
|
|
278
|
-
| `get(id, Comp.field)` | Read a single field
|
|
279
|
-
| `set(id, Comp.field, value)` | Write a single field
|
|
271
|
+
| `get(id, Comp.field)` | Read a single field |
|
|
272
|
+
| `set(id, Comp.field, value)` | Write a single field |
|
|
280
273
|
| `query(include, exclude?)` | Get matching entity IDs |
|
|
281
274
|
| `count(include, exclude?)` | Count matching entities |
|
|
282
|
-
| `forEach(include, callback, exclude?)` | Iterate archetypes with
|
|
275
|
+
| `forEach(include, callback, exclude?)` | Iterate archetypes with TypedArray access |
|
|
283
276
|
| `serialize(symbolToName, strip?, skip?, opts?)` | Serialize world to JSON-friendly object |
|
|
284
277
|
| `deserialize(data, nameToSymbol, opts?)` | Restore world from serialized data |
|
|
285
278
|
|
|
@@ -289,7 +282,7 @@ Returns an entity manager with the following methods:
|
|
|
289
282
|
|
|
290
283
|
1M entities, Position += Velocity, 5 runs (median), Node.js:
|
|
291
284
|
|
|
292
|
-
| | archetype-ecs | bitecs | wolf-ecs | harmony-ecs | miniplex |
|
|
285
|
+
| | archetype-ecs | [bitecs](https://github.com/NateTheGreatt/bitECS) | [wolf-ecs](https://github.com/EnderShadow8/wolf-ecs) | [harmony-ecs](https://github.com/3mcd/harmony-ecs) | [miniplex](https://github.com/hmans/miniplex) |
|
|
293
286
|
|---|---:|---:|---:|---:|---:|
|
|
294
287
|
| **Iteration** (ms/frame) | **1.7** | 2.2 | 2.2 | 1.8 | 32.5 |
|
|
295
288
|
| **Entity creation** (ms) | 401 | 366 | **106** | 248 | 265 |
|
|
@@ -311,7 +304,7 @@ em.forEach([Position, Velocity], (arch) => {
|
|
|
311
304
|
})
|
|
312
305
|
```
|
|
313
306
|
|
|
314
|
-
archetype-ecs
|
|
307
|
+
archetype-ecs is fastest at iteration. Harmony-ecs and wolf-ecs are close; miniplex is ~20x slower due to object-based storage.
|
|
315
308
|
|
|
316
309
|
Run them yourself:
|
|
317
310
|
|
|
@@ -323,7 +316,7 @@ npm run bench
|
|
|
323
316
|
|
|
324
317
|
## Feature comparison
|
|
325
318
|
|
|
326
|
-
|
|
319
|
+
Compared against other JS ECS libraries:
|
|
327
320
|
|
|
328
321
|
### Unique to archetype-ecs
|
|
329
322
|
|
|
@@ -350,7 +343,7 @@ How archetype-ecs stacks up against other JS ECS libraries:
|
|
|
350
343
|
|
|
351
344
|
✓✓ = notably stronger implementation in that library.
|
|
352
345
|
|
|
353
|
-
archetype-ecs is the only
|
|
346
|
+
archetype-ecs is the only one that combines fast iteration, string storage, serialization, and type safety.
|
|
354
347
|
|
|
355
348
|
---
|
|
356
349
|
|
package/package.json
CHANGED
package/src/EntityManager.js
CHANGED
|
@@ -46,10 +46,39 @@ function soaSwap(store, idxA, idxB) {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
function createSnapshotStore(schema, capacity) {
|
|
50
|
+
const snap = {};
|
|
51
|
+
for (const [field, Ctor] of Object.entries(schema)) {
|
|
52
|
+
snap[field] = new Ctor(capacity);
|
|
53
|
+
}
|
|
54
|
+
return snap;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function growSnapshotStore(snap, schema, newCapacity) {
|
|
58
|
+
for (const [field, Ctor] of Object.entries(schema)) {
|
|
59
|
+
const old = snap[field];
|
|
60
|
+
snap[field] = new Ctor(newCapacity);
|
|
61
|
+
if (Ctor === Array) {
|
|
62
|
+
for (let i = 0; i < old.length; i++) snap[field][i] = old[i];
|
|
63
|
+
} else {
|
|
64
|
+
snap[field].set(old);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
49
69
|
export function createEntityManager() {
|
|
50
70
|
let nextId = 1;
|
|
51
71
|
const allEntityIds = new Set();
|
|
52
72
|
|
|
73
|
+
// Change tracking (opt-in via enableTracking)
|
|
74
|
+
let trackFilter = 0; // bitmask — only track archetypes matching this
|
|
75
|
+
let createdSet = null; // Set<EntityId>
|
|
76
|
+
let destroyedSet = null; // Set<EntityId>
|
|
77
|
+
|
|
78
|
+
// Double-buffered snapshots: tracked archetypes get back-buffer arrays
|
|
79
|
+
// Game systems write to front (normal SoA arrays), flushSnapshots copies front→back
|
|
80
|
+
const trackedArchetypes = []; // archetypes that match trackFilter
|
|
81
|
+
|
|
53
82
|
// Component bit registry (symbol → bit index 0..31)
|
|
54
83
|
const componentBitIndex = new Map();
|
|
55
84
|
let nextBitIndex = 0;
|
|
@@ -84,20 +113,29 @@ export function createEntityManager() {
|
|
|
84
113
|
const key = computeMask(types);
|
|
85
114
|
let arch = archetypes.get(key);
|
|
86
115
|
if (!arch) {
|
|
116
|
+
const tracked = trackFilter !== 0 && (key & trackFilter) !== 0;
|
|
87
117
|
arch = {
|
|
88
118
|
key,
|
|
89
119
|
types: new Set(types),
|
|
90
120
|
entityIds: [],
|
|
91
121
|
components: new Map(),
|
|
122
|
+
snapshots: tracked ? new Map() : null,
|
|
123
|
+
snapshotEntityIds: tracked ? [] : null,
|
|
124
|
+
snapshotCount: 0,
|
|
92
125
|
entityToIndex: new Map(),
|
|
93
126
|
count: 0,
|
|
94
127
|
capacity: INITIAL_CAPACITY
|
|
95
128
|
};
|
|
96
129
|
for (const t of types) {
|
|
97
130
|
const schema = componentSchemas.get(t);
|
|
98
|
-
|
|
131
|
+
const store = schema ? createSoAStore(schema, INITIAL_CAPACITY) : null;
|
|
132
|
+
arch.components.set(t, store);
|
|
133
|
+
if (tracked && store) {
|
|
134
|
+
arch.snapshots.set(t, createSnapshotStore(schema, INITIAL_CAPACITY));
|
|
135
|
+
}
|
|
99
136
|
}
|
|
100
137
|
archetypes.set(key, arch);
|
|
138
|
+
if (tracked) trackedArchetypes.push(arch);
|
|
101
139
|
queryCacheVersion++;
|
|
102
140
|
}
|
|
103
141
|
return arch;
|
|
@@ -108,7 +146,13 @@ export function createEntityManager() {
|
|
|
108
146
|
const newCap = arch.capacity * 2;
|
|
109
147
|
arch.capacity = newCap;
|
|
110
148
|
for (const [type, store] of arch.components) {
|
|
111
|
-
if (store)
|
|
149
|
+
if (store) {
|
|
150
|
+
growSoAStore(store, newCap);
|
|
151
|
+
if (arch.snapshots) {
|
|
152
|
+
const snap = arch.snapshots.get(type);
|
|
153
|
+
if (snap) growSnapshotStore(snap, store._schema, newCap);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
112
156
|
}
|
|
113
157
|
}
|
|
114
158
|
|
|
@@ -182,6 +226,7 @@ export function createEntityManager() {
|
|
|
182
226
|
destroyEntity(id) {
|
|
183
227
|
const arch = entityArchetype.get(id);
|
|
184
228
|
if (arch) {
|
|
229
|
+
if (destroyedSet && (arch.key & trackFilter)) destroyedSet.add(id);
|
|
185
230
|
removeFromArchetype(arch, id);
|
|
186
231
|
}
|
|
187
232
|
allEntityIds.delete(id);
|
|
@@ -224,6 +269,9 @@ export function createEntityManager() {
|
|
|
224
269
|
const arch = entityArchetype.get(entityId);
|
|
225
270
|
if (!arch || !arch.types.has(type)) return;
|
|
226
271
|
|
|
272
|
+
// If entity is leaving a tracked archetype, treat as destroyed
|
|
273
|
+
if (destroyedSet && (arch.key & trackFilter)) destroyedSet.add(entityId);
|
|
274
|
+
|
|
227
275
|
if (arch.types.size === 1) {
|
|
228
276
|
removeFromArchetype(arch, entityId);
|
|
229
277
|
return;
|
|
@@ -309,6 +357,7 @@ export function createEntityManager() {
|
|
|
309
357
|
const arch = getOrCreateArchetype(types);
|
|
310
358
|
addToArchetype(arch, id, map);
|
|
311
359
|
|
|
360
|
+
if (createdSet && (arch.key & trackFilter)) createdSet.add(id);
|
|
312
361
|
return id;
|
|
313
362
|
},
|
|
314
363
|
|
|
@@ -326,20 +375,87 @@ export function createEntityManager() {
|
|
|
326
375
|
for (let a = 0; a < matching.length; a++) {
|
|
327
376
|
const arch = matching[a];
|
|
328
377
|
if (arch.count === 0) continue;
|
|
378
|
+
const snaps = arch.snapshots;
|
|
329
379
|
const view = {
|
|
380
|
+
id: arch.key,
|
|
330
381
|
entityIds: arch.entityIds,
|
|
331
382
|
count: arch.count,
|
|
383
|
+
snapshotEntityIds: arch.snapshotEntityIds,
|
|
384
|
+
snapshotCount: arch.snapshotCount,
|
|
332
385
|
field(ref) {
|
|
333
386
|
const sym = ref._sym || ref;
|
|
334
387
|
const store = arch.components.get(sym);
|
|
335
388
|
if (!store) return undefined;
|
|
336
389
|
return store[ref._field];
|
|
390
|
+
},
|
|
391
|
+
snapshot(ref) {
|
|
392
|
+
if (!snaps) return undefined;
|
|
393
|
+
const sym = ref._sym || ref;
|
|
394
|
+
const snap = snaps.get(sym);
|
|
395
|
+
if (!snap) return undefined;
|
|
396
|
+
return snap[ref._field];
|
|
337
397
|
}
|
|
338
398
|
};
|
|
339
399
|
callback(view);
|
|
340
400
|
}
|
|
341
401
|
},
|
|
342
402
|
|
|
403
|
+
enableTracking(filterComponent) {
|
|
404
|
+
trackFilter = 1 << getBit(filterComponent);
|
|
405
|
+
createdSet = new Set();
|
|
406
|
+
destroyedSet = new Set();
|
|
407
|
+
// Retroactively add snapshots to existing matching archetypes
|
|
408
|
+
for (const arch of archetypes.values()) {
|
|
409
|
+
if ((arch.key & trackFilter) !== 0 && !arch.snapshots) {
|
|
410
|
+
arch.snapshots = new Map();
|
|
411
|
+
arch.snapshotEntityIds = [];
|
|
412
|
+
arch.snapshotCount = 0;
|
|
413
|
+
for (const [t, store] of arch.components) {
|
|
414
|
+
if (store) {
|
|
415
|
+
arch.snapshots.set(t, createSnapshotStore(store._schema, arch.capacity));
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
trackedArchetypes.push(arch);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
},
|
|
422
|
+
|
|
423
|
+
flushChanges() {
|
|
424
|
+
const result = { created: createdSet, destroyed: destroyedSet };
|
|
425
|
+
createdSet = new Set();
|
|
426
|
+
destroyedSet = new Set();
|
|
427
|
+
return result;
|
|
428
|
+
},
|
|
429
|
+
|
|
430
|
+
flushSnapshots() {
|
|
431
|
+
for (let a = 0; a < trackedArchetypes.length; a++) {
|
|
432
|
+
const arch = trackedArchetypes[a];
|
|
433
|
+
const count = arch.count;
|
|
434
|
+
// Copy entityIds
|
|
435
|
+
const eids = arch.entityIds;
|
|
436
|
+
const snapEids = arch.snapshotEntityIds;
|
|
437
|
+
for (let i = 0; i < count; i++) snapEids[i] = eids[i];
|
|
438
|
+
arch.snapshotCount = count;
|
|
439
|
+
// Copy all field arrays via .set() (one memcpy per field)
|
|
440
|
+
for (const [type, store] of arch.components) {
|
|
441
|
+
if (!store) continue;
|
|
442
|
+
const snap = arch.snapshots.get(type);
|
|
443
|
+
if (!snap) continue;
|
|
444
|
+
for (const field in store._schema) {
|
|
445
|
+
const src = store[field];
|
|
446
|
+
const dst = snap[field];
|
|
447
|
+
if (src.set) {
|
|
448
|
+
// TypedArray — use .set() for memcpy, only copy active region
|
|
449
|
+
dst.set(src.subarray(0, count));
|
|
450
|
+
} else {
|
|
451
|
+
// Regular Array (string fields)
|
|
452
|
+
for (let i = 0; i < count; i++) dst[i] = src[i];
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
},
|
|
458
|
+
|
|
343
459
|
serialize(symbolToName, stripComponents = [], skipEntitiesWith = [], { serializers } = {}) {
|
|
344
460
|
const stripSymbols = new Set(stripComponents.map(toSym));
|
|
345
461
|
const skipSymbols = new Set(skipEntitiesWith.map(toSym));
|
package/src/index.d.ts
CHANGED
|
@@ -41,9 +41,13 @@ type TypedArray = Float32Array | Float64Array | Int8Array | Int16Array | Int32Ar
|
|
|
41
41
|
|
|
42
42
|
// === ArchetypeView (forEach callback) ===
|
|
43
43
|
export interface ArchetypeView {
|
|
44
|
+
readonly id: number;
|
|
44
45
|
readonly entityIds: EntityId[];
|
|
45
46
|
readonly count: number;
|
|
47
|
+
readonly snapshotEntityIds: EntityId[] | null;
|
|
48
|
+
readonly snapshotCount: number;
|
|
46
49
|
field(ref: FieldRef<any>): TypedArray | unknown[] | undefined;
|
|
50
|
+
snapshot(ref: FieldRef<any>): TypedArray | unknown[] | undefined;
|
|
47
51
|
}
|
|
48
52
|
|
|
49
53
|
// === Serialize/Deserialize ===
|
|
@@ -76,6 +80,9 @@ export interface EntityManager {
|
|
|
76
80
|
createEntityWith(...args: unknown[]): EntityId;
|
|
77
81
|
count(include: ComponentDef[], exclude?: ComponentDef[]): number;
|
|
78
82
|
forEach(include: ComponentDef[], callback: (view: ArchetypeView) => void, exclude?: ComponentDef[]): void;
|
|
83
|
+
enableTracking(filterComponent: ComponentDef): void;
|
|
84
|
+
flushChanges(): { created: Set<EntityId>; destroyed: Set<EntityId> };
|
|
85
|
+
flushSnapshots(): void;
|
|
79
86
|
serialize(
|
|
80
87
|
symbolToName: Map<symbol, string>,
|
|
81
88
|
stripComponents?: ComponentDef[],
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"permissions": {
|
|
3
|
-
"allow": [
|
|
4
|
-
"Bash(node /mnt/d/Projects/ECS/bench/vs-bitecs.js:*)",
|
|
5
|
-
"Bash(node:*)",
|
|
6
|
-
"Bash(npm test:*)",
|
|
7
|
-
"Bash(git add:*)",
|
|
8
|
-
"Bash(git commit -m \"$\\(cat <<''EOF''\nAdd getField/setField for zero-allocation field access with TS generics\n\ngetField and setField read/write individual component fields directly\nfrom TypedArrays without reconstructing an object. TypeScript generics\non component\\(\\) flow through to autocomplete field names and catch\ninvalid fields at compile time.\n\nCo-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>\nEOF\n\\)\")",
|
|
9
|
-
"Bash(git push:*)",
|
|
10
|
-
"Bash(git commit -m \"$\\(cat <<''EOF''\nAdd compile-time type tests for TS generics validation\n\ntests/types.ts validates that component\\(\\) schema inference flows\nthrough getField, setField, addComponent, and forEach field\\(\\) with\n@ts-expect-error assertions for invalid field names. npm test now\nruns both runtime tests and tsc --noEmit.\n\nCo-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>\nEOF\n\\)\")",
|
|
11
|
-
"Bash(git -C /mnt/d/Projects/ECS status)",
|
|
12
|
-
"Bash(git -C /mnt/d/Projects/ECS diff)",
|
|
13
|
-
"Bash(git -C /mnt/d/Projects/ECS log --oneline -5)",
|
|
14
|
-
"Bash(git -C /mnt/d/Projects/ECS add bench/typed-vs-bitecs-1m.js src/ComponentRegistry.js src/EntityManager.js src/index.d.ts src/index.js tests/EntityManager.test.js tests/types.ts)",
|
|
15
|
-
"Bash(git -C /mnt/d/Projects/ECS commit -m \"$\\(cat <<''EOF''\nAdd field descriptor API: Position.x instead of string literals\n\nComponent objects now have field descriptors as properties, enabling\nem.get\\(id, Position.x\\) / em.set\\(id, Position.x, 5\\) for zero-alloc\nfield access and arch.field\\(Position.x\\) in forEach hot loops.\n\n- component\\(\\) supports short form: component\\(''Pos'', ''f32'', [''x'',''y'']\\)\n- toSym\\(\\) helper normalizes component objects to underlying symbols\n- Updated TS types: ComponentDef<T> with FieldRef descriptors\n- All tests and benchmarks updated to new API\n\nCo-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>\nEOF\n\\)\")",
|
|
16
|
-
"Bash(wc:*)",
|
|
17
|
-
"Bash(git commit:*)",
|
|
18
|
-
"WebSearch",
|
|
19
|
-
"WebFetch(domain:github.com)",
|
|
20
|
-
"WebFetch(domain:www.webgamedev.com)",
|
|
21
|
-
"WebFetch(domain:3mcd.github.io)",
|
|
22
|
-
"WebFetch(domain:www.npmjs.com)",
|
|
23
|
-
"WebFetch(domain:raw.githubusercontent.com)",
|
|
24
|
-
"Bash(npm view:*)",
|
|
25
|
-
"Bash(npm pack:*)",
|
|
26
|
-
"Bash(curl:*)",
|
|
27
|
-
"Bash(npm install:*)",
|
|
28
|
-
"Bash(npm run bench:*)",
|
|
29
|
-
"Bash(FILTER_BRANCH_SQUELCH_WARNING=1 git filter-branch:*)"
|
|
30
|
-
]
|
|
31
|
-
}
|
|
32
|
-
}
|