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
package/src/System.ts
CHANGED
|
@@ -66,7 +66,8 @@ export class System {
|
|
|
66
66
|
|
|
67
67
|
tick?(): void;
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
/** Process hooks and tick without clearing removed-data snapshots. */
|
|
70
|
+
_runCore(): void {
|
|
70
71
|
for (const hook of this._hooks) {
|
|
71
72
|
for (const id of hook.buffer) hook.handler(id);
|
|
72
73
|
hook.buffer.clear();
|
|
@@ -74,6 +75,11 @@ export class System {
|
|
|
74
75
|
if (this.tick) this.tick();
|
|
75
76
|
}
|
|
76
77
|
|
|
78
|
+
run(): void {
|
|
79
|
+
this._runCore();
|
|
80
|
+
this.em.commitRemovals();
|
|
81
|
+
}
|
|
82
|
+
|
|
77
83
|
dispose(): void {
|
|
78
84
|
for (const unsub of this._unsubs) unsub();
|
|
79
85
|
this._unsubs.length = 0;
|
|
@@ -101,6 +107,8 @@ interface FunctionalHook {
|
|
|
101
107
|
|
|
102
108
|
export interface FunctionalSystem {
|
|
103
109
|
(): void;
|
|
110
|
+
/** @internal Process hooks and tick without clearing removed-data snapshots. */
|
|
111
|
+
_runCore(): void;
|
|
104
112
|
dispose(): void;
|
|
105
113
|
}
|
|
106
114
|
|
|
@@ -108,7 +116,7 @@ export function createSystem(em: EntityManager, constructor: FunctionalSystemCon
|
|
|
108
116
|
const hooks: FunctionalHook[] = [];
|
|
109
117
|
|
|
110
118
|
const sys: SystemContext = {
|
|
111
|
-
onAdded(...args:
|
|
119
|
+
onAdded(...args: [...ComponentDef[], HookCallback]) {
|
|
112
120
|
const callback = args[args.length - 1] as HookCallback;
|
|
113
121
|
const types = args.slice(0, -1) as ComponentDef[];
|
|
114
122
|
const buffer = new Set<EntityId>();
|
|
@@ -127,7 +135,7 @@ export function createSystem(em: EntityManager, constructor: FunctionalSystemCon
|
|
|
127
135
|
hooks.push({ unsubs, buffer, callback });
|
|
128
136
|
},
|
|
129
137
|
|
|
130
|
-
onRemoved(...args:
|
|
138
|
+
onRemoved(...args: [...ComponentDef[], HookCallback]) {
|
|
131
139
|
const callback = args[args.length - 1] as HookCallback;
|
|
132
140
|
const types = args.slice(0, -1) as ComponentDef[];
|
|
133
141
|
const buffer = new Set<EntityId>();
|
|
@@ -150,7 +158,7 @@ export function createSystem(em: EntityManager, constructor: FunctionalSystemCon
|
|
|
150
158
|
|
|
151
159
|
const tick = constructor(sys);
|
|
152
160
|
|
|
153
|
-
function
|
|
161
|
+
function runCore() {
|
|
154
162
|
for (const hook of hooks) {
|
|
155
163
|
for (const id of hook.buffer) hook.callback(id);
|
|
156
164
|
hook.buffer.clear();
|
|
@@ -158,6 +166,13 @@ export function createSystem(em: EntityManager, constructor: FunctionalSystemCon
|
|
|
158
166
|
if (tick) tick();
|
|
159
167
|
}
|
|
160
168
|
|
|
169
|
+
function system() {
|
|
170
|
+
runCore();
|
|
171
|
+
em.commitRemovals();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
system._runCore = runCore;
|
|
175
|
+
|
|
161
176
|
system.dispose = function () {
|
|
162
177
|
for (const hook of hooks) {
|
|
163
178
|
for (const unsub of hook.unsubs) unsub();
|
|
@@ -171,7 +186,7 @@ export function createSystem(em: EntityManager, constructor: FunctionalSystemCon
|
|
|
171
186
|
// ── Activator ────────────────────────────────────────────
|
|
172
187
|
|
|
173
188
|
interface Runnable {
|
|
174
|
-
|
|
189
|
+
_runCore(): void;
|
|
175
190
|
dispose(): void;
|
|
176
191
|
}
|
|
177
192
|
|
|
@@ -180,17 +195,27 @@ export interface Pipeline {
|
|
|
180
195
|
dispose(): void;
|
|
181
196
|
}
|
|
182
197
|
|
|
198
|
+
function isSystemClass(entry: Function): entry is new (em: EntityManager) => System {
|
|
199
|
+
let proto = entry.prototype;
|
|
200
|
+
while (proto) {
|
|
201
|
+
proto = Object.getPrototypeOf(proto);
|
|
202
|
+
if (proto === System.prototype) return true;
|
|
203
|
+
}
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
|
|
183
207
|
export function createSystems(em: EntityManager, entries: (FunctionalSystemConstructor | (new (em: EntityManager) => System))[]): Pipeline {
|
|
184
208
|
const systems: Runnable[] = entries.map(Entry => {
|
|
185
|
-
if (
|
|
186
|
-
return new
|
|
209
|
+
if (isSystemClass(Entry)) {
|
|
210
|
+
return new Entry(em);
|
|
187
211
|
}
|
|
188
212
|
const sys = createSystem(em, Entry as FunctionalSystemConstructor);
|
|
189
|
-
return {
|
|
213
|
+
return { _runCore: sys._runCore, dispose: sys.dispose };
|
|
190
214
|
});
|
|
191
215
|
|
|
192
216
|
function pipeline() {
|
|
193
|
-
for (let i = 0; i < systems.length; i++) systems[i].
|
|
217
|
+
for (let i = 0; i < systems.length; i++) systems[i]._runCore();
|
|
218
|
+
em.commitRemovals();
|
|
194
219
|
}
|
|
195
220
|
|
|
196
221
|
pipeline.dispose = function () {
|
package/src/WasmArena.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { SoAArrayValue } from './EntityManager.js';
|
|
2
|
+
|
|
3
|
+
export type NumericTypedArrayConstructor =
|
|
4
|
+
| typeof Float32Array | typeof Float64Array
|
|
5
|
+
| typeof Int8Array | typeof Int16Array | typeof Int32Array
|
|
6
|
+
| typeof Uint8Array | typeof Uint16Array | typeof Uint32Array;
|
|
7
|
+
|
|
8
|
+
interface ViewRegistration {
|
|
9
|
+
fields: Record<string, SoAArrayValue>;
|
|
10
|
+
field: string;
|
|
11
|
+
offset: number;
|
|
12
|
+
Ctor: NumericTypedArrayConstructor;
|
|
13
|
+
count: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const ALIGN = 16; // 16-byte alignment for SIMD
|
|
17
|
+
|
|
18
|
+
export class WasmArena {
|
|
19
|
+
memory: WebAssembly.Memory;
|
|
20
|
+
private nextOffset: number = 0;
|
|
21
|
+
private views: ViewRegistration[] = [];
|
|
22
|
+
|
|
23
|
+
constructor(initialPages = 2048, maxPages = 16384) {
|
|
24
|
+
this.memory = new WebAssembly.Memory({ initial: initialPages, maximum: maxPages });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
alloc(byteLength: number): number {
|
|
28
|
+
// Align up to 16 bytes
|
|
29
|
+
const aligned = (this.nextOffset + ALIGN - 1) & ~(ALIGN - 1);
|
|
30
|
+
const end = aligned + byteLength;
|
|
31
|
+
|
|
32
|
+
if (end > this.memory.buffer.byteLength) {
|
|
33
|
+
const needed = Math.ceil((end - this.memory.buffer.byteLength) / 65536);
|
|
34
|
+
this.memory.grow(needed);
|
|
35
|
+
this.handleGrow();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
this.nextOffset = end;
|
|
39
|
+
return aligned;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
createView<T extends SoAArrayValue>(
|
|
43
|
+
Ctor: NumericTypedArrayConstructor,
|
|
44
|
+
offset: number,
|
|
45
|
+
count: number,
|
|
46
|
+
fields: Record<string, SoAArrayValue>,
|
|
47
|
+
field: string
|
|
48
|
+
): T {
|
|
49
|
+
const view = new Ctor(this.memory.buffer, offset, count) as unknown as T;
|
|
50
|
+
this.views.push({ fields, field, offset, Ctor, count });
|
|
51
|
+
fields[field] = view;
|
|
52
|
+
return view;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private handleGrow(): void {
|
|
56
|
+
const buf = this.memory.buffer;
|
|
57
|
+
for (const reg of this.views) {
|
|
58
|
+
reg.fields[reg.field] = new reg.Ctor(buf, reg.offset, reg.count);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Update a registered view to point to a new (larger) allocation. */
|
|
63
|
+
updateView(
|
|
64
|
+
fields: Record<string, SoAArrayValue>,
|
|
65
|
+
field: string,
|
|
66
|
+
offset: number,
|
|
67
|
+
Ctor: NumericTypedArrayConstructor,
|
|
68
|
+
count: number
|
|
69
|
+
): void {
|
|
70
|
+
// Find and update the existing registration
|
|
71
|
+
for (const reg of this.views) {
|
|
72
|
+
if (reg.fields === fields && reg.field === field) {
|
|
73
|
+
reg.offset = offset;
|
|
74
|
+
reg.Ctor = Ctor;
|
|
75
|
+
reg.count = count;
|
|
76
|
+
fields[field] = new Ctor(this.memory.buffer, offset, count);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Not found — register as new
|
|
81
|
+
this.createView(Ctor, offset, count, fields, field);
|
|
82
|
+
}
|
|
83
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,21 +1,26 @@
|
|
|
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
|
|
|
10
|
-
import { parseTypeSpec, componentSchemas, type ComponentDef, type TypeSpec } from './ComponentRegistry.js';
|
|
11
|
-
import type { FieldRef } from './EntityManager.js';
|
|
13
|
+
import { parseTypeSpec, componentSchemas, type ComponentDef, type FieldRef, type TypeSpec } from './ComponentRegistry.js';
|
|
12
14
|
|
|
15
|
+
// Tag component (no fields)
|
|
13
16
|
export function component(name: string): ComponentDef;
|
|
14
|
-
|
|
15
|
-
export function component<
|
|
16
|
-
|
|
17
|
+
// Uniform type with field list
|
|
18
|
+
export function component<const F extends readonly string[]>(name: string, type: string, fields: F): ComponentDef<F[number]>;
|
|
19
|
+
// Schema object with mixed types
|
|
20
|
+
export function component<S extends Record<string, string>>(name: string, schema: S): ComponentDef<Extract<keyof S, string>>;
|
|
21
|
+
export function component(name: string, typeOrSchema?: string | Record<string, string>, fields?: string[]): ComponentDef<string> {
|
|
17
22
|
const sym = Symbol(name);
|
|
18
|
-
const comp:
|
|
23
|
+
const comp: Record<string, unknown> = { _sym: sym, _name: name };
|
|
19
24
|
|
|
20
25
|
let schema: Record<string, TypeSpec> | undefined;
|
|
21
26
|
|
|
@@ -24,13 +29,13 @@ export function component(name: string, typeOrSchema?: string | Record<string, s
|
|
|
24
29
|
schema = {};
|
|
25
30
|
for (const f of fields) {
|
|
26
31
|
schema[f] = spec;
|
|
27
|
-
comp[f] = { _sym: sym, _field: f };
|
|
32
|
+
comp[f] = { _sym: sym, _field: f } satisfies FieldRef;
|
|
28
33
|
}
|
|
29
34
|
} else if (typeOrSchema && typeof typeOrSchema === 'object') {
|
|
30
35
|
schema = {};
|
|
31
36
|
for (const [field, type] of Object.entries(typeOrSchema)) {
|
|
32
37
|
schema[field] = parseTypeSpec(type);
|
|
33
|
-
comp[field] = { _sym: sym, _field: field };
|
|
38
|
+
comp[field] = { _sym: sym, _field: field } satisfies FieldRef;
|
|
34
39
|
}
|
|
35
40
|
}
|
|
36
41
|
|
|
@@ -38,5 +43,5 @@ export function component(name: string, typeOrSchema?: string | Record<string, s
|
|
|
38
43
|
componentSchemas.set(sym, schema);
|
|
39
44
|
}
|
|
40
45
|
|
|
41
|
-
return comp
|
|
46
|
+
return comp as ComponentDef<string>;
|
|
42
47
|
}
|
package/src/iterate.wat
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
(module
|
|
2
|
+
(memory (import "env" "memory") 1)
|
|
3
|
+
|
|
4
|
+
;; Scalar loop: px[i] += vx[i]; py[i] += vy[i]
|
|
5
|
+
;; params: byte offsets for px, py, vx, vy arrays + element count
|
|
6
|
+
(func (export "iterate_scalar")
|
|
7
|
+
(param $px i32) (param $py i32) (param $vx i32) (param $vy i32) (param $count i32)
|
|
8
|
+
(local $i i32)
|
|
9
|
+
(local $off i32)
|
|
10
|
+
(local.set $i (i32.const 0))
|
|
11
|
+
(block $break
|
|
12
|
+
(loop $loop
|
|
13
|
+
(br_if $break (i32.ge_u (local.get $i) (local.get $count)))
|
|
14
|
+
(local.set $off (i32.shl (local.get $i) (i32.const 2)))
|
|
15
|
+
;; px[i] += vx[i]
|
|
16
|
+
(f32.store
|
|
17
|
+
(i32.add (local.get $px) (local.get $off))
|
|
18
|
+
(f32.add
|
|
19
|
+
(f32.load (i32.add (local.get $px) (local.get $off)))
|
|
20
|
+
(f32.load (i32.add (local.get $vx) (local.get $off)))
|
|
21
|
+
)
|
|
22
|
+
)
|
|
23
|
+
;; py[i] += vy[i]
|
|
24
|
+
(f32.store
|
|
25
|
+
(i32.add (local.get $py) (local.get $off))
|
|
26
|
+
(f32.add
|
|
27
|
+
(f32.load (i32.add (local.get $py) (local.get $off)))
|
|
28
|
+
(f32.load (i32.add (local.get $vy) (local.get $off)))
|
|
29
|
+
)
|
|
30
|
+
)
|
|
31
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
32
|
+
(br $loop)
|
|
33
|
+
)
|
|
34
|
+
)
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
;; SIMD loop: processes 4 floats at a time using v128 / f32x4
|
|
38
|
+
(func (export "iterate_simd")
|
|
39
|
+
(param $px i32) (param $py i32) (param $vx i32) (param $vy i32) (param $count i32)
|
|
40
|
+
(local $i i32)
|
|
41
|
+
(local $off i32)
|
|
42
|
+
(local $end4 i32)
|
|
43
|
+
;; end4 = count & ~3 (round down to multiple of 4)
|
|
44
|
+
(local.set $end4 (i32.and (local.get $count) (i32.const -4)))
|
|
45
|
+
;; SIMD loop: 4 elements per iteration
|
|
46
|
+
(local.set $i (i32.const 0))
|
|
47
|
+
(block $break
|
|
48
|
+
(loop $loop
|
|
49
|
+
(br_if $break (i32.ge_u (local.get $i) (local.get $end4)))
|
|
50
|
+
(local.set $off (i32.shl (local.get $i) (i32.const 2)))
|
|
51
|
+
;; px[i..i+4] += vx[i..i+4]
|
|
52
|
+
(v128.store
|
|
53
|
+
(i32.add (local.get $px) (local.get $off))
|
|
54
|
+
(f32x4.add
|
|
55
|
+
(v128.load (i32.add (local.get $px) (local.get $off)))
|
|
56
|
+
(v128.load (i32.add (local.get $vx) (local.get $off)))
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
;; py[i..i+4] += vy[i..i+4]
|
|
60
|
+
(v128.store
|
|
61
|
+
(i32.add (local.get $py) (local.get $off))
|
|
62
|
+
(f32x4.add
|
|
63
|
+
(v128.load (i32.add (local.get $py) (local.get $off)))
|
|
64
|
+
(v128.load (i32.add (local.get $vy) (local.get $off)))
|
|
65
|
+
)
|
|
66
|
+
)
|
|
67
|
+
(local.set $i (i32.add (local.get $i) (i32.const 4)))
|
|
68
|
+
(br $loop)
|
|
69
|
+
)
|
|
70
|
+
)
|
|
71
|
+
;; Scalar remainder
|
|
72
|
+
(block $break2
|
|
73
|
+
(loop $loop2
|
|
74
|
+
(br_if $break2 (i32.ge_u (local.get $i) (local.get $count)))
|
|
75
|
+
(local.set $off (i32.shl (local.get $i) (i32.const 2)))
|
|
76
|
+
(f32.store
|
|
77
|
+
(i32.add (local.get $px) (local.get $off))
|
|
78
|
+
(f32.add
|
|
79
|
+
(f32.load (i32.add (local.get $px) (local.get $off)))
|
|
80
|
+
(f32.load (i32.add (local.get $vx) (local.get $off)))
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
(f32.store
|
|
84
|
+
(i32.add (local.get $py) (local.get $off))
|
|
85
|
+
(f32.add
|
|
86
|
+
(f32.load (i32.add (local.get $py) (local.get $off)))
|
|
87
|
+
(f32.load (i32.add (local.get $vy) (local.get $off)))
|
|
88
|
+
)
|
|
89
|
+
)
|
|
90
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
91
|
+
(br $loop2)
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
;; Generic f32 add: dst[i] += src[i], SIMD 4-wide + scalar remainder
|
|
97
|
+
(func (export "add_f32")
|
|
98
|
+
(param $dst i32) (param $src i32) (param $count i32)
|
|
99
|
+
(local $i i32)
|
|
100
|
+
(local $off i32)
|
|
101
|
+
(local $end4 i32)
|
|
102
|
+
(local.set $end4 (i32.and (local.get $count) (i32.const -4)))
|
|
103
|
+
(local.set $i (i32.const 0))
|
|
104
|
+
(block $break
|
|
105
|
+
(loop $loop
|
|
106
|
+
(br_if $break (i32.ge_u (local.get $i) (local.get $end4)))
|
|
107
|
+
(local.set $off (i32.shl (local.get $i) (i32.const 2)))
|
|
108
|
+
(v128.store
|
|
109
|
+
(i32.add (local.get $dst) (local.get $off))
|
|
110
|
+
(f32x4.add
|
|
111
|
+
(v128.load (i32.add (local.get $dst) (local.get $off)))
|
|
112
|
+
(v128.load (i32.add (local.get $src) (local.get $off)))
|
|
113
|
+
)
|
|
114
|
+
)
|
|
115
|
+
(local.set $i (i32.add (local.get $i) (i32.const 4)))
|
|
116
|
+
(br $loop)
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
(block $break2
|
|
120
|
+
(loop $loop2
|
|
121
|
+
(br_if $break2 (i32.ge_u (local.get $i) (local.get $count)))
|
|
122
|
+
(local.set $off (i32.shl (local.get $i) (i32.const 2)))
|
|
123
|
+
(f32.store
|
|
124
|
+
(i32.add (local.get $dst) (local.get $off))
|
|
125
|
+
(f32.add
|
|
126
|
+
(f32.load (i32.add (local.get $dst) (local.get $off)))
|
|
127
|
+
(f32.load (i32.add (local.get $src) (local.get $off)))
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
131
|
+
(br $loop2)
|
|
132
|
+
)
|
|
133
|
+
)
|
|
134
|
+
)
|
|
135
|
+
)
|
|
@@ -0,0 +1,68 @@
|
|
|
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
|
+
|
|
30
|
+
export interface IterateKernels {
|
|
31
|
+
iterate_scalar(px: number, py: number, vx: number, vy: number, count: number): void;
|
|
32
|
+
iterate_simd(px: number, py: number, vx: number, vy: number, count: number): void;
|
|
33
|
+
add_f32(dst: number, src: number, count: number): void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Cached compiled module (shared across all EntityManager instances)
|
|
37
|
+
let cachedModule: WebAssembly.Module | null = null;
|
|
38
|
+
|
|
39
|
+
function getCompiledModule(): WebAssembly.Module {
|
|
40
|
+
if (!cachedModule) {
|
|
41
|
+
cachedModule = new WebAssembly.Module(ITERATE_WASM);
|
|
42
|
+
}
|
|
43
|
+
return cachedModule;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Returns true if the runtime supports WebAssembly SIMD. Result is cached. */
|
|
47
|
+
export function isWasmSimdAvailable(): boolean {
|
|
48
|
+
if (cachedModule !== null) return true;
|
|
49
|
+
try {
|
|
50
|
+
getCompiledModule();
|
|
51
|
+
return true;
|
|
52
|
+
} catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function instantiateKernels(memory: WebAssembly.Memory): Promise<IterateKernels> {
|
|
58
|
+
const module = getCompiledModule();
|
|
59
|
+
const instance = await WebAssembly.instantiate(module, { env: { memory } });
|
|
60
|
+
return instance.exports as unknown as IterateKernels;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Synchronous instantiation — only for small modules (<4KB). Used internally. */
|
|
64
|
+
export function instantiateKernelsSync(memory: WebAssembly.Memory): IterateKernels {
|
|
65
|
+
const module = getCompiledModule();
|
|
66
|
+
const instance = new WebAssembly.Instance(module, { env: { memory } });
|
|
67
|
+
return instance.exports as unknown as IterateKernels;
|
|
68
|
+
}
|