archetype-ecs 1.4.2 → 1.5.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 +79 -14
- package/bench/allocations-1m.js +1 -1
- package/bench/component-churn-bench.js +193 -0
- package/bench/iterate.wat +95 -0
- package/bench/multi-ecs-bench.js +1 -1
- package/bench/run-js-vs-go-ts.sh +147 -0
- package/bench/typed-vs-bitecs-1m.js +1 -1
- package/bench/typed-vs-untyped.js +1 -1
- package/bench/vs-bitecs.js +1 -1
- package/bench/wasm-iteration-bench.js +289 -0
- package/dist/{src/ComponentRegistry.d.ts → ComponentRegistry.d.ts} +8 -3
- package/dist/{src/EntityManager.d.ts → EntityManager.d.ts} +15 -10
- package/dist/{src/EntityManager.js → EntityManager.js} +196 -95
- package/dist/{src/System.d.ts → System.d.ts} +4 -0
- package/dist/{src/System.js → System.js} +25 -5
- package/dist/WasmArena.d.ts +13 -0
- package/dist/WasmArena.js +48 -0
- package/dist/{src/index.d.ts → index.d.ts} +7 -9
- package/dist/{src/index.js → index.js} +2 -0
- package/dist/wasm-kernels.d.ts +10 -0
- package/dist/wasm-kernels.js +59 -0
- package/package.json +12 -7
- package/src/ComponentRegistry.ts +7 -3
- package/src/EntityManager.ts +209 -119
- package/src/System.ts +34 -9
- package/src/WasmArena.ts +83 -0
- package/src/index.ts +16 -11
- package/src/iterate.wat +135 -0
- package/src/wasm-kernels.ts +68 -0
- package/tests/EntityManager.test.ts +51 -86
- package/tests/System.test.ts +184 -0
- package/tests/types.ts +1 -1
- package/tsconfig.json +2 -2
- package/tsconfig.test.json +13 -0
- package/dist/tests/EntityManager.test.d.ts +0 -1
- package/dist/tests/EntityManager.test.js +0 -651
- package/dist/tests/System.test.d.ts +0 -1
- package/dist/tests/System.test.js +0 -630
- package/dist/tests/types.d.ts +0 -1
- package/dist/tests/types.js +0 -129
- /package/dist/{src/ComponentRegistry.js → ComponentRegistry.js} +0 -0
- /package/dist/{src/Profiler.d.ts → Profiler.d.ts} +0 -0
- /package/dist/{src/Profiler.js → Profiler.js} +0 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const ALIGN = 16; // 16-byte alignment for SIMD
|
|
2
|
+
export class WasmArena {
|
|
3
|
+
memory;
|
|
4
|
+
nextOffset = 0;
|
|
5
|
+
views = [];
|
|
6
|
+
constructor(initialPages = 2048, maxPages = 16384) {
|
|
7
|
+
this.memory = new WebAssembly.Memory({ initial: initialPages, maximum: maxPages });
|
|
8
|
+
}
|
|
9
|
+
alloc(byteLength) {
|
|
10
|
+
// Align up to 16 bytes
|
|
11
|
+
const aligned = (this.nextOffset + ALIGN - 1) & ~(ALIGN - 1);
|
|
12
|
+
const end = aligned + byteLength;
|
|
13
|
+
if (end > this.memory.buffer.byteLength) {
|
|
14
|
+
const needed = Math.ceil((end - this.memory.buffer.byteLength) / 65536);
|
|
15
|
+
this.memory.grow(needed);
|
|
16
|
+
this.handleGrow();
|
|
17
|
+
}
|
|
18
|
+
this.nextOffset = end;
|
|
19
|
+
return aligned;
|
|
20
|
+
}
|
|
21
|
+
createView(Ctor, offset, count, fields, field) {
|
|
22
|
+
const view = new Ctor(this.memory.buffer, offset, count);
|
|
23
|
+
this.views.push({ fields, field, offset, Ctor, count });
|
|
24
|
+
fields[field] = view;
|
|
25
|
+
return view;
|
|
26
|
+
}
|
|
27
|
+
handleGrow() {
|
|
28
|
+
const buf = this.memory.buffer;
|
|
29
|
+
for (const reg of this.views) {
|
|
30
|
+
reg.fields[reg.field] = new reg.Ctor(buf, reg.offset, reg.count);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** Update a registered view to point to a new (larger) allocation. */
|
|
34
|
+
updateView(fields, field, offset, Ctor, count) {
|
|
35
|
+
// Find and update the existing registration
|
|
36
|
+
for (const reg of this.views) {
|
|
37
|
+
if (reg.fields === fields && reg.field === field) {
|
|
38
|
+
reg.offset = offset;
|
|
39
|
+
reg.Ctor = Ctor;
|
|
40
|
+
reg.count = count;
|
|
41
|
+
fields[field] = new Ctor(this.memory.buffer, offset, count);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// Not found — register as new
|
|
46
|
+
this.createView(Ctor, offset, count, fields, field);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
export { createEntityManager } from './EntityManager.js';
|
|
2
|
-
export type { EntityId,
|
|
2
|
+
export type { EntityId, ArchetypeView, EntityManager, SerializedData } from './EntityManager.js';
|
|
3
3
|
export { createSystem, createSystems, System, OnAdded, OnRemoved } from './System.js';
|
|
4
4
|
export type { SystemContext, FunctionalSystemConstructor, FunctionalSystem, Pipeline } from './System.js';
|
|
5
5
|
export { profiler } from './Profiler.js';
|
|
6
6
|
export type { Profiler, ProfilerEntry } from './Profiler.js';
|
|
7
7
|
export { TYPED, componentSchemas, parseTypeSpec } from './ComponentRegistry.js';
|
|
8
|
-
export type { ComponentDef, TypeSpec } from './ComponentRegistry.js';
|
|
8
|
+
export type { ComponentDef, FieldRef, TypeSpec } from './ComponentRegistry.js';
|
|
9
|
+
export { WasmArena } from './WasmArena.js';
|
|
10
|
+
export { instantiateKernels, isWasmSimdAvailable } from './wasm-kernels.js';
|
|
11
|
+
export type { IterateKernels } from './wasm-kernels.js';
|
|
9
12
|
import { type ComponentDef } from './ComponentRegistry.js';
|
|
10
|
-
import type { FieldRef } from './EntityManager.js';
|
|
11
13
|
export declare function component(name: string): ComponentDef;
|
|
12
|
-
export declare function component<F extends readonly string[]>(name: string, type: string, fields: F): ComponentDef
|
|
13
|
-
|
|
14
|
-
};
|
|
15
|
-
export declare function component<S extends Record<string, string>>(name: string, schema: S): ComponentDef & {
|
|
16
|
-
readonly [K in keyof S]: FieldRef;
|
|
17
|
-
};
|
|
14
|
+
export declare function component<const F extends readonly string[]>(name: string, type: string, fields: F): ComponentDef<F[number]>;
|
|
15
|
+
export declare function component<S extends Record<string, string>>(name: string, schema: S): ComponentDef<Extract<keyof S, string>>;
|
|
@@ -2,6 +2,8 @@ export { createEntityManager } from './EntityManager.js';
|
|
|
2
2
|
export { createSystem, createSystems, System, OnAdded, OnRemoved } from './System.js';
|
|
3
3
|
export { profiler } from './Profiler.js';
|
|
4
4
|
export { TYPED, componentSchemas, parseTypeSpec } from './ComponentRegistry.js';
|
|
5
|
+
export { WasmArena } from './WasmArena.js';
|
|
6
|
+
export { instantiateKernels, isWasmSimdAvailable } from './wasm-kernels.js';
|
|
5
7
|
import { parseTypeSpec, componentSchemas } from './ComponentRegistry.js';
|
|
6
8
|
export function component(name, typeOrSchema, fields) {
|
|
7
9
|
const sym = Symbol(name);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface IterateKernels {
|
|
2
|
+
iterate_scalar(px: number, py: number, vx: number, vy: number, count: number): void;
|
|
3
|
+
iterate_simd(px: number, py: number, vx: number, vy: number, count: number): void;
|
|
4
|
+
add_f32(dst: number, src: number, count: number): void;
|
|
5
|
+
}
|
|
6
|
+
/** Returns true if the runtime supports WebAssembly SIMD. Result is cached. */
|
|
7
|
+
export declare function isWasmSimdAvailable(): boolean;
|
|
8
|
+
export declare function instantiateKernels(memory: WebAssembly.Memory): Promise<IterateKernels>;
|
|
9
|
+
/** Synchronous instantiation — only for small modules (<4KB). Used internally. */
|
|
10
|
+
export declare function instantiateKernelsSync(memory: WebAssembly.Memory): IterateKernels;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Auto-generated from src/iterate.wat — do not edit by hand
|
|
2
|
+
const ITERATE_WASM = new Uint8Array([
|
|
3
|
+
0, 97, 115, 109, 1, 0, 0, 0, 1, 15, 2, 96, 5, 127, 127, 127, 127, 127, 0, 96,
|
|
4
|
+
3, 127, 127, 127, 0, 2, 15, 1, 3, 101, 110, 118, 6, 109, 101, 109, 111, 114, 121, 2,
|
|
5
|
+
0, 1, 3, 4, 3, 0, 0, 1, 7, 43, 3, 14, 105, 116, 101, 114, 97, 116, 101, 95,
|
|
6
|
+
115, 99, 97, 108, 97, 114, 0, 0, 12, 105, 116, 101, 114, 97, 116, 101, 95, 115, 105, 109,
|
|
7
|
+
100, 0, 1, 7, 97, 100, 100, 95, 102, 51, 50, 0, 2, 10, 148, 3, 3, 87, 1, 2,
|
|
8
|
+
127, 65, 0, 33, 5, 2, 64, 3, 64, 32, 5, 32, 4, 79, 13, 1, 32, 5, 65, 2,
|
|
9
|
+
116, 33, 6, 32, 0, 32, 6, 106, 32, 0, 32, 6, 106, 42, 2, 0, 32, 2, 32, 6,
|
|
10
|
+
106, 42, 2, 0, 146, 56, 2, 0, 32, 1, 32, 6, 106, 32, 1, 32, 6, 106, 42, 2,
|
|
11
|
+
0, 32, 3, 32, 6, 106, 42, 2, 0, 146, 56, 2, 0, 32, 5, 65, 1, 106, 33, 5,
|
|
12
|
+
12, 0, 11, 11, 11, 183, 1, 1, 3, 127, 32, 4, 65, 124, 113, 33, 7, 65, 0, 33,
|
|
13
|
+
5, 2, 64, 3, 64, 32, 5, 32, 7, 79, 13, 1, 32, 5, 65, 2, 116, 33, 6, 32,
|
|
14
|
+
0, 32, 6, 106, 32, 0, 32, 6, 106, 253, 0, 4, 0, 32, 2, 32, 6, 106, 253, 0,
|
|
15
|
+
4, 0, 253, 228, 1, 253, 11, 4, 0, 32, 1, 32, 6, 106, 32, 1, 32, 6, 106, 253,
|
|
16
|
+
0, 4, 0, 32, 3, 32, 6, 106, 253, 0, 4, 0, 253, 228, 1, 253, 11, 4, 0, 32,
|
|
17
|
+
5, 65, 4, 106, 33, 5, 12, 0, 11, 11, 2, 64, 3, 64, 32, 5, 32, 4, 79, 13,
|
|
18
|
+
1, 32, 5, 65, 2, 116, 33, 6, 32, 0, 32, 6, 106, 32, 0, 32, 6, 106, 42, 2,
|
|
19
|
+
0, 32, 2, 32, 6, 106, 42, 2, 0, 146, 56, 2, 0, 32, 1, 32, 6, 106, 32, 1,
|
|
20
|
+
32, 6, 106, 42, 2, 0, 32, 3, 32, 6, 106, 42, 2, 0, 146, 56, 2, 0, 32, 5,
|
|
21
|
+
65, 1, 106, 33, 5, 12, 0, 11, 11, 11, 128, 1, 1, 3, 127, 32, 2, 65, 124, 113,
|
|
22
|
+
33, 5, 65, 0, 33, 3, 2, 64, 3, 64, 32, 3, 32, 5, 79, 13, 1, 32, 3, 65,
|
|
23
|
+
2, 116, 33, 4, 32, 0, 32, 4, 106, 32, 0, 32, 4, 106, 253, 0, 4, 0, 32, 1,
|
|
24
|
+
32, 4, 106, 253, 0, 4, 0, 253, 228, 1, 253, 11, 4, 0, 32, 3, 65, 4, 106, 33,
|
|
25
|
+
3, 12, 0, 11, 11, 2, 64, 3, 64, 32, 3, 32, 2, 79, 13, 1, 32, 3, 65, 2,
|
|
26
|
+
116, 33, 4, 32, 0, 32, 4, 106, 32, 0, 32, 4, 106, 42, 2, 0, 32, 1, 32, 4,
|
|
27
|
+
106, 42, 2, 0, 146, 56, 2, 0, 32, 3, 65, 1, 106, 33, 3, 12, 0, 11, 11, 11
|
|
28
|
+
]); // 500 bytes
|
|
29
|
+
// Cached compiled module (shared across all EntityManager instances)
|
|
30
|
+
let cachedModule = null;
|
|
31
|
+
function getCompiledModule() {
|
|
32
|
+
if (!cachedModule) {
|
|
33
|
+
cachedModule = new WebAssembly.Module(ITERATE_WASM);
|
|
34
|
+
}
|
|
35
|
+
return cachedModule;
|
|
36
|
+
}
|
|
37
|
+
/** Returns true if the runtime supports WebAssembly SIMD. Result is cached. */
|
|
38
|
+
export function isWasmSimdAvailable() {
|
|
39
|
+
if (cachedModule !== null)
|
|
40
|
+
return true;
|
|
41
|
+
try {
|
|
42
|
+
getCompiledModule();
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export async function instantiateKernels(memory) {
|
|
50
|
+
const module = getCompiledModule();
|
|
51
|
+
const instance = await WebAssembly.instantiate(module, { env: { memory } });
|
|
52
|
+
return instance.exports;
|
|
53
|
+
}
|
|
54
|
+
/** Synchronous instantiation — only for small modules (<4KB). Used internally. */
|
|
55
|
+
export function instantiateKernelsSync(memory) {
|
|
56
|
+
const module = getCompiledModule();
|
|
57
|
+
const instance = new WebAssembly.Instance(module, { env: { memory } });
|
|
58
|
+
return instance.exports;
|
|
59
|
+
}
|
package/package.json
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "archetype-ecs",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Lightweight archetype-based Entity Component System",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "dist/
|
|
7
|
-
"types": "dist/
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
|
-
"types": "./dist/
|
|
11
|
-
"default": "./dist/
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
14
|
"scripts": {
|
|
15
15
|
"build": "tsc",
|
|
16
|
-
"test": "tsc && node --test dist/tests/EntityManager.test.js dist/tests/System.test.js",
|
|
17
|
-
"bench": "node --expose-gc bench/multi-ecs-bench.js"
|
|
16
|
+
"test": "tsc && tsc -p tsconfig.test.json && node --test dist-test/tests/EntityManager.test.js dist-test/tests/System.test.js",
|
|
17
|
+
"bench": "node --expose-gc bench/multi-ecs-bench.js",
|
|
18
|
+
"bench:churn": "tsc && node --expose-gc bench/component-churn-bench.js",
|
|
19
|
+
"bench:tsc-vs-tsgo": "bash bench/run-js-vs-go-ts.sh"
|
|
18
20
|
},
|
|
19
21
|
"keywords": [
|
|
20
22
|
"ecs",
|
|
@@ -26,10 +28,13 @@
|
|
|
26
28
|
],
|
|
27
29
|
"license": "MIT",
|
|
28
30
|
"devDependencies": {
|
|
31
|
+
"@types/node": "^25.2.3",
|
|
32
|
+
"@typescript/native-preview": "^7.0.0-dev.20260220.1",
|
|
29
33
|
"bitecs": "^0.4.0",
|
|
30
34
|
"harmony-ecs": "^0.0.12",
|
|
31
35
|
"miniplex": "^2.0.0",
|
|
32
36
|
"typescript": "^5.9.3",
|
|
37
|
+
"wabt": "^1.0.39",
|
|
33
38
|
"wolf-ecs": "^2.0.0"
|
|
34
39
|
}
|
|
35
40
|
}
|
package/src/ComponentRegistry.ts
CHANGED
|
@@ -34,12 +34,16 @@ export function parseTypeSpec(typeStr: string): TypeSpec {
|
|
|
34
34
|
|
|
35
35
|
export const componentSchemas = new Map<symbol, Record<string, TypeSpec>>();
|
|
36
36
|
|
|
37
|
-
export interface
|
|
37
|
+
export interface FieldRef {
|
|
38
38
|
readonly _sym: symbol;
|
|
39
|
-
readonly
|
|
40
|
-
[key: string]: unknown;
|
|
39
|
+
readonly _field: string;
|
|
41
40
|
}
|
|
42
41
|
|
|
42
|
+
export type ComponentDef<F extends string = never> = {
|
|
43
|
+
readonly _sym: symbol;
|
|
44
|
+
readonly _name: string;
|
|
45
|
+
} & { readonly [K in F]: FieldRef };
|
|
46
|
+
|
|
43
47
|
export function toSym(type: ComponentDef | symbol): symbol {
|
|
44
48
|
return (type as ComponentDef)._sym || (type as symbol);
|
|
45
49
|
}
|