archetype-ecs 1.2.0 → 1.3.2
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 +126 -63
- package/bench/allocations-1m.js +1 -1
- package/bench/multi-ecs-bench.js +1 -1
- package/bench/typed-vs-bitecs-1m.js +1 -1
- package/bench/typed-vs-untyped.js +81 -81
- package/bench/vs-bitecs.js +31 -56
- package/dist/ComponentRegistry.d.ts +18 -0
- package/dist/ComponentRegistry.js +29 -0
- package/dist/EntityManager.d.ts +52 -0
- package/dist/EntityManager.js +891 -0
- package/dist/Profiler.d.ts +12 -0
- package/dist/Profiler.js +38 -0
- package/dist/System.d.ts +41 -0
- package/dist/System.js +159 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +29 -0
- package/dist/src/ComponentRegistry.d.ts +18 -0
- package/dist/src/ComponentRegistry.js +29 -0
- package/dist/src/EntityManager.d.ts +49 -0
- package/dist/src/EntityManager.js +853 -0
- package/dist/src/Profiler.d.ts +12 -0
- package/dist/src/Profiler.js +38 -0
- package/dist/src/System.d.ts +37 -0
- package/dist/src/System.js +139 -0
- package/dist/src/index.d.ts +14 -0
- package/dist/src/index.js +29 -0
- package/dist/tests/EntityManager.test.d.ts +1 -0
- package/dist/tests/EntityManager.test.js +651 -0
- package/dist/tests/System.test.d.ts +1 -0
- package/dist/tests/System.test.js +630 -0
- package/dist/tests/types.d.ts +1 -0
- package/dist/tests/types.js +129 -0
- package/package.json +9 -7
- package/src/ComponentRegistry.ts +49 -0
- package/src/EntityManager.ts +1018 -0
- package/src/{Profiler.js → Profiler.ts} +18 -5
- package/src/System.ts +226 -0
- package/src/index.ts +44 -0
- package/tests/{EntityManager.test.js → EntityManager.test.ts} +338 -70
- package/tests/System.test.ts +730 -0
- package/tests/types.ts +67 -66
- package/tsconfig.json +8 -5
- package/tsconfig.test.json +13 -0
- package/.claude/settings.local.json +0 -32
- package/src/ComponentRegistry.js +0 -21
- package/src/EntityManager.js +0 -462
- package/src/index.d.ts +0 -111
- package/src/index.js +0 -37
package/tests/types.ts
CHANGED
|
@@ -1,101 +1,102 @@
|
|
|
1
1
|
// Compile-time type tests — run with: npx tsc --noEmit
|
|
2
|
-
// These tests validate TS
|
|
2
|
+
// These tests validate TS types flow correctly through the API.
|
|
3
3
|
// No runtime execution needed; if this file compiles, the types are correct.
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
createEntityManager, createSystem, createSystems, component,
|
|
7
|
+
System, OnAdded, OnRemoved,
|
|
8
|
+
type ComponentDef, type FieldRef, type EntityId,
|
|
9
|
+
type SystemContext, type FunctionalSystemConstructor, type FunctionalSystem, type Pipeline
|
|
10
|
+
} from '../src/index.js';
|
|
6
11
|
|
|
7
|
-
// --- component()
|
|
12
|
+
// --- component() creates ComponentDef ---
|
|
8
13
|
const Position = component('Position', 'f32', ['x', 'y']);
|
|
9
14
|
const Velocity = component('Velocity', { vx: 'f32', vy: 'f32' });
|
|
10
15
|
const Tag = component('Tag');
|
|
11
16
|
const Name = component('Name', { name: 'string', title: 'string' });
|
|
12
17
|
const Label = component('Label', 'string', ['text', 'color']);
|
|
13
18
|
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
// Position.x should be a FieldRef<number>
|
|
19
|
-
type AssertFieldRef = typeof Position.x extends FieldRef<number> ? true : never;
|
|
20
|
-
const _assertField: AssertFieldRef = true;
|
|
21
|
-
|
|
22
|
-
// Name should be ComponentDef<{ name: string, title: string }>
|
|
23
|
-
type AssertName = typeof Name extends ComponentDef<{ name: string; title: string }> ? true : never;
|
|
24
|
-
const _assertName: AssertName = true;
|
|
25
|
-
|
|
26
|
-
// Name.name should be a FieldRef<string>
|
|
27
|
-
type AssertNameField = typeof Name.name extends FieldRef<string> ? true : never;
|
|
28
|
-
const _assertNameField: AssertNameField = true;
|
|
29
|
-
|
|
30
|
-
// Label short form: ComponentDef<{ text: string, color: string }>
|
|
31
|
-
type AssertLabel = typeof Label extends ComponentDef<{ text: string; color: string }> ? true : never;
|
|
32
|
-
const _assertLabel: AssertLabel = true;
|
|
33
|
-
|
|
34
|
-
// Tag should be ComponentDef<unknown> (no schema)
|
|
35
|
-
type AssertTag = typeof Tag extends ComponentDef ? true : never;
|
|
36
|
-
const _assertTag: AssertTag = true;
|
|
19
|
+
// component() returns ComponentDef
|
|
20
|
+
const _assertComp: ComponentDef = Position;
|
|
21
|
+
const _assertTag: ComponentDef = Tag;
|
|
37
22
|
|
|
38
23
|
// --- EntityManager typed methods ---
|
|
39
24
|
const em = createEntityManager();
|
|
40
25
|
const id: EntityId = em.createEntity();
|
|
41
26
|
|
|
42
|
-
// addComponent: accepts
|
|
27
|
+
// addComponent: accepts data
|
|
43
28
|
em.addComponent(id, Position, { x: 1, y: 2 });
|
|
44
29
|
em.addComponent(id, Velocity, { vx: 0, vy: 0 });
|
|
45
30
|
em.addComponent(id, Name, { name: 'Hero', title: 'Sir' });
|
|
46
31
|
|
|
47
|
-
// getComponent: returns
|
|
32
|
+
// getComponent: returns data or undefined
|
|
48
33
|
const pos = em.getComponent(id, Position);
|
|
49
|
-
if (pos) {
|
|
50
|
-
const x: number = pos.x;
|
|
51
|
-
const y: number = pos.y;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const nameComp = em.getComponent(id, Name);
|
|
55
|
-
if (nameComp) {
|
|
56
|
-
const n: string = nameComp.name;
|
|
57
|
-
const t: string = nameComp.title;
|
|
58
|
-
}
|
|
59
34
|
|
|
60
|
-
// get/set:
|
|
61
|
-
const px
|
|
62
|
-
const py: number | undefined = em.get(id, Position.y);
|
|
35
|
+
// get/set: field descriptor access
|
|
36
|
+
const px = em.get(id, Position.x);
|
|
63
37
|
em.set(id, Position.x, 10);
|
|
64
|
-
em.set(id, Position.y, 20);
|
|
65
|
-
|
|
66
|
-
// get/set: string field descriptor access
|
|
67
|
-
const nameVal: string | undefined = em.get(id, Name.name);
|
|
68
|
-
em.set(id, Name.name, 'Villain');
|
|
69
38
|
|
|
70
39
|
// forEach + field: accepts FieldRef
|
|
71
40
|
em.forEach([Position, Velocity], (arch) => {
|
|
72
41
|
const count: number = arch.count;
|
|
73
42
|
const ids: EntityId[] = arch.entityIds;
|
|
74
43
|
const arrX = arch.field(Position.x);
|
|
75
|
-
const arrVx = arch.field(Velocity.vx);
|
|
76
44
|
});
|
|
77
45
|
|
|
78
46
|
// createEntityWith: alternating type, data
|
|
79
47
|
em.createEntityWith(Position, { x: 0, y: 0 }, Velocity, { vx: 1, vy: 1 });
|
|
80
48
|
|
|
81
|
-
// ---
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
49
|
+
// --- createSystem returns FunctionalSystem ---
|
|
50
|
+
const sys: FunctionalSystem = createSystem(em, (s: SystemContext) => {
|
|
51
|
+
// onAdded with 1 type
|
|
52
|
+
s.onAdded(Position, (entityId: EntityId) => {});
|
|
53
|
+
// onAdded with 2 types
|
|
54
|
+
s.onAdded(Position, Velocity, (entityId: EntityId) => {});
|
|
55
|
+
// onRemoved with 1 type
|
|
56
|
+
s.onRemoved(Position, (entityId: EntityId) => {});
|
|
57
|
+
// onRemoved with 2 types
|
|
58
|
+
s.onRemoved(Position, Velocity, (entityId: EntityId) => {});
|
|
59
|
+
|
|
60
|
+
return () => {
|
|
61
|
+
s.forEach([Position, Velocity], (view) => {
|
|
62
|
+
const count: number = view.count;
|
|
63
|
+
});
|
|
64
|
+
s.forEach([Position], (_view) => {}, [Velocity]);
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
sys();
|
|
68
|
+
sys.dispose();
|
|
69
|
+
|
|
70
|
+
// --- FunctionalSystemConstructor type ---
|
|
71
|
+
const ctor: FunctionalSystemConstructor = (s) => {
|
|
72
|
+
s.onAdded(Position, (_id) => {});
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// --- createSystems returns Pipeline ---
|
|
76
|
+
const pipeline: Pipeline = createSystems(em, [ctor]);
|
|
77
|
+
pipeline();
|
|
78
|
+
pipeline.dispose();
|
|
79
|
+
|
|
80
|
+
// --- Class-based System ---
|
|
81
|
+
class TestSystem extends System {
|
|
82
|
+
@OnAdded(Position)
|
|
83
|
+
handleAdd(id: EntityId) {}
|
|
84
|
+
|
|
85
|
+
@OnRemoved(Position)
|
|
86
|
+
handleRemove(id: EntityId) {}
|
|
87
|
+
|
|
88
|
+
tick() {
|
|
89
|
+
this.forEach([Position], (view) => {
|
|
90
|
+
const count: number = view.count;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
96
94
|
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
const testSys = new TestSystem(em);
|
|
96
|
+
testSys.run();
|
|
97
|
+
testSys.dispose();
|
|
99
98
|
|
|
100
|
-
//
|
|
101
|
-
em
|
|
99
|
+
// --- createSystems accepts mixed entries ---
|
|
100
|
+
const mixedPipeline: Pipeline = createSystems(em, [ctor, TestSystem]);
|
|
101
|
+
mixedPipeline();
|
|
102
|
+
mixedPipeline.dispose();
|
package/tsconfig.json
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"compilerOptions": {
|
|
3
3
|
"strict": true,
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
4
|
+
"target": "ES2022",
|
|
5
|
+
"module": "NodeNext",
|
|
6
|
+
"moduleResolution": "NodeNext",
|
|
7
|
+
"declaration": true,
|
|
8
|
+
"outDir": "dist",
|
|
9
|
+
"rootDir": "src",
|
|
10
|
+
"skipLibCheck": true
|
|
8
11
|
},
|
|
9
|
-
"include": ["
|
|
12
|
+
"include": ["src/**/*.ts"]
|
|
10
13
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"strict": true,
|
|
4
|
+
"target": "ES2022",
|
|
5
|
+
"module": "NodeNext",
|
|
6
|
+
"moduleResolution": "NodeNext",
|
|
7
|
+
"declaration": false,
|
|
8
|
+
"outDir": "dist-test",
|
|
9
|
+
"rootDir": ".",
|
|
10
|
+
"skipLibCheck": true
|
|
11
|
+
},
|
|
12
|
+
"include": ["src/**/*.ts", "tests/**/*.ts"]
|
|
13
|
+
}
|
|
@@ -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
|
-
}
|
package/src/ComponentRegistry.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
export const TYPED = Symbol('typed');
|
|
2
|
-
|
|
3
|
-
export const TYPE_MAP = {
|
|
4
|
-
'f32': Float32Array,
|
|
5
|
-
'f64': Float64Array,
|
|
6
|
-
'i8': Int8Array,
|
|
7
|
-
'i16': Int16Array,
|
|
8
|
-
'i32': Int32Array,
|
|
9
|
-
'u8': Uint8Array,
|
|
10
|
-
'u16': Uint16Array,
|
|
11
|
-
'u32': Uint32Array,
|
|
12
|
-
'string': Array,
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
/** @type {Map<symbol, Record<string, typeof Float32Array>>} */
|
|
16
|
-
export const componentSchemas = new Map();
|
|
17
|
-
|
|
18
|
-
/** Extract the underlying symbol from a component object or pass through a plain symbol */
|
|
19
|
-
export function toSym(type) {
|
|
20
|
-
return type._sym || type;
|
|
21
|
-
}
|