@oasys/oecs 0.1.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/LICENSE +21 -0
- package/README.md +305 -0
- package/dist/archetype.d.ts +61 -0
- package/dist/archetype.d.ts.map +1 -0
- package/dist/component.d.ts +18 -0
- package/dist/component.d.ts.map +1 -0
- package/dist/entity.d.ts +11 -0
- package/dist/entity.d.ts.map +1 -0
- package/dist/event.d.ts +24 -0
- package/dist/event.d.ts.map +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1182 -0
- package/dist/query.d.ts +73 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/schedule.d.ts +40 -0
- package/dist/schedule.d.ts.map +1 -0
- package/dist/store.d.ts +92 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/system.d.ts +16 -0
- package/dist/system.d.ts.map +1 -0
- package/dist/type_primitives/assertions.d.ts +14 -0
- package/dist/type_primitives/assertions.d.ts.map +1 -0
- package/dist/type_primitives/bitset/bitset.d.ts +37 -0
- package/dist/type_primitives/bitset/bitset.d.ts.map +1 -0
- package/dist/type_primitives/brand.d.ts +18 -0
- package/dist/type_primitives/brand.d.ts.map +1 -0
- package/dist/type_primitives/error.d.ts +10 -0
- package/dist/type_primitives/error.d.ts.map +1 -0
- package/dist/type_primitives/index.d.ts +8 -0
- package/dist/type_primitives/index.d.ts.map +1 -0
- package/dist/type_primitives/sparse_map/sparse_map.d.ts +26 -0
- package/dist/type_primitives/sparse_map/sparse_map.d.ts.map +1 -0
- package/dist/type_primitives/sparse_set/sparse_set.d.ts +24 -0
- package/dist/type_primitives/sparse_set/sparse_set.d.ts.map +1 -0
- package/dist/type_primitives/typed_arrays/typed_arrays.d.ts +79 -0
- package/dist/type_primitives/typed_arrays/typed_arrays.d.ts.map +1 -0
- package/dist/utils/arrays.d.ts +6 -0
- package/dist/utils/arrays.d.ts.map +1 -0
- package/dist/utils/error.d.ts +28 -0
- package/dist/utils/error.d.ts.map +1 -0
- package/dist/world.d.ts +63 -0
- package/dist/world.d.ts.map +1 -0
- package/package.json +53 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1182 @@
|
|
|
1
|
+
var F = Object.defineProperty;
|
|
2
|
+
var X = (a, t, e) => t in a ? F(a, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : a[t] = e;
|
|
3
|
+
var l = (a, t, e) => X(a, typeof t != "symbol" ? t + "" : t, e);
|
|
4
|
+
class Y extends Error {
|
|
5
|
+
constructor(t, e, s) {
|
|
6
|
+
super(t), this.is_operational = e, this.context = s, this.name = this.constructor.name, Error.captureStackTrace(this, this.constructor);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
var u = /* @__PURE__ */ ((a) => (a.EID_MAX_INDEX_OVERFLOW = "EID_MAX_INDEX_OVERFLOW", a.EID_MAX_GEN_OVERFLOW = "EID_MAX_GEN_OVERFLOW", a.COMPONENT_NOT_REGISTERED = "COMPONENT_NOT_REGISTERED", a.ENTITY_NOT_ALIVE = "ENTITY_NOT_ALIVE", a.CIRCULAR_SYSTEM_DEPENDENCY = "CIRCULAR_SYSTEM_DEPENDENCY", a.DUPLICATE_SYSTEM = "DUPLICATE_SYSTEM", a.ARCHETYPE_NOT_FOUND = "ARCHETYPE_NOT_FOUND", a))(u || {});
|
|
10
|
+
class g extends Y {
|
|
11
|
+
constructor(t, e, s) {
|
|
12
|
+
super(e ?? t, !0, s), this.category = t;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
var C = /* @__PURE__ */ ((a) => (a.ASSERTION_FAIL_CONDITION = "ASSERTION_FAIL_CONDITION", a.VALIDATION_FAIL_CONDITION = "VALIDATION_FAIL_CONDITION", a))(C || {});
|
|
16
|
+
class G extends Y {
|
|
17
|
+
constructor(t, e, s) {
|
|
18
|
+
super(e, !1, s), this.category = t;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const P = (a) => Number.isInteger(a) && a >= 0;
|
|
22
|
+
function b(a, t, e) {
|
|
23
|
+
if (process.env.NODE_ENV !== "production" && !t(a))
|
|
24
|
+
throw new G(
|
|
25
|
+
C.VALIDATION_FAIL_CONDITION,
|
|
26
|
+
`Expected value to meet validation: ${e}`
|
|
27
|
+
);
|
|
28
|
+
return a;
|
|
29
|
+
}
|
|
30
|
+
const $ = 4;
|
|
31
|
+
class T {
|
|
32
|
+
constructor(t) {
|
|
33
|
+
l(this, "_words");
|
|
34
|
+
this._words = t ?? new Array($).fill(0);
|
|
35
|
+
}
|
|
36
|
+
has(t) {
|
|
37
|
+
const e = t >>> 5;
|
|
38
|
+
return e >= this._words.length ? !1 : (this._words[e] & 1 << (t & 31)) !== 0;
|
|
39
|
+
}
|
|
40
|
+
set(t) {
|
|
41
|
+
const e = t >>> 5;
|
|
42
|
+
e >= this._words.length && this.grow(e + 1), this._words[e] |= 1 << (t & 31);
|
|
43
|
+
}
|
|
44
|
+
clear(t) {
|
|
45
|
+
const e = t >>> 5;
|
|
46
|
+
e >= this._words.length || (this._words[e] &= ~(1 << (t & 31)));
|
|
47
|
+
}
|
|
48
|
+
/** True if any bit is set in both this and other (non-empty intersection). */
|
|
49
|
+
overlaps(t) {
|
|
50
|
+
const e = this._words, s = t._words, n = e.length < s.length ? e.length : s.length;
|
|
51
|
+
for (let r = 0; r < n; r++)
|
|
52
|
+
if ((e[r] & s[r]) !== 0) return !0;
|
|
53
|
+
return !1;
|
|
54
|
+
}
|
|
55
|
+
/** True if this is a superset of other (all bits in other are set in this). */
|
|
56
|
+
contains(t) {
|
|
57
|
+
const e = t._words, s = this._words, n = s.length;
|
|
58
|
+
for (let r = 0; r < e.length; r++) {
|
|
59
|
+
const o = e[r];
|
|
60
|
+
if (o !== 0 && (r >= n || (s[r] & o) !== o))
|
|
61
|
+
return !1;
|
|
62
|
+
}
|
|
63
|
+
return !0;
|
|
64
|
+
}
|
|
65
|
+
equals(t) {
|
|
66
|
+
const e = this._words, s = t._words, n = e.length > s.length ? e.length : s.length;
|
|
67
|
+
for (let r = 0; r < n; r++) {
|
|
68
|
+
const o = r < e.length ? e[r] : 0, _ = r < s.length ? s[r] : 0;
|
|
69
|
+
if (o !== _) return !1;
|
|
70
|
+
}
|
|
71
|
+
return !0;
|
|
72
|
+
}
|
|
73
|
+
copy() {
|
|
74
|
+
return new T(this._words.slice());
|
|
75
|
+
}
|
|
76
|
+
copy_with_set(t) {
|
|
77
|
+
const e = t >>> 5, s = e + 1, n = this._words.length > s ? this._words.length : s, r = new Array(n).fill(0);
|
|
78
|
+
for (let o = 0; o < this._words.length; o++) r[o] = this._words[o];
|
|
79
|
+
return r[e] |= 1 << (t & 31), new T(r);
|
|
80
|
+
}
|
|
81
|
+
copy_with_clear(t) {
|
|
82
|
+
const e = this._words.slice(), s = t >>> 5;
|
|
83
|
+
return s < e.length && (e[s] &= ~(1 << (t & 31))), new T(e);
|
|
84
|
+
}
|
|
85
|
+
/** FNV-1a hash. Skips trailing zero words so differently-sized arrays with the same bits hash equally. */
|
|
86
|
+
hash() {
|
|
87
|
+
let t = 2166136261;
|
|
88
|
+
const e = this._words;
|
|
89
|
+
let s = e.length - 1;
|
|
90
|
+
for (; s >= 0 && e[s] === 0; ) s--;
|
|
91
|
+
for (let n = 0; n <= s; n++)
|
|
92
|
+
t ^= e[n], t = Math.imul(t, 16777619);
|
|
93
|
+
return t;
|
|
94
|
+
}
|
|
95
|
+
/** Iterate all set bits via lowest-set-bit extraction. */
|
|
96
|
+
for_each(t) {
|
|
97
|
+
const e = this._words;
|
|
98
|
+
for (let s = 0; s < e.length; s++) {
|
|
99
|
+
let n = e[s];
|
|
100
|
+
if (n === 0) continue;
|
|
101
|
+
const r = s << 5;
|
|
102
|
+
for (; n !== 0; ) {
|
|
103
|
+
const o = n & -n >>> 0, _ = 31 - Math.clz32(o);
|
|
104
|
+
t(r + _), n ^= o;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
grow(t) {
|
|
109
|
+
let e = this._words.length;
|
|
110
|
+
for (; e < t; ) e *= 2;
|
|
111
|
+
const s = new Array(e).fill(0);
|
|
112
|
+
for (let n = 0; n < this._words.length; n++) s[n] = this._words[n];
|
|
113
|
+
this._words = s;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const D = 20, S = (1 << D) - 1, z = S, W = 31 - D, k = (1 << W) - 1, B = (a, t) => {
|
|
117
|
+
if (process.env.NODE_ENV !== "production") {
|
|
118
|
+
if (a < 0 || a > z)
|
|
119
|
+
throw new g(u.EID_MAX_INDEX_OVERFLOW);
|
|
120
|
+
if (t < 0 || t > k)
|
|
121
|
+
throw new g(u.EID_MAX_GEN_OVERFLOW);
|
|
122
|
+
}
|
|
123
|
+
return t << D | a;
|
|
124
|
+
}, m = (a) => a & S, R = (a) => a >> D, Q = (a) => b(
|
|
125
|
+
a,
|
|
126
|
+
P,
|
|
127
|
+
"ComponentID must be a non-negative integer"
|
|
128
|
+
), K = (a) => b(
|
|
129
|
+
a,
|
|
130
|
+
P,
|
|
131
|
+
"EventID must be a non-negative integer"
|
|
132
|
+
);
|
|
133
|
+
class J {
|
|
134
|
+
constructor(t) {
|
|
135
|
+
l(this, "field_names");
|
|
136
|
+
l(this, "columns");
|
|
137
|
+
l(this, "reader");
|
|
138
|
+
this.field_names = t, this.columns = [];
|
|
139
|
+
for (let s = 0; s < t.length; s++)
|
|
140
|
+
this.columns.push([]);
|
|
141
|
+
const e = { length: 0 };
|
|
142
|
+
for (let s = 0; s < t.length; s++)
|
|
143
|
+
e[t[s]] = this.columns[s];
|
|
144
|
+
this.reader = e;
|
|
145
|
+
}
|
|
146
|
+
emit(t) {
|
|
147
|
+
const e = this.field_names, s = this.columns;
|
|
148
|
+
for (let n = 0; n < e.length; n++)
|
|
149
|
+
s[n].push(t[e[n]]);
|
|
150
|
+
this.reader.length++;
|
|
151
|
+
}
|
|
152
|
+
/** Emit a signal (zero-field event). */
|
|
153
|
+
emit_signal() {
|
|
154
|
+
this.reader.length++;
|
|
155
|
+
}
|
|
156
|
+
clear() {
|
|
157
|
+
this.reader.length = 0;
|
|
158
|
+
const t = this.columns;
|
|
159
|
+
for (let e = 0; e < t.length; e++)
|
|
160
|
+
t[e].length = 0;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const Z = (a) => b(
|
|
164
|
+
a,
|
|
165
|
+
P,
|
|
166
|
+
"ArchetypeID must be a non-negative integer"
|
|
167
|
+
);
|
|
168
|
+
class H {
|
|
169
|
+
constructor(t, e, s) {
|
|
170
|
+
l(this, "id");
|
|
171
|
+
l(this, "mask");
|
|
172
|
+
l(this, "has_columns");
|
|
173
|
+
l(this, "entity_ids", []);
|
|
174
|
+
l(this, "length", 0);
|
|
175
|
+
l(this, "edges", []);
|
|
176
|
+
// Sparse array indexed by ComponentID — undefined for absent components.
|
|
177
|
+
// Allows O(1) column group lookup by component.
|
|
178
|
+
l(this, "column_groups", []);
|
|
179
|
+
// Dense list of ComponentIDs that have columns — used to iterate only
|
|
180
|
+
// data-bearing components in copy/add/remove operations.
|
|
181
|
+
l(this, "_column_ids", []);
|
|
182
|
+
if (this.id = t, this.mask = e, s)
|
|
183
|
+
for (let n = 0; n < s.length; n++) {
|
|
184
|
+
const r = s[n], o = new Array(r.field_names.length);
|
|
185
|
+
for (let h = 0; h < r.field_names.length; h++)
|
|
186
|
+
o[h] = [];
|
|
187
|
+
const _ = /* @__PURE__ */ Object.create(null);
|
|
188
|
+
for (let h = 0; h < r.field_names.length; h++)
|
|
189
|
+
_[r.field_names[h]] = o[h];
|
|
190
|
+
this.column_groups[r.component_id] = {
|
|
191
|
+
layout: r,
|
|
192
|
+
columns: o,
|
|
193
|
+
record: _
|
|
194
|
+
}, this._column_ids.push(r.component_id);
|
|
195
|
+
}
|
|
196
|
+
this.has_columns = this._column_ids.length > 0;
|
|
197
|
+
}
|
|
198
|
+
get entity_count() {
|
|
199
|
+
return this.length;
|
|
200
|
+
}
|
|
201
|
+
get entity_list() {
|
|
202
|
+
return this.entity_ids;
|
|
203
|
+
}
|
|
204
|
+
has_component(t) {
|
|
205
|
+
return this.mask.has(t);
|
|
206
|
+
}
|
|
207
|
+
matches(t) {
|
|
208
|
+
return this.mask.contains(t);
|
|
209
|
+
}
|
|
210
|
+
/** Get a single field's column. Valid data: indices 0..entity_count-1. */
|
|
211
|
+
get_column(t, e) {
|
|
212
|
+
const s = this.column_groups[t];
|
|
213
|
+
if (process.env.NODE_ENV !== "production" && !s)
|
|
214
|
+
throw new g(
|
|
215
|
+
u.COMPONENT_NOT_REGISTERED,
|
|
216
|
+
`Component ${t} not in archetype ${this.id}`
|
|
217
|
+
);
|
|
218
|
+
const n = s.layout.field_index[e];
|
|
219
|
+
if (process.env.NODE_ENV !== "production" && n === void 0)
|
|
220
|
+
throw new g(
|
|
221
|
+
u.COMPONENT_NOT_REGISTERED,
|
|
222
|
+
`Field "${e}" does not exist on component`
|
|
223
|
+
);
|
|
224
|
+
return s.columns[n];
|
|
225
|
+
}
|
|
226
|
+
/** Get all columns for a component as { fieldName: number[] }. */
|
|
227
|
+
get_column_group(t) {
|
|
228
|
+
const e = this.column_groups[t];
|
|
229
|
+
return e ? e.record : {};
|
|
230
|
+
}
|
|
231
|
+
write_fields(t, e, s) {
|
|
232
|
+
const n = this.column_groups[e];
|
|
233
|
+
if (!n) return;
|
|
234
|
+
const { field_names: r } = n.layout;
|
|
235
|
+
for (let o = 0; o < r.length; o++)
|
|
236
|
+
n.columns[o][t] = s[r[o]];
|
|
237
|
+
}
|
|
238
|
+
read_field(t, e, s) {
|
|
239
|
+
const n = this.column_groups[e];
|
|
240
|
+
if (!n) return NaN;
|
|
241
|
+
const r = n.layout.field_index[s];
|
|
242
|
+
return r === void 0 ? NaN : n.columns[r][t] ?? NaN;
|
|
243
|
+
}
|
|
244
|
+
/** Copy all shared component columns from source archetype at src_row into dst_row. */
|
|
245
|
+
copy_shared_from(t, e, s) {
|
|
246
|
+
const n = t.column_groups, r = this._column_ids;
|
|
247
|
+
for (let o = 0; o < r.length; o++) {
|
|
248
|
+
const _ = r[o], h = n[_];
|
|
249
|
+
if (!h) continue;
|
|
250
|
+
const i = this.column_groups[_];
|
|
251
|
+
for (let c = 0; c < i.columns.length; c++)
|
|
252
|
+
i.columns[c][s] = h.columns[c][e];
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Add an entity. Pushes zeroes into all columns and returns the assigned row.
|
|
257
|
+
* Store is responsible for tracking entity_index → row.
|
|
258
|
+
*/
|
|
259
|
+
add_entity(t) {
|
|
260
|
+
const e = this.length;
|
|
261
|
+
this.entity_ids.push(t);
|
|
262
|
+
const s = this._column_ids;
|
|
263
|
+
for (let n = 0; n < s.length; n++) {
|
|
264
|
+
const r = this.column_groups[s[n]];
|
|
265
|
+
for (let o = 0; o < r.columns.length; o++)
|
|
266
|
+
r.columns[o].push(0);
|
|
267
|
+
}
|
|
268
|
+
return this.length++, e;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Remove entity at row via swap-and-pop. Swaps the last entity into the
|
|
272
|
+
* vacated row to keep data dense. Returns the entity_index of the swapped
|
|
273
|
+
* entity (so Store can update its row), or -1 if no swap was needed.
|
|
274
|
+
*/
|
|
275
|
+
remove_entity(t) {
|
|
276
|
+
const e = this.length - 1;
|
|
277
|
+
let s = -1;
|
|
278
|
+
const n = this._column_ids;
|
|
279
|
+
if (t !== e) {
|
|
280
|
+
this.entity_ids[t] = this.entity_ids[e], s = m(this.entity_ids[t]);
|
|
281
|
+
for (let r = 0; r < n.length; r++) {
|
|
282
|
+
const o = this.column_groups[n[r]];
|
|
283
|
+
for (let _ = 0; _ < o.columns.length; _++)
|
|
284
|
+
o.columns[_][t] = o.columns[_][e], o.columns[_].pop();
|
|
285
|
+
}
|
|
286
|
+
} else
|
|
287
|
+
for (let r = 0; r < n.length; r++) {
|
|
288
|
+
const o = this.column_groups[n[r]];
|
|
289
|
+
for (let _ = 0; _ < o.columns.length; _++)
|
|
290
|
+
o.columns[_].pop();
|
|
291
|
+
}
|
|
292
|
+
return this.entity_ids.pop(), this.length--, s;
|
|
293
|
+
}
|
|
294
|
+
/** Tag-optimized add: skip column push entirely (no data to store). */
|
|
295
|
+
add_entity_tag(t) {
|
|
296
|
+
const e = this.length;
|
|
297
|
+
return this.entity_ids.push(t), this.length++, e;
|
|
298
|
+
}
|
|
299
|
+
/** Tag-optimized remove via swap-and-pop: skip column swap/pop entirely. */
|
|
300
|
+
remove_entity_tag(t) {
|
|
301
|
+
const e = this.length - 1;
|
|
302
|
+
let s = -1;
|
|
303
|
+
return t !== e && (this.entity_ids[t] = this.entity_ids[e], s = m(this.entity_ids[t])), this.entity_ids.pop(), this.length--, s;
|
|
304
|
+
}
|
|
305
|
+
get_edge(t) {
|
|
306
|
+
return this.edges[t];
|
|
307
|
+
}
|
|
308
|
+
set_edge(t, e) {
|
|
309
|
+
this.edges[t] = e;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
function j(a, t, e) {
|
|
313
|
+
const s = a.get(t);
|
|
314
|
+
s !== void 0 ? s.push(e) : a.set(t, [e]);
|
|
315
|
+
}
|
|
316
|
+
const w = -1, M = Object.freeze(/* @__PURE__ */ Object.create(null));
|
|
317
|
+
class tt {
|
|
318
|
+
constructor() {
|
|
319
|
+
// --- Entity ID management ---
|
|
320
|
+
// Generational slot allocator: entity_generations[index] holds the current
|
|
321
|
+
// generation for that slot. Free indices are recycled via a stack.
|
|
322
|
+
l(this, "entity_generations", []);
|
|
323
|
+
l(this, "entity_high_water", 0);
|
|
324
|
+
l(this, "entity_free_indices", []);
|
|
325
|
+
l(this, "entity_alive_count", 0);
|
|
326
|
+
// --- Component metadata ---
|
|
327
|
+
// Parallel array indexed by ComponentID: field_names and field_index
|
|
328
|
+
// for building archetype column layouts.
|
|
329
|
+
l(this, "component_metas", []);
|
|
330
|
+
l(this, "component_count", 0);
|
|
331
|
+
// --- Event channels ---
|
|
332
|
+
// Parallel array indexed by EventID: each channel holds SoA columns + reader.
|
|
333
|
+
l(this, "event_channels", []);
|
|
334
|
+
l(this, "event_count", 0);
|
|
335
|
+
// --- Archetype management ---
|
|
336
|
+
l(this, "archetypes", []);
|
|
337
|
+
// Hash-bucketed lookup: BitSet.hash() → ArchetypeID[] for deduplication
|
|
338
|
+
l(this, "archetype_map", /* @__PURE__ */ new Map());
|
|
339
|
+
l(this, "next_archetype_id", 0);
|
|
340
|
+
// Inverted index: ComponentID → set of ArchetypeIDs containing that component.
|
|
341
|
+
// Used by get_matching_archetypes to start from the smallest set.
|
|
342
|
+
l(this, "component_index", /* @__PURE__ */ new Map());
|
|
343
|
+
// Registered queries: the Store pushes newly-created archetypes into matching
|
|
344
|
+
// query result arrays, so queries are always up-to-date.
|
|
345
|
+
l(this, "registered_queries", []);
|
|
346
|
+
l(this, "empty_archetype_id");
|
|
347
|
+
// entity_index → ArchetypeID (UNASSIGNED = not in any archetype)
|
|
348
|
+
l(this, "entity_archetype", []);
|
|
349
|
+
// entity_index → row within its archetype (UNASSIGNED = no row)
|
|
350
|
+
l(this, "entity_row", []);
|
|
351
|
+
// --- Deferred operation buffers ---
|
|
352
|
+
// Flat parallel arrays: pending_add_ids[i], pending_add_defs[i], pending_add_values[i]
|
|
353
|
+
// describe one deferred add. No per-operation object allocation.
|
|
354
|
+
l(this, "pending_destroy", []);
|
|
355
|
+
l(this, "pending_add_ids", []);
|
|
356
|
+
l(this, "pending_add_defs", []);
|
|
357
|
+
l(this, "pending_add_values", []);
|
|
358
|
+
l(this, "pending_remove_ids", []);
|
|
359
|
+
l(this, "pending_remove_defs", []);
|
|
360
|
+
this.empty_archetype_id = this.arch_get_or_create_from_mask(new T());
|
|
361
|
+
}
|
|
362
|
+
// =======================================================
|
|
363
|
+
// Archetype graph
|
|
364
|
+
// =======================================================
|
|
365
|
+
arch_get(t) {
|
|
366
|
+
if (process.env.NODE_ENV !== "production" && (t < 0 || t >= this.archetypes.length))
|
|
367
|
+
throw new g(
|
|
368
|
+
u.ARCHETYPE_NOT_FOUND,
|
|
369
|
+
`Archetype with ID ${t} not found`
|
|
370
|
+
);
|
|
371
|
+
return this.archetypes[t];
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Find or create an archetype for the given component mask.
|
|
375
|
+
* Also updates the component_index and pushes into matching registered queries.
|
|
376
|
+
*/
|
|
377
|
+
arch_get_or_create_from_mask(t) {
|
|
378
|
+
const e = t.hash(), s = this.archetype_map.get(e);
|
|
379
|
+
if (s !== void 0) {
|
|
380
|
+
for (let h = 0; h < s.length; h++)
|
|
381
|
+
if (this.archetypes[s[h]].mask.equals(t))
|
|
382
|
+
return s[h];
|
|
383
|
+
}
|
|
384
|
+
const n = Z(this.next_archetype_id++), r = [];
|
|
385
|
+
t.for_each((h) => {
|
|
386
|
+
const i = h, c = this.component_metas[i];
|
|
387
|
+
c && c.field_names.length > 0 && r.push({
|
|
388
|
+
component_id: i,
|
|
389
|
+
field_names: c.field_names,
|
|
390
|
+
field_index: c.field_index
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
const o = new H(n, t, r);
|
|
394
|
+
this.archetypes.push(o), j(this.archetype_map, e, n), t.for_each((h) => {
|
|
395
|
+
const i = h;
|
|
396
|
+
let c = this.component_index.get(i);
|
|
397
|
+
c || (c = /* @__PURE__ */ new Set(), this.component_index.set(i, c)), c.add(n);
|
|
398
|
+
});
|
|
399
|
+
const _ = this.registered_queries;
|
|
400
|
+
for (let h = 0; h < _.length; h++) {
|
|
401
|
+
const i = _[h];
|
|
402
|
+
o.matches(i.include_mask) && (!i.exclude_mask || !o.mask.overlaps(i.exclude_mask)) && (!i.any_of_mask || o.mask.overlaps(i.any_of_mask)) && i.result.push(o);
|
|
403
|
+
}
|
|
404
|
+
return n;
|
|
405
|
+
}
|
|
406
|
+
/** Resolve "add component_id to archetype_id" → target ArchetypeID. Caches the edge. */
|
|
407
|
+
arch_resolve_add(t, e) {
|
|
408
|
+
const s = this.arch_get(t);
|
|
409
|
+
if (s.mask.has(e)) return t;
|
|
410
|
+
const n = s.get_edge(e);
|
|
411
|
+
if ((n == null ? void 0 : n.add) != null) return n.add;
|
|
412
|
+
const r = this.arch_get_or_create_from_mask(
|
|
413
|
+
s.mask.copy_with_set(e)
|
|
414
|
+
);
|
|
415
|
+
return this.arch_cache_edge(s, this.arch_get(r), e), r;
|
|
416
|
+
}
|
|
417
|
+
/** Resolve "remove component_id from archetype_id" → target ArchetypeID. Caches the edge. */
|
|
418
|
+
arch_resolve_remove(t, e) {
|
|
419
|
+
const s = this.arch_get(t);
|
|
420
|
+
if (!s.mask.has(e)) return t;
|
|
421
|
+
const n = s.get_edge(e);
|
|
422
|
+
if ((n == null ? void 0 : n.remove) != null) return n.remove;
|
|
423
|
+
const r = this.arch_get_or_create_from_mask(
|
|
424
|
+
s.mask.copy_with_clear(e)
|
|
425
|
+
);
|
|
426
|
+
return this.arch_cache_edge(this.arch_get(r), s, e), r;
|
|
427
|
+
}
|
|
428
|
+
/** Cache a bidirectional add/remove edge between two archetypes. */
|
|
429
|
+
arch_cache_edge(t, e, s) {
|
|
430
|
+
const n = t.get_edge(s) ?? {
|
|
431
|
+
add: null,
|
|
432
|
+
remove: null
|
|
433
|
+
};
|
|
434
|
+
n.add = e.id, t.set_edge(s, n);
|
|
435
|
+
const r = e.get_edge(s) ?? { add: null, remove: null };
|
|
436
|
+
r.remove = t.id, e.set_edge(s, r);
|
|
437
|
+
}
|
|
438
|
+
// =======================================================
|
|
439
|
+
// Entity lifecycle
|
|
440
|
+
// =======================================================
|
|
441
|
+
create_entity() {
|
|
442
|
+
let t, e;
|
|
443
|
+
this.entity_free_indices.length > 0 ? (t = this.entity_free_indices.pop(), e = this.entity_generations[t]) : (t = this.entity_high_water++, this.entity_generations[t] = 0, e = 0), this.entity_alive_count++;
|
|
444
|
+
const s = B(t, e);
|
|
445
|
+
return this.entity_archetype[t] = this.empty_archetype_id, this.entity_row[t] = w, s;
|
|
446
|
+
}
|
|
447
|
+
/** Immediately destroy an entity, removing it from its archetype. */
|
|
448
|
+
destroy_entity(t) {
|
|
449
|
+
if (!this.is_alive(t)) {
|
|
450
|
+
if (process.env.NODE_ENV !== "production") throw new g(u.ENTITY_NOT_ALIVE);
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
const e = m(t), s = this.entity_row[e];
|
|
454
|
+
if (s !== w) {
|
|
455
|
+
const o = this.arch_get(this.entity_archetype[e]).remove_entity(s);
|
|
456
|
+
o !== -1 && (this.entity_row[o] = s);
|
|
457
|
+
}
|
|
458
|
+
this.entity_archetype[e] = w, this.entity_row[e] = w;
|
|
459
|
+
const n = R(t);
|
|
460
|
+
if (process.env.NODE_ENV !== "production" && n >= k)
|
|
461
|
+
throw new g(u.EID_MAX_GEN_OVERFLOW);
|
|
462
|
+
this.entity_generations[e] = n + 1 & k, this.entity_free_indices.push(e), this.entity_alive_count--;
|
|
463
|
+
}
|
|
464
|
+
is_alive(t) {
|
|
465
|
+
const e = m(t);
|
|
466
|
+
return e < this.entity_high_water && this.entity_generations[e] === R(t);
|
|
467
|
+
}
|
|
468
|
+
get entity_count() {
|
|
469
|
+
return this.entity_alive_count;
|
|
470
|
+
}
|
|
471
|
+
// =======================================================
|
|
472
|
+
// Deferred destruction
|
|
473
|
+
// =======================================================
|
|
474
|
+
destroy_entity_deferred(t) {
|
|
475
|
+
if (process.env.NODE_ENV !== "production" && !this.is_alive(t))
|
|
476
|
+
throw new g(u.ENTITY_NOT_ALIVE);
|
|
477
|
+
this.pending_destroy.push(t);
|
|
478
|
+
}
|
|
479
|
+
/** Flush all buffered entity destructions in batch. */
|
|
480
|
+
flush_destroyed() {
|
|
481
|
+
const t = this.pending_destroy;
|
|
482
|
+
if (t.length === 0) return;
|
|
483
|
+
const e = this.entity_archetype, s = this.entity_row, n = this.entity_generations, r = this.archetypes, o = this.entity_high_water;
|
|
484
|
+
for (let _ = 0; _ < t.length; _++) {
|
|
485
|
+
const h = t[_], i = h & S, c = h >> D;
|
|
486
|
+
if (i >= o || n[i] !== c) continue;
|
|
487
|
+
const d = s[i];
|
|
488
|
+
if (d !== w) {
|
|
489
|
+
const f = r[e[i]], p = f.has_columns ? f.remove_entity(d) : f.remove_entity_tag(d);
|
|
490
|
+
p !== -1 && (s[p] = d);
|
|
491
|
+
}
|
|
492
|
+
if (e[i] = w, s[i] = w, process.env.NODE_ENV !== "production" && c >= k)
|
|
493
|
+
throw new g(u.EID_MAX_GEN_OVERFLOW);
|
|
494
|
+
n[i] = c + 1 & k, this.entity_free_indices.push(i), this.entity_alive_count--;
|
|
495
|
+
}
|
|
496
|
+
t.length = 0;
|
|
497
|
+
}
|
|
498
|
+
get pending_destroy_count() {
|
|
499
|
+
return this.pending_destroy.length;
|
|
500
|
+
}
|
|
501
|
+
add_component_deferred(t, e, s) {
|
|
502
|
+
if (process.env.NODE_ENV !== "production" && !this.is_alive(t))
|
|
503
|
+
throw new g(u.ENTITY_NOT_ALIVE);
|
|
504
|
+
this.pending_add_ids.push(t), this.pending_add_defs.push(e), this.pending_add_values.push(s ?? M);
|
|
505
|
+
}
|
|
506
|
+
remove_component_deferred(t, e) {
|
|
507
|
+
if (process.env.NODE_ENV !== "production" && !this.is_alive(t))
|
|
508
|
+
throw new g(u.ENTITY_NOT_ALIVE);
|
|
509
|
+
this.pending_remove_ids.push(t), this.pending_remove_defs.push(e);
|
|
510
|
+
}
|
|
511
|
+
flush_structural() {
|
|
512
|
+
this.pending_add_ids.length > 0 && this._flush_adds(), this.pending_remove_ids.length > 0 && this._flush_removes();
|
|
513
|
+
}
|
|
514
|
+
/** Batch-apply all deferred component additions. */
|
|
515
|
+
_flush_adds() {
|
|
516
|
+
const t = this.pending_add_ids, e = this.pending_add_defs, s = this.pending_add_values, n = t.length, r = this.entity_archetype, o = this.entity_row, _ = this.entity_generations, h = this.archetypes, i = this.component_metas, c = this.entity_high_water;
|
|
517
|
+
for (let d = 0; d < n; d++) {
|
|
518
|
+
const f = t[d], p = f & S, v = f >> D;
|
|
519
|
+
if (p >= c || _[p] !== v) continue;
|
|
520
|
+
const O = r[p], E = e[d], y = h[O];
|
|
521
|
+
if (y.mask.has(E)) {
|
|
522
|
+
i[E].field_names.length > 0 && y.write_fields(o[p], E, s[d]);
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
const A = this.arch_resolve_add(O, E), N = h[A], I = o[p], x = !N.has_columns && !y.has_columns, V = x ? N.add_entity_tag(f) : N.add_entity(f);
|
|
526
|
+
if (I !== w) {
|
|
527
|
+
x || N.copy_shared_from(y, I, V);
|
|
528
|
+
const q = x ? y.remove_entity_tag(I) : y.remove_entity(I);
|
|
529
|
+
q !== -1 && (o[q] = I);
|
|
530
|
+
}
|
|
531
|
+
i[E].field_names.length > 0 && N.write_fields(V, E, s[d]), r[p] = A, o[p] = V;
|
|
532
|
+
}
|
|
533
|
+
t.length = 0, e.length = 0, s.length = 0;
|
|
534
|
+
}
|
|
535
|
+
/** Batch-apply all deferred component removals. */
|
|
536
|
+
_flush_removes() {
|
|
537
|
+
const t = this.pending_remove_ids, e = this.pending_remove_defs, s = t.length, n = this.entity_archetype, r = this.entity_row, o = this.entity_generations, _ = this.archetypes, h = this.entity_high_water;
|
|
538
|
+
for (let i = 0; i < s; i++) {
|
|
539
|
+
const c = t[i], d = c & S, f = c >> D;
|
|
540
|
+
if (d >= h || o[d] !== f) continue;
|
|
541
|
+
const p = n[d], v = e[i], O = _[p];
|
|
542
|
+
if (!O.mask.has(v)) continue;
|
|
543
|
+
const E = this.arch_resolve_remove(p, v), y = _[E], A = r[d], N = !y.has_columns && !O.has_columns, I = N ? y.add_entity_tag(c) : y.add_entity(c);
|
|
544
|
+
N || y.copy_shared_from(O, A, I);
|
|
545
|
+
const x = N ? O.remove_entity_tag(A) : O.remove_entity(A);
|
|
546
|
+
x !== -1 && (r[x] = A), n[d] = E, r[d] = I;
|
|
547
|
+
}
|
|
548
|
+
t.length = 0, e.length = 0;
|
|
549
|
+
}
|
|
550
|
+
get pending_structural_count() {
|
|
551
|
+
return this.pending_add_ids.length + this.pending_remove_ids.length;
|
|
552
|
+
}
|
|
553
|
+
// =======================================================
|
|
554
|
+
// Component registration
|
|
555
|
+
// =======================================================
|
|
556
|
+
register_component(t) {
|
|
557
|
+
const e = Q(this.component_count++), s = t, n = /* @__PURE__ */ Object.create(null);
|
|
558
|
+
for (let r = 0; r < s.length; r++)
|
|
559
|
+
n[s[r]] = r;
|
|
560
|
+
return this.component_metas.push({ field_names: s, field_index: n }), e;
|
|
561
|
+
}
|
|
562
|
+
add_component(t, e, s) {
|
|
563
|
+
if (!this.is_alive(t)) {
|
|
564
|
+
if (process.env.NODE_ENV !== "production") throw new g(u.ENTITY_NOT_ALIVE);
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
const n = m(t), r = this.entity_archetype[n], o = this.arch_get(r);
|
|
568
|
+
if (o.has_component(e)) {
|
|
569
|
+
o.write_fields(
|
|
570
|
+
this.entity_row[n],
|
|
571
|
+
e,
|
|
572
|
+
s
|
|
573
|
+
);
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
const _ = this.arch_resolve_add(
|
|
577
|
+
r,
|
|
578
|
+
e
|
|
579
|
+
), h = this.arch_get(_), i = this.entity_row[n], c = h.add_entity(t);
|
|
580
|
+
if (i !== w) {
|
|
581
|
+
h.copy_shared_from(o, i, c);
|
|
582
|
+
const d = o.remove_entity(i);
|
|
583
|
+
d !== -1 && (this.entity_row[d] = i);
|
|
584
|
+
}
|
|
585
|
+
h.write_fields(
|
|
586
|
+
c,
|
|
587
|
+
e,
|
|
588
|
+
s
|
|
589
|
+
), this.entity_archetype[n] = _, this.entity_row[n] = c;
|
|
590
|
+
}
|
|
591
|
+
/** Add multiple components in one transition (resolves final archetype, then moves once). */
|
|
592
|
+
add_components(t, e) {
|
|
593
|
+
if (!this.is_alive(t)) {
|
|
594
|
+
if (process.env.NODE_ENV !== "production") throw new g(u.ENTITY_NOT_ALIVE);
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
const s = m(t), n = this.entity_archetype[s];
|
|
598
|
+
let r = n;
|
|
599
|
+
for (let o = 0; o < e.length; o++)
|
|
600
|
+
r = this.arch_resolve_add(
|
|
601
|
+
r,
|
|
602
|
+
e[o].def
|
|
603
|
+
);
|
|
604
|
+
if (r !== n) {
|
|
605
|
+
const o = this.arch_get(n), _ = this.arch_get(r), h = this.entity_row[s], i = _.add_entity(t);
|
|
606
|
+
if (h !== w) {
|
|
607
|
+
_.copy_shared_from(o, h, i);
|
|
608
|
+
const c = o.remove_entity(h);
|
|
609
|
+
c !== -1 && (this.entity_row[c] = h);
|
|
610
|
+
}
|
|
611
|
+
for (let c = 0; c < e.length; c++)
|
|
612
|
+
_.write_fields(
|
|
613
|
+
i,
|
|
614
|
+
e[c].def,
|
|
615
|
+
e[c].values ?? M
|
|
616
|
+
);
|
|
617
|
+
this.entity_archetype[s] = r, this.entity_row[s] = i;
|
|
618
|
+
} else {
|
|
619
|
+
const o = this.arch_get(n), _ = this.entity_row[s];
|
|
620
|
+
for (let h = 0; h < e.length; h++)
|
|
621
|
+
o.write_fields(
|
|
622
|
+
_,
|
|
623
|
+
e[h].def,
|
|
624
|
+
e[h].values ?? M
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
remove_component(t, e) {
|
|
629
|
+
if (!this.is_alive(t)) {
|
|
630
|
+
if (process.env.NODE_ENV !== "production") throw new g(u.ENTITY_NOT_ALIVE);
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
const s = m(t), n = this.entity_archetype[s], r = this.arch_get(n);
|
|
634
|
+
if (!r.has_component(e)) return;
|
|
635
|
+
const o = this.arch_resolve_remove(
|
|
636
|
+
n,
|
|
637
|
+
e
|
|
638
|
+
), _ = this.arch_get(o), h = this.entity_row[s], i = _.add_entity(t);
|
|
639
|
+
_.copy_shared_from(r, h, i);
|
|
640
|
+
const c = r.remove_entity(h);
|
|
641
|
+
c !== -1 && (this.entity_row[c] = h), this.entity_archetype[s] = o, this.entity_row[s] = i;
|
|
642
|
+
}
|
|
643
|
+
/** Remove multiple components in one transition (resolves final archetype, then moves once). */
|
|
644
|
+
remove_components(t, e) {
|
|
645
|
+
if (!this.is_alive(t)) {
|
|
646
|
+
if (process.env.NODE_ENV !== "production") throw new g(u.ENTITY_NOT_ALIVE);
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
const s = m(t), n = this.entity_archetype[s];
|
|
650
|
+
let r = n;
|
|
651
|
+
for (let d = 0; d < e.length; d++)
|
|
652
|
+
r = this.arch_resolve_remove(
|
|
653
|
+
r,
|
|
654
|
+
e[d]
|
|
655
|
+
);
|
|
656
|
+
if (r === n) return;
|
|
657
|
+
const o = this.arch_get(n), _ = this.arch_get(r), h = this.entity_row[s], i = _.add_entity(t);
|
|
658
|
+
_.copy_shared_from(o, h, i);
|
|
659
|
+
const c = o.remove_entity(h);
|
|
660
|
+
c !== -1 && (this.entity_row[c] = h), this.entity_archetype[s] = r, this.entity_row[s] = i;
|
|
661
|
+
}
|
|
662
|
+
has_component(t, e) {
|
|
663
|
+
if (!this.is_alive(t)) {
|
|
664
|
+
if (process.env.NODE_ENV !== "production") throw new g(u.ENTITY_NOT_ALIVE);
|
|
665
|
+
return !1;
|
|
666
|
+
}
|
|
667
|
+
const s = m(t);
|
|
668
|
+
return this.arch_get(
|
|
669
|
+
this.entity_archetype[s]
|
|
670
|
+
).has_component(e);
|
|
671
|
+
}
|
|
672
|
+
// =======================================================
|
|
673
|
+
// Direct data access (used by SystemContext)
|
|
674
|
+
// =======================================================
|
|
675
|
+
get_entity_archetype(t) {
|
|
676
|
+
return this.arch_get(
|
|
677
|
+
this.entity_archetype[m(t)]
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
get_entity_row(t) {
|
|
681
|
+
return this.entity_row[m(t)];
|
|
682
|
+
}
|
|
683
|
+
// =======================================================
|
|
684
|
+
// Query support
|
|
685
|
+
// =======================================================
|
|
686
|
+
/**
|
|
687
|
+
* Find all archetypes matching the given masks.
|
|
688
|
+
* Uses the inverted component_index to start from the component with the
|
|
689
|
+
* fewest archetypes, minimizing the number of superset checks.
|
|
690
|
+
*/
|
|
691
|
+
get_matching_archetypes(t, e, s) {
|
|
692
|
+
const n = t._words;
|
|
693
|
+
let r = !1;
|
|
694
|
+
for (let i = 0; i < n.length; i++)
|
|
695
|
+
if (n[i] !== 0) {
|
|
696
|
+
r = !0;
|
|
697
|
+
break;
|
|
698
|
+
}
|
|
699
|
+
if (!r)
|
|
700
|
+
return this.archetypes.filter(
|
|
701
|
+
(i) => (!e || !i.mask.overlaps(e)) && (!s || i.mask.overlaps(s))
|
|
702
|
+
);
|
|
703
|
+
let o, _ = !1;
|
|
704
|
+
for (let i = 0; i < n.length; i++) {
|
|
705
|
+
let c = n[i];
|
|
706
|
+
if (c === 0) continue;
|
|
707
|
+
const d = i << 5;
|
|
708
|
+
for (; c !== 0; ) {
|
|
709
|
+
const f = c & -c >>> 0, p = d + (31 - Math.clz32(f));
|
|
710
|
+
c ^= f;
|
|
711
|
+
const v = this.component_index.get(p);
|
|
712
|
+
if (!v || v.size === 0) {
|
|
713
|
+
_ = !0;
|
|
714
|
+
break;
|
|
715
|
+
}
|
|
716
|
+
(!o || v.size < o.size) && (o = v);
|
|
717
|
+
}
|
|
718
|
+
if (_) break;
|
|
719
|
+
}
|
|
720
|
+
if (_ || !o) return [];
|
|
721
|
+
const h = [];
|
|
722
|
+
for (const i of o) {
|
|
723
|
+
const c = this.arch_get(i);
|
|
724
|
+
c.matches(t) && (!e || !c.mask.overlaps(e)) && (!s || c.mask.overlaps(s)) && h.push(c);
|
|
725
|
+
}
|
|
726
|
+
return h;
|
|
727
|
+
}
|
|
728
|
+
/**
|
|
729
|
+
* Register a live query. Returns a mutable Archetype[] that this Store will
|
|
730
|
+
* push newly-created matching archetypes into, keeping the query always up-to-date.
|
|
731
|
+
*/
|
|
732
|
+
register_query(t, e, s) {
|
|
733
|
+
const n = this.get_matching_archetypes(
|
|
734
|
+
t,
|
|
735
|
+
e,
|
|
736
|
+
s
|
|
737
|
+
);
|
|
738
|
+
return this.registered_queries.push({
|
|
739
|
+
include_mask: t.copy(),
|
|
740
|
+
exclude_mask: e ? e.copy() : null,
|
|
741
|
+
any_of_mask: s ? s.copy() : null,
|
|
742
|
+
result: n
|
|
743
|
+
}), n;
|
|
744
|
+
}
|
|
745
|
+
get archetype_count() {
|
|
746
|
+
return this.archetypes.length;
|
|
747
|
+
}
|
|
748
|
+
// =======================================================
|
|
749
|
+
// Event channels
|
|
750
|
+
// =======================================================
|
|
751
|
+
register_event(t) {
|
|
752
|
+
const e = K(this.event_count++), s = new J(t);
|
|
753
|
+
return this.event_channels.push(s), e;
|
|
754
|
+
}
|
|
755
|
+
emit_event(t, e) {
|
|
756
|
+
this.event_channels[t].emit(e);
|
|
757
|
+
}
|
|
758
|
+
emit_signal(t) {
|
|
759
|
+
this.event_channels[t].emit_signal();
|
|
760
|
+
}
|
|
761
|
+
get_event_reader(t) {
|
|
762
|
+
return this.event_channels[t].reader;
|
|
763
|
+
}
|
|
764
|
+
clear_events() {
|
|
765
|
+
const t = this.event_channels;
|
|
766
|
+
for (let e = 0; e < t.length; e++)
|
|
767
|
+
t[e].clear();
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
var et = /* @__PURE__ */ ((a) => (a.PRE_STARTUP = "PRE_STARTUP", a.STARTUP = "STARTUP", a.POST_STARTUP = "POST_STARTUP", a.PRE_UPDATE = "PRE_UPDATE", a.UPDATE = "UPDATE", a.POST_UPDATE = "POST_UPDATE", a))(et || {});
|
|
771
|
+
const L = [
|
|
772
|
+
"PRE_STARTUP",
|
|
773
|
+
"STARTUP",
|
|
774
|
+
"POST_STARTUP"
|
|
775
|
+
/* POST_STARTUP */
|
|
776
|
+
], U = [
|
|
777
|
+
"PRE_UPDATE",
|
|
778
|
+
"UPDATE",
|
|
779
|
+
"POST_UPDATE"
|
|
780
|
+
/* POST_UPDATE */
|
|
781
|
+
];
|
|
782
|
+
class st {
|
|
783
|
+
constructor() {
|
|
784
|
+
l(this, "label_systems", /* @__PURE__ */ new Map());
|
|
785
|
+
l(this, "sorted_cache", /* @__PURE__ */ new Map());
|
|
786
|
+
l(this, "system_index", /* @__PURE__ */ new Map());
|
|
787
|
+
l(this, "next_insertion_order", 0);
|
|
788
|
+
for (let t = 0; t < L.length; t++)
|
|
789
|
+
this.label_systems.set(L[t], []);
|
|
790
|
+
for (let t = 0; t < U.length; t++)
|
|
791
|
+
this.label_systems.set(U[t], []);
|
|
792
|
+
}
|
|
793
|
+
add_systems(t, ...e) {
|
|
794
|
+
for (const s of e) {
|
|
795
|
+
const n = "system" in s ? s.system : s, r = "system" in s ? s.ordering : void 0;
|
|
796
|
+
if (process.env.NODE_ENV !== "production" && this.system_index.has(n))
|
|
797
|
+
throw new g(
|
|
798
|
+
u.DUPLICATE_SYSTEM,
|
|
799
|
+
`System ${n.id} is already scheduled`
|
|
800
|
+
);
|
|
801
|
+
const o = {
|
|
802
|
+
descriptor: n,
|
|
803
|
+
insertion_order: this.next_insertion_order++,
|
|
804
|
+
before: new Set((r == null ? void 0 : r.before) ?? []),
|
|
805
|
+
after: new Set((r == null ? void 0 : r.after) ?? [])
|
|
806
|
+
};
|
|
807
|
+
this.label_systems.get(t).push(o), this.system_index.set(n, t), this.sorted_cache.delete(t);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
remove_system(t) {
|
|
811
|
+
const e = this.system_index.get(t);
|
|
812
|
+
if (e === void 0) return;
|
|
813
|
+
const s = this.label_systems.get(e), n = s.findIndex((r) => r.descriptor === t);
|
|
814
|
+
if (n !== -1) {
|
|
815
|
+
const r = s.length - 1;
|
|
816
|
+
n !== r && (s[n] = s[r]), s.pop();
|
|
817
|
+
for (const o of s)
|
|
818
|
+
o.before.delete(t), o.after.delete(t);
|
|
819
|
+
}
|
|
820
|
+
this.system_index.delete(t), this.sorted_cache.delete(e);
|
|
821
|
+
}
|
|
822
|
+
run_startup(t) {
|
|
823
|
+
for (const e of L)
|
|
824
|
+
this.run_label(e, t, 0);
|
|
825
|
+
}
|
|
826
|
+
run_update(t, e) {
|
|
827
|
+
for (const s of U)
|
|
828
|
+
this.run_label(s, t, e);
|
|
829
|
+
}
|
|
830
|
+
get_all_systems() {
|
|
831
|
+
const t = [];
|
|
832
|
+
for (const e of this.label_systems.values())
|
|
833
|
+
for (const s of e)
|
|
834
|
+
t.push(s.descriptor);
|
|
835
|
+
return t;
|
|
836
|
+
}
|
|
837
|
+
has_system(t) {
|
|
838
|
+
return this.system_index.has(t);
|
|
839
|
+
}
|
|
840
|
+
clear() {
|
|
841
|
+
for (const t of this.label_systems.values())
|
|
842
|
+
t.length = 0;
|
|
843
|
+
this.sorted_cache.clear(), this.system_index.clear();
|
|
844
|
+
}
|
|
845
|
+
run_label(t, e, s) {
|
|
846
|
+
const n = this.get_sorted(t);
|
|
847
|
+
for (let r = 0; r < n.length; r++)
|
|
848
|
+
n[r].fn(e, s);
|
|
849
|
+
e.flush();
|
|
850
|
+
}
|
|
851
|
+
get_sorted(t) {
|
|
852
|
+
const e = this.sorted_cache.get(t);
|
|
853
|
+
if (e !== void 0) return e;
|
|
854
|
+
const s = this.label_systems.get(t), n = this.topological_sort(s, t);
|
|
855
|
+
return this.sorted_cache.set(t, n), n;
|
|
856
|
+
}
|
|
857
|
+
/**
|
|
858
|
+
* Kahn's algorithm: BFS-based topological sort.
|
|
859
|
+
* Uses insertion_order as a stable tiebreaker (lower insertion order runs first).
|
|
860
|
+
*/
|
|
861
|
+
topological_sort(t, e) {
|
|
862
|
+
if (t.length === 0) return [];
|
|
863
|
+
const s = /* @__PURE__ */ new Map(), n = /* @__PURE__ */ new Map(), r = /* @__PURE__ */ new Map(), o = /* @__PURE__ */ new Set();
|
|
864
|
+
for (const i of t)
|
|
865
|
+
s.set(i.descriptor, /* @__PURE__ */ new Set()), n.set(i.descriptor, 0), r.set(i.descriptor, i.insertion_order), o.add(i.descriptor);
|
|
866
|
+
for (const i of t) {
|
|
867
|
+
for (const c of i.before)
|
|
868
|
+
o.has(c) && (s.get(i.descriptor).add(c), n.set(c, n.get(c) + 1));
|
|
869
|
+
for (const c of i.after)
|
|
870
|
+
o.has(c) && (s.get(c).add(i.descriptor), n.set(i.descriptor, n.get(i.descriptor) + 1));
|
|
871
|
+
}
|
|
872
|
+
let _ = [];
|
|
873
|
+
for (const i of t)
|
|
874
|
+
n.get(i.descriptor) === 0 && _.push(i.descriptor);
|
|
875
|
+
_.sort((i, c) => r.get(c) - r.get(i));
|
|
876
|
+
const h = [];
|
|
877
|
+
for (; _.length > 0; ) {
|
|
878
|
+
const i = _.pop();
|
|
879
|
+
h.push(i);
|
|
880
|
+
for (const c of s.get(i)) {
|
|
881
|
+
const d = n.get(c) - 1;
|
|
882
|
+
n.set(c, d), d === 0 && _.push(c);
|
|
883
|
+
}
|
|
884
|
+
_.sort((c, d) => r.get(d) - r.get(c));
|
|
885
|
+
}
|
|
886
|
+
if (h.length !== t.length) {
|
|
887
|
+
const i = new Set(h), c = t.filter((d) => !i.has(d.descriptor)).map((d) => `system_${d.descriptor.id}`);
|
|
888
|
+
throw new g(
|
|
889
|
+
u.CIRCULAR_SYSTEM_DEPENDENCY,
|
|
890
|
+
`Circular system dependency detected in ${e}: [${c.join(", ")}]`
|
|
891
|
+
);
|
|
892
|
+
}
|
|
893
|
+
return h;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
const nt = Object.freeze(/* @__PURE__ */ Object.create(null));
|
|
897
|
+
class rt {
|
|
898
|
+
constructor(t, e, s, n, r, o) {
|
|
899
|
+
l(this, "_archetypes");
|
|
900
|
+
l(this, "_defs");
|
|
901
|
+
l(this, "_resolver");
|
|
902
|
+
l(this, "_include");
|
|
903
|
+
l(this, "_exclude");
|
|
904
|
+
l(this, "_any_of");
|
|
905
|
+
// Pre-allocated args buffer for each() — avoids allocating a new array per
|
|
906
|
+
// archetype. Holds [columnGroup0, columnGroup1, ..., entityCount].
|
|
907
|
+
l(this, "_args_buf");
|
|
908
|
+
this._archetypes = t, this._defs = e, this._resolver = s, this._include = n, this._exclude = r, this._any_of = o, this._args_buf = new Array(e.length + 1);
|
|
909
|
+
}
|
|
910
|
+
/** Number of matching archetypes (including empty ones). */
|
|
911
|
+
get length() {
|
|
912
|
+
return this._archetypes.length;
|
|
913
|
+
}
|
|
914
|
+
/** Total entity count across all matching archetypes. */
|
|
915
|
+
count() {
|
|
916
|
+
const t = this._archetypes;
|
|
917
|
+
let e = 0;
|
|
918
|
+
for (let s = 0; s < t.length; s++) e += t[s].entity_count;
|
|
919
|
+
return e;
|
|
920
|
+
}
|
|
921
|
+
get archetypes() {
|
|
922
|
+
return this._archetypes;
|
|
923
|
+
}
|
|
924
|
+
/** Iterate non-empty archetypes. Skips archetypes with zero entities. */
|
|
925
|
+
*[Symbol.iterator]() {
|
|
926
|
+
const t = this._archetypes;
|
|
927
|
+
for (let e = 0; e < t.length; e++)
|
|
928
|
+
t[e].entity_count > 0 && (yield t[e]);
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* Typed per-archetype iteration. Calls fn once per non-empty archetype
|
|
932
|
+
* with column groups for each queried component, plus the entity count.
|
|
933
|
+
* The system is responsible for the inner loop over entities.
|
|
934
|
+
*/
|
|
935
|
+
each(t) {
|
|
936
|
+
const e = this._archetypes, s = this._defs, n = this._args_buf;
|
|
937
|
+
for (let r = 0; r < e.length; r++) {
|
|
938
|
+
const o = e[r], _ = o.entity_count;
|
|
939
|
+
if (_ !== 0) {
|
|
940
|
+
for (let h = 0; h < s.length; h++)
|
|
941
|
+
n[h] = o.get_column_group(s[h]);
|
|
942
|
+
n[s.length] = _, t.apply(null, n);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
/** Extend required component set. Returns a new (cached) Query. */
|
|
947
|
+
and(...t) {
|
|
948
|
+
const e = this._include.copy(), s = this._defs.slice();
|
|
949
|
+
for (let n = 0; n < t.length; n++)
|
|
950
|
+
e.has(t[n]) || (e.set(t[n]), s.push(t[n]));
|
|
951
|
+
return this._resolver._resolve_query(
|
|
952
|
+
e,
|
|
953
|
+
this._exclude,
|
|
954
|
+
this._any_of,
|
|
955
|
+
s
|
|
956
|
+
);
|
|
957
|
+
}
|
|
958
|
+
/** Exclude archetypes that have any of these components. */
|
|
959
|
+
not(...t) {
|
|
960
|
+
const e = this._exclude ? this._exclude.copy() : new T();
|
|
961
|
+
for (let s = 0; s < t.length; s++) e.set(t[s]);
|
|
962
|
+
return this._resolver._resolve_query(
|
|
963
|
+
this._include,
|
|
964
|
+
e,
|
|
965
|
+
this._any_of,
|
|
966
|
+
this._defs
|
|
967
|
+
);
|
|
968
|
+
}
|
|
969
|
+
/** Require at least one of these components. */
|
|
970
|
+
or(...t) {
|
|
971
|
+
const e = this._any_of ? this._any_of.copy() : new T();
|
|
972
|
+
for (let s = 0; s < t.length; s++) e.set(t[s]);
|
|
973
|
+
return this._resolver._resolve_query(
|
|
974
|
+
this._include,
|
|
975
|
+
this._exclude,
|
|
976
|
+
e,
|
|
977
|
+
this._defs
|
|
978
|
+
);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
class ot {
|
|
982
|
+
constructor(t) {
|
|
983
|
+
this._resolver = t;
|
|
984
|
+
}
|
|
985
|
+
every(...t) {
|
|
986
|
+
const e = new T();
|
|
987
|
+
for (let s = 0; s < t.length; s++) e.set(t[s]);
|
|
988
|
+
return this._resolver._resolve_query(e, null, null, t);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
class it {
|
|
992
|
+
constructor(t) {
|
|
993
|
+
l(this, "store");
|
|
994
|
+
this.store = t;
|
|
995
|
+
}
|
|
996
|
+
create_entity() {
|
|
997
|
+
return this.store.create_entity();
|
|
998
|
+
}
|
|
999
|
+
get_field(t, e, s) {
|
|
1000
|
+
const n = this.store.get_entity_archetype(e), r = this.store.get_entity_row(e);
|
|
1001
|
+
return n.read_field(r, t, s);
|
|
1002
|
+
}
|
|
1003
|
+
set_field(t, e, s, n) {
|
|
1004
|
+
const r = this.store.get_entity_archetype(e), o = this.store.get_entity_row(e), _ = r.get_column(t, s);
|
|
1005
|
+
_[o] = n;
|
|
1006
|
+
}
|
|
1007
|
+
/** Buffer an entity for deferred destruction (applied at phase flush). */
|
|
1008
|
+
destroy_entity(t) {
|
|
1009
|
+
return this.store.destroy_entity_deferred(t), this;
|
|
1010
|
+
}
|
|
1011
|
+
flush_destroyed() {
|
|
1012
|
+
this.store.flush_destroyed();
|
|
1013
|
+
}
|
|
1014
|
+
add_component(t, e, s) {
|
|
1015
|
+
return this.store.add_component_deferred(t, e, s ?? nt), this;
|
|
1016
|
+
}
|
|
1017
|
+
remove_component(t, e) {
|
|
1018
|
+
return this.store.remove_component_deferred(t, e), this;
|
|
1019
|
+
}
|
|
1020
|
+
/** Flush all deferred changes: structural (add/remove) first, then destructions. */
|
|
1021
|
+
flush() {
|
|
1022
|
+
this.store.flush_structural(), this.store.flush_destroyed();
|
|
1023
|
+
}
|
|
1024
|
+
emit(t, e) {
|
|
1025
|
+
e === void 0 ? this.store.emit_signal(t) : this.store.emit_event(t, e);
|
|
1026
|
+
}
|
|
1027
|
+
read(t) {
|
|
1028
|
+
return this.store.get_event_reader(t);
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
const _t = (a) => b(
|
|
1032
|
+
a,
|
|
1033
|
+
P,
|
|
1034
|
+
"SystemID must be a non-negative integer"
|
|
1035
|
+
), ct = Object.freeze(/* @__PURE__ */ Object.create(null));
|
|
1036
|
+
class at {
|
|
1037
|
+
constructor() {
|
|
1038
|
+
l(this, "store");
|
|
1039
|
+
l(this, "schedule");
|
|
1040
|
+
l(this, "ctx");
|
|
1041
|
+
l(this, "systems", /* @__PURE__ */ new Set());
|
|
1042
|
+
l(this, "next_system_id", 0);
|
|
1043
|
+
// Query deduplication: hash(include, exclude, any_of) → bucket of cache entries.
|
|
1044
|
+
// Multiple queries can share the same hash (collision), so each bucket is an array.
|
|
1045
|
+
l(this, "query_cache", /* @__PURE__ */ new Map());
|
|
1046
|
+
// Reusable BitSet for building query masks — avoids allocation per query() call
|
|
1047
|
+
l(this, "scratch_mask", new T());
|
|
1048
|
+
this.store = new tt(), this.schedule = new st(), this.ctx = new it(this.store);
|
|
1049
|
+
}
|
|
1050
|
+
register_component(t) {
|
|
1051
|
+
return this.store.register_component(t);
|
|
1052
|
+
}
|
|
1053
|
+
register_tag() {
|
|
1054
|
+
return this.store.register_component([]);
|
|
1055
|
+
}
|
|
1056
|
+
register_event(t) {
|
|
1057
|
+
return this.store.register_event(t);
|
|
1058
|
+
}
|
|
1059
|
+
register_signal() {
|
|
1060
|
+
return this.store.register_event([]);
|
|
1061
|
+
}
|
|
1062
|
+
create_entity() {
|
|
1063
|
+
return this.store.create_entity();
|
|
1064
|
+
}
|
|
1065
|
+
destroy_entity(t) {
|
|
1066
|
+
this.store.destroy_entity_deferred(t);
|
|
1067
|
+
}
|
|
1068
|
+
is_alive(t) {
|
|
1069
|
+
return this.store.is_alive(t);
|
|
1070
|
+
}
|
|
1071
|
+
get entity_count() {
|
|
1072
|
+
return this.store.entity_count;
|
|
1073
|
+
}
|
|
1074
|
+
add_component(t, e, s) {
|
|
1075
|
+
this.store.add_component(t, e, s ?? ct);
|
|
1076
|
+
}
|
|
1077
|
+
add_components(t, e) {
|
|
1078
|
+
this.store.add_components(t, e);
|
|
1079
|
+
}
|
|
1080
|
+
remove_component(t, e) {
|
|
1081
|
+
return this.store.remove_component(t, e), this;
|
|
1082
|
+
}
|
|
1083
|
+
remove_components(t, ...e) {
|
|
1084
|
+
this.store.remove_components(t, e);
|
|
1085
|
+
}
|
|
1086
|
+
has_component(t, e) {
|
|
1087
|
+
return this.store.has_component(t, e);
|
|
1088
|
+
}
|
|
1089
|
+
get_field(t, e, s) {
|
|
1090
|
+
const n = this.store.get_entity_archetype(e), r = this.store.get_entity_row(e);
|
|
1091
|
+
return n.read_field(r, t, s);
|
|
1092
|
+
}
|
|
1093
|
+
emit(t, e) {
|
|
1094
|
+
e === void 0 ? this.store.emit_signal(t) : this.store.emit_event(t, e);
|
|
1095
|
+
}
|
|
1096
|
+
query(...t) {
|
|
1097
|
+
const e = this.scratch_mask;
|
|
1098
|
+
e._words.fill(0);
|
|
1099
|
+
for (let s = 0; s < t.length; s++)
|
|
1100
|
+
e.set(t[s]);
|
|
1101
|
+
return this._resolve_query(e.copy(), null, null, t);
|
|
1102
|
+
}
|
|
1103
|
+
/** QueryResolver implementation — creates or retrieves a cached Query. */
|
|
1104
|
+
_resolve_query(t, e, s, n) {
|
|
1105
|
+
const r = t.hash(), o = e ? e.hash() : 0, _ = s ? s.hash() : 0, h = r ^ Math.imul(o, 2654435769) ^ Math.imul(_, 1367130551) | 0, i = this._find_cached(h, t, e, s);
|
|
1106
|
+
if (i !== void 0) return i.query;
|
|
1107
|
+
const c = this.store.register_query(
|
|
1108
|
+
t,
|
|
1109
|
+
e ?? void 0,
|
|
1110
|
+
s ?? void 0
|
|
1111
|
+
), d = new rt(
|
|
1112
|
+
c,
|
|
1113
|
+
n,
|
|
1114
|
+
this,
|
|
1115
|
+
t.copy(),
|
|
1116
|
+
(e == null ? void 0 : e.copy()) ?? null,
|
|
1117
|
+
(s == null ? void 0 : s.copy()) ?? null
|
|
1118
|
+
);
|
|
1119
|
+
return j(this.query_cache, h, {
|
|
1120
|
+
include_mask: t.copy(),
|
|
1121
|
+
exclude_mask: (e == null ? void 0 : e.copy()) ?? null,
|
|
1122
|
+
any_of_mask: (s == null ? void 0 : s.copy()) ?? null,
|
|
1123
|
+
query: d
|
|
1124
|
+
}), d;
|
|
1125
|
+
}
|
|
1126
|
+
_find_cached(t, e, s, n) {
|
|
1127
|
+
const r = this.query_cache.get(t);
|
|
1128
|
+
if (r)
|
|
1129
|
+
for (let o = 0; o < r.length; o++) {
|
|
1130
|
+
const _ = r[o];
|
|
1131
|
+
if (!(!_.include_mask.equals(e) || !(s === null ? _.exclude_mask === null : _.exclude_mask !== null && _.exclude_mask.equals(s)) || !(n === null ? _.any_of_mask === null : _.any_of_mask !== null && _.any_of_mask.equals(n))))
|
|
1132
|
+
return _;
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
register_system(t, e) {
|
|
1136
|
+
let s;
|
|
1137
|
+
if (typeof t == "function") {
|
|
1138
|
+
const o = e(new ot(this)), _ = this.ctx;
|
|
1139
|
+
s = { fn: (h, i) => t(o, _, i) };
|
|
1140
|
+
} else
|
|
1141
|
+
s = t;
|
|
1142
|
+
const n = _t(this.next_system_id++), r = Object.freeze(
|
|
1143
|
+
Object.assign({ id: n }, s)
|
|
1144
|
+
);
|
|
1145
|
+
return this.systems.add(r), r;
|
|
1146
|
+
}
|
|
1147
|
+
add_systems(t, ...e) {
|
|
1148
|
+
return this.schedule.add_systems(t, ...e), this;
|
|
1149
|
+
}
|
|
1150
|
+
remove_system(t) {
|
|
1151
|
+
var e;
|
|
1152
|
+
this.schedule.remove_system(t), (e = t.on_removed) == null || e.call(t), this.systems.delete(t);
|
|
1153
|
+
}
|
|
1154
|
+
get system_count() {
|
|
1155
|
+
return this.systems.size;
|
|
1156
|
+
}
|
|
1157
|
+
startup() {
|
|
1158
|
+
var t;
|
|
1159
|
+
for (const e of this.systems.values())
|
|
1160
|
+
(t = e.on_added) == null || t.call(e, this.store);
|
|
1161
|
+
this.schedule.run_startup(this.ctx);
|
|
1162
|
+
}
|
|
1163
|
+
update(t) {
|
|
1164
|
+
this.store.clear_events(), this.schedule.run_update(this.ctx, t);
|
|
1165
|
+
}
|
|
1166
|
+
flush() {
|
|
1167
|
+
this.ctx.flush();
|
|
1168
|
+
}
|
|
1169
|
+
dispose() {
|
|
1170
|
+
var t, e;
|
|
1171
|
+
for (const s of this.systems.values())
|
|
1172
|
+
(t = s.dispose) == null || t.call(s), (e = s.on_removed) == null || e.call(s);
|
|
1173
|
+
this.systems.clear(), this.schedule.clear();
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
export {
|
|
1177
|
+
rt as Query,
|
|
1178
|
+
ot as QueryBuilder,
|
|
1179
|
+
et as SCHEDULE,
|
|
1180
|
+
it as SystemContext,
|
|
1181
|
+
at as World
|
|
1182
|
+
};
|