@forgeax/engine-ecs 0.1.33 → 0.1.34
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 +35 -18
- package/dist/__tests__/query-idle.unit.test.d.ts +2 -0
- package/dist/__tests__/query-idle.unit.test.d.ts.map +1 -0
- package/dist/__tests__/set-allocation.unit.test.d.ts +2 -0
- package/dist/__tests__/set-allocation.unit.test.d.ts.map +1 -0
- package/dist/__tests__/state-projection.unit.test.d.ts +2 -0
- package/dist/__tests__/state-projection.unit.test.d.ts.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +579 -623
- package/dist/index.mjs.map +1 -1
- package/dist/projection/index.d.ts +2 -18
- package/dist/projection/index.d.ts.map +1 -1
- package/dist/projection/index.mjs +433 -23
- package/dist/projection/index.mjs.map +1 -1
- package/dist/projection/state-projection.d.ts +37 -0
- package/dist/projection/state-projection.d.ts.map +1 -0
- package/dist/query/query.d.ts.map +1 -1
- package/dist/shared-ref-store.d.ts +0 -24
- package/dist/shared-ref-store.d.ts.map +1 -1
- package/dist/shared.mjs.map +1 -1
- package/dist/storage/archetype-graph.d.ts +2 -0
- package/dist/storage/archetype-graph.d.ts.map +1 -1
- package/dist/storage/change-detection.d.ts +4 -0
- package/dist/storage/change-detection.d.ts.map +1 -1
- package/dist/storage/table.d.ts +6 -3
- package/dist/storage/table.d.ts.map +1 -1
- package/dist/world-internal.d.ts +0 -2
- package/dist/world-internal.d.ts.map +1 -1
- package/dist/world.d.ts +0 -2
- package/dist/world.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/__tests__/component-version-surface.test.ts +1 -7
- package/src/__tests__/derived-range-writer.contract.test.ts +6 -0
- package/src/__tests__/ecs-core-reduction.characterization.test.ts +2 -3
- package/src/__tests__/execution-conflict-boundary.unit.test.ts +4 -0
- package/src/__tests__/externalization-render-read-lease.unit.test.ts +4 -10
- package/src/__tests__/query-idle.unit.test.ts +19 -0
- package/src/__tests__/set-allocation.unit.test.ts +30 -0
- package/src/__tests__/shared-ref-lifetime.unit.test.ts +1 -2
- package/src/__tests__/shared-ref-store.unit.test.ts +2 -34
- package/src/__tests__/state-projection.unit.test.ts +159 -0
- package/src/__tests__/world-health.contract.test.ts +0 -23
- package/src/index.ts +1 -1
- package/src/projection/index.ts +9 -39
- package/src/projection/state-projection.ts +250 -0
- package/src/query/query.ts +23 -16
- package/src/shared-ref-store.ts +0 -70
- package/src/storage/archetype-graph.ts +15 -1
- package/src/storage/change-detection.ts +36 -4
- package/src/storage/table.ts +20 -1
- package/src/world-internal.ts +0 -2
- package/src/world.ts +35 -38
- package/dist/__tests__/structural-evidence.contract.test-d.d.ts +0 -2
- package/dist/__tests__/structural-evidence.contract.test-d.d.ts.map +0 -1
- package/dist/__tests__/structural-evidence.contract.test.d.ts +0 -2
- package/dist/__tests__/structural-evidence.contract.test.d.ts.map +0 -1
- package/dist/storage/structural-evidence.d.ts +0 -30
- package/dist/storage/structural-evidence.d.ts.map +0 -1
- package/src/__tests__/structural-evidence.contract.test-d.ts +0 -6
- package/src/__tests__/structural-evidence.contract.test.ts +0 -49
- package/src/storage/structural-evidence.ts +0 -64
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import type { Result } from '@forgeax/engine-types';
|
|
2
2
|
import type { Component } from '../component';
|
|
3
3
|
import type { EntityHandle } from '../entity-handle';
|
|
4
|
-
import type { SharedRefMutationRead } from '../shared-ref-store';
|
|
5
|
-
import type { StructuralEvidenceRead } from '../storage/structural-evidence';
|
|
6
4
|
import type { EcsError, World } from '../world';
|
|
7
5
|
export { fillComponentDefaults } from '../component-default-fallback';
|
|
8
6
|
export { ComponentNotDefinedError, InstanceTransformsStrideMismatchError, ManagedBufferOutOfBoundsError, ResourceInvalidValueError, SpawnLightInvalidBoundsError, SpriteAnimationInvalidError, SpriteInstancesCountMismatchError, SpriteInstancesMutuallyExclusiveWithInstancesError, SpriteInstancesRequiresSpriteShaderError, StaleEntityError, } from '../errors';
|
|
9
|
-
/** Read producer-owned typed structural facts without inferring from a World scan. */
|
|
10
|
-
export declare function readStructuralEvidence(world: World, cursor: number): StructuralEvidenceRead;
|
|
11
7
|
export interface RenderProjectionComponentRequest {
|
|
12
8
|
readonly component: Component;
|
|
13
9
|
readonly fields: readonly string[];
|
|
@@ -21,29 +17,17 @@ export interface RenderProjectionSpan {
|
|
|
21
17
|
}
|
|
22
18
|
export interface RenderProjectionSpans {
|
|
23
19
|
readonly generation: number;
|
|
24
|
-
readonly sharedRefEpoch: number;
|
|
25
20
|
readonly spans: readonly RenderProjectionSpan[];
|
|
26
21
|
}
|
|
27
|
-
export interface
|
|
28
|
-
readonly status: 'ok';
|
|
22
|
+
export interface RenderChangeBatch {
|
|
29
23
|
readonly version: RenderReadVersion;
|
|
30
24
|
readonly world: RenderWorldChanges;
|
|
31
|
-
readonly sharedRefs: SharedRefMutationRead;
|
|
32
|
-
}
|
|
33
|
-
export interface RenderChangeBatchRebuild {
|
|
34
|
-
readonly status: 'rebuild';
|
|
35
|
-
readonly version: RenderReadVersion;
|
|
36
|
-
readonly resync: true;
|
|
37
|
-
readonly reason: 'structure-changed';
|
|
38
|
-
readonly world: RenderWorldChanges;
|
|
39
|
-
readonly sharedRefs: SharedRefMutationRead;
|
|
40
25
|
}
|
|
41
26
|
export interface RenderWorldChanges {
|
|
42
27
|
readonly fromEpoch: number;
|
|
43
28
|
readonly toEpoch: number;
|
|
44
29
|
readonly changedComponentIds: readonly number[];
|
|
45
30
|
}
|
|
46
|
-
export type RenderChangeBatch = RenderChangeBatchOk | RenderChangeBatchRebuild;
|
|
47
31
|
export interface RenderReadLease {
|
|
48
32
|
readonly worldIdentity: string;
|
|
49
33
|
readonly generation: number;
|
|
@@ -61,7 +45,6 @@ export declare function readRenderArrayView(world: World, entity: EntityHandle,
|
|
|
61
45
|
export interface RenderReadVersion {
|
|
62
46
|
readonly mutationEpoch: number;
|
|
63
47
|
readonly structureEpoch: number;
|
|
64
|
-
readonly sharedRefEpoch: number;
|
|
65
48
|
}
|
|
66
49
|
/** Create the render-owned lease from the ECS projection boundary. */
|
|
67
50
|
export declare function createRenderReadLease(world: World, token?: object): RenderReadLease;
|
|
@@ -75,4 +58,5 @@ export declare function setDerivedComponent(world: World, entity: EntityHandle,
|
|
|
75
58
|
export declare function routeWorldError(world: World, error: unknown, context?: {
|
|
76
59
|
readonly systemName: string;
|
|
77
60
|
}): void;
|
|
61
|
+
export { createStateProjection, type StateProjection, type StateProjectionBatch, StateProjectionExpiredError, } from './state-projection';
|
|
78
62
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/projection/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/projection/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import '@forgeax/engine-types';
|
|
1
|
+
import { unpackSlot, pack, MAX_SLOT } from '@forgeax/engine-types';
|
|
2
2
|
|
|
3
3
|
// src/component.ts
|
|
4
4
|
|
|
@@ -10,6 +10,9 @@ var definitions = globalSymbols[COMPONENT_REGISTRY] ?? (() => {
|
|
|
10
10
|
globalSymbols[COMPONENT_REGISTRY] = registry;
|
|
11
11
|
return registry;
|
|
12
12
|
})();
|
|
13
|
+
function registerComponentDefinition(component, definition) {
|
|
14
|
+
definitions.definitions.set(component, definition);
|
|
15
|
+
}
|
|
13
16
|
function componentDefinition(component) {
|
|
14
17
|
const definition = definitions.definitions.get(component);
|
|
15
18
|
if (definition === void 0) {
|
|
@@ -17,6 +20,22 @@ function componentDefinition(component) {
|
|
|
17
20
|
}
|
|
18
21
|
return definition;
|
|
19
22
|
}
|
|
23
|
+
function deepFreeze(value) {
|
|
24
|
+
if (value === null || typeof value !== "object" && typeof value !== "function") {
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) {
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
const object = value;
|
|
31
|
+
for (const key of Reflect.ownKeys(object)) {
|
|
32
|
+
const child = object[key];
|
|
33
|
+
if (child !== null && (typeof child === "object" || typeof child === "function")) {
|
|
34
|
+
deepFreeze(child);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return Object.freeze(value);
|
|
38
|
+
}
|
|
20
39
|
|
|
21
40
|
// src/errors/query-and-component-errors.ts
|
|
22
41
|
var ComponentNotDefinedError = class extends Error {
|
|
@@ -248,7 +267,56 @@ var worldInternal = /* @__PURE__ */ Symbol.for(
|
|
|
248
267
|
"forgeax.ecs.worldInternal"
|
|
249
268
|
);
|
|
250
269
|
|
|
270
|
+
// src/execution/shared-kernel.ts
|
|
271
|
+
var WorldPoisonedError = class extends Error {
|
|
272
|
+
code = "world-poisoned";
|
|
273
|
+
expected = "World health is healthy before update";
|
|
274
|
+
hint = "stop scheduling this World and explicitly bootstrap a new World identity";
|
|
275
|
+
detail;
|
|
276
|
+
constructor(worldIdentity, fault) {
|
|
277
|
+
super(`World ${worldIdentity} is poisoned and cannot update.`);
|
|
278
|
+
this.name = "WorldPoisonedError";
|
|
279
|
+
this.detail = { worldIdentity, fault };
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
|
|
251
283
|
// src/errors.ts
|
|
284
|
+
var EntityIndexOverflowError = class extends RangeError {
|
|
285
|
+
name = "EntityIndexOverflowError";
|
|
286
|
+
code = "entity-index-overflow";
|
|
287
|
+
hint;
|
|
288
|
+
constructor(index) {
|
|
289
|
+
const hint = "Entity index exceeds 24-bit max (16777215). Reduce simultaneous entity count or investigate entity leaks.";
|
|
290
|
+
super(
|
|
291
|
+
`Entity index ${index} exceeds 24-bit max (16777215).
|
|
292
|
+
index: ${index}
|
|
293
|
+
hint: ${hint}`
|
|
294
|
+
);
|
|
295
|
+
this.hint = hint;
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
var SchemaUnsupportedFieldError = class extends Error {
|
|
299
|
+
name = "SchemaUnsupportedFieldError";
|
|
300
|
+
code = "schema-unsupported-field";
|
|
301
|
+
hint;
|
|
302
|
+
constructor(fieldName, fieldType) {
|
|
303
|
+
let hint = "Supported types: f32 / f64 / i32 / u32 / i16 / u16 / i8 / u8 / bool / enum / ref.";
|
|
304
|
+
if (fieldType.startsWith("handle<") && fieldType.endsWith(">")) {
|
|
305
|
+
const tag = fieldType.slice(7, -1);
|
|
306
|
+
hint = `'handle<${tag}>' was removed in feat-20260614 M5; use 'shared<${tag}>' instead. The brand and storage layout are unchanged; only the keyword + write-barrier dispatch (SharedRefStore retain/release) is new.`;
|
|
307
|
+
} else if (fieldType.startsWith("ref<") && fieldType.endsWith(">")) {
|
|
308
|
+
const tag = fieldType.slice(4, -1);
|
|
309
|
+
hint = `'ref<${tag}>' was renamed in feat-20260614 M1; use 'unique<${tag}>' instead. The brand and storage layout are unchanged; the dispatch still routes through UniqueRefStore (single-holder direct release).`;
|
|
310
|
+
}
|
|
311
|
+
super(
|
|
312
|
+
`Schema field "${fieldName}" has unsupported type "${fieldType}".
|
|
313
|
+
field: ${fieldName}
|
|
314
|
+
type: ${fieldType}
|
|
315
|
+
hint: ${hint}`
|
|
316
|
+
);
|
|
317
|
+
this.hint = hint;
|
|
318
|
+
}
|
|
319
|
+
};
|
|
252
320
|
var StaleEntityError = class extends Error {
|
|
253
321
|
name = "StaleEntityError";
|
|
254
322
|
code = "stale-entity";
|
|
@@ -325,6 +393,28 @@ var InstanceTransformsStrideMismatchError = class extends Error {
|
|
|
325
393
|
this.detail = { actualLength, expectedStride: 16 };
|
|
326
394
|
}
|
|
327
395
|
};
|
|
396
|
+
var ManagedArrayElementTypeNotAllowedError = class extends Error {
|
|
397
|
+
name = "ManagedArrayElementTypeNotAllowedError";
|
|
398
|
+
code = "managed-array-element-type-not-allowed";
|
|
399
|
+
hint;
|
|
400
|
+
expected;
|
|
401
|
+
detail;
|
|
402
|
+
constructor(fieldName, elementType) {
|
|
403
|
+
const hint = `array<T> element type must be a scalar (f32/f64/i32/u32/i16/u16/i8/u8/bool/enum/ref) or entity. ref<X> / handle<X> / buffer:N / nested array<...> are forbidden on field "${fieldName}".`;
|
|
404
|
+
const expected = "element type in {scalar | entity}";
|
|
405
|
+
super(
|
|
406
|
+
`managed-array: element type not allowed.
|
|
407
|
+
code: managed-array-element-type-not-allowed
|
|
408
|
+
field: ${fieldName}
|
|
409
|
+
elementType: ${elementType}
|
|
410
|
+
expected: ${expected}
|
|
411
|
+
hint: ${hint}`
|
|
412
|
+
);
|
|
413
|
+
this.hint = hint;
|
|
414
|
+
this.expected = expected;
|
|
415
|
+
this.detail = { fieldName, elementType, hint };
|
|
416
|
+
}
|
|
417
|
+
};
|
|
328
418
|
|
|
329
419
|
// src/component.ts
|
|
330
420
|
var FIELD_SIZE_BYTES = {
|
|
@@ -340,6 +430,59 @@ var FIELD_SIZE_BYTES = {
|
|
|
340
430
|
enum: 4,
|
|
341
431
|
ref: 4
|
|
342
432
|
};
|
|
433
|
+
var MANAGED_ARRAY_ELEMENT_TYPES = /* @__PURE__ */ new Set([
|
|
434
|
+
"f32",
|
|
435
|
+
"f64",
|
|
436
|
+
"i32",
|
|
437
|
+
"u32",
|
|
438
|
+
"i16",
|
|
439
|
+
"u16",
|
|
440
|
+
"i8",
|
|
441
|
+
"u8",
|
|
442
|
+
"bool",
|
|
443
|
+
"enum",
|
|
444
|
+
"ref",
|
|
445
|
+
"entity"
|
|
446
|
+
]);
|
|
447
|
+
function isValidArrayElementType(elementType) {
|
|
448
|
+
if (MANAGED_ARRAY_ELEMENT_TYPES.has(elementType)) return true;
|
|
449
|
+
if (elementType.startsWith("shared<") && elementType.endsWith(">") && elementType.length > 9)
|
|
450
|
+
return true;
|
|
451
|
+
return false;
|
|
452
|
+
}
|
|
453
|
+
function parseManagedArraySchema(fieldType) {
|
|
454
|
+
if (!fieldType.startsWith("array<") || !fieldType.endsWith(">")) return null;
|
|
455
|
+
const inner = fieldType.slice(6, -1);
|
|
456
|
+
const commaIdx = inner.indexOf(",");
|
|
457
|
+
if (commaIdx === -1) {
|
|
458
|
+
if (!isValidArrayElementType(inner)) return null;
|
|
459
|
+
return { elementType: inner, length: void 0 };
|
|
460
|
+
}
|
|
461
|
+
const head = inner.slice(0, commaIdx).trim();
|
|
462
|
+
const tail = inner.slice(commaIdx + 1).trim();
|
|
463
|
+
if (!isValidArrayElementType(head)) return null;
|
|
464
|
+
if (!/^[1-9]\d*$/.test(tail)) return null;
|
|
465
|
+
return { elementType: head, length: Number.parseInt(tail, 10) };
|
|
466
|
+
}
|
|
467
|
+
function isSchemaVocabKeyword(s) {
|
|
468
|
+
if (s === "string") return true;
|
|
469
|
+
if (s === "entity") return true;
|
|
470
|
+
if (s === "buffer") return true;
|
|
471
|
+
if (s.startsWith("buffer<") && s.endsWith(">")) {
|
|
472
|
+
const tail = s.slice(7, -1);
|
|
473
|
+
return /^[1-9]\d*$/.test(tail);
|
|
474
|
+
}
|
|
475
|
+
if (s.startsWith("unique<") && s.endsWith(">")) {
|
|
476
|
+
return /^\w+$/.test(s.slice(7, -1));
|
|
477
|
+
}
|
|
478
|
+
if (s.startsWith("shared<") && s.endsWith(">")) {
|
|
479
|
+
return /^\w+$/.test(s.slice(7, -1));
|
|
480
|
+
}
|
|
481
|
+
if (s.startsWith("array<") && s.endsWith(">")) {
|
|
482
|
+
return parseManagedArraySchema(s) !== null;
|
|
483
|
+
}
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
343
486
|
var COMPONENT_OWNER_REGISTRY = /* @__PURE__ */ Symbol.for("forgeax.ecs.componentOwnerRegistry");
|
|
344
487
|
var ownerSymbols = globalThis;
|
|
345
488
|
var ownerRegistry = ownerSymbols[COMPONENT_OWNER_REGISTRY] ?? (() => {
|
|
@@ -388,7 +531,7 @@ function scalarRow(t) {
|
|
|
388
531
|
isArray: false
|
|
389
532
|
};
|
|
390
533
|
}
|
|
391
|
-
Object.freeze({
|
|
534
|
+
var TYPE_METADATA = Object.freeze({
|
|
392
535
|
f32: scalarRow("f32"),
|
|
393
536
|
f64: scalarRow("f64"),
|
|
394
537
|
i32: scalarRow("i32"),
|
|
@@ -460,7 +603,89 @@ Object.freeze({
|
|
|
460
603
|
isArray: true
|
|
461
604
|
}
|
|
462
605
|
});
|
|
606
|
+
function fieldSpecType(fieldName, spec) {
|
|
607
|
+
if (typeof spec === "string") return spec;
|
|
608
|
+
const t = spec.type;
|
|
609
|
+
if (typeof t !== "string") {
|
|
610
|
+
throw new SchemaUnsupportedFieldError(
|
|
611
|
+
fieldName,
|
|
612
|
+
`<field-descriptor missing 'type'> (expected { type, default?, meta? })`
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
return t;
|
|
616
|
+
}
|
|
617
|
+
function defineComponent(name, fields, options) {
|
|
618
|
+
const storage = "table";
|
|
619
|
+
const schema = {};
|
|
620
|
+
const reflectedFields = {};
|
|
621
|
+
const collectedMeta = {};
|
|
622
|
+
const collectedDefaults = {};
|
|
623
|
+
for (const fieldName of Object.keys(fields)) {
|
|
624
|
+
const spec = fields[fieldName];
|
|
625
|
+
const fieldType = fieldSpecType(fieldName, spec);
|
|
626
|
+
let arrayMeta;
|
|
627
|
+
if (TYPE_METADATA[fieldType]?.isScalar === true) ; else if (fieldType === "string") ; else if (fieldType.startsWith("array<") && fieldType.endsWith(">")) {
|
|
628
|
+
const parsed = parseManagedArraySchema(fieldType);
|
|
629
|
+
if (parsed === null) {
|
|
630
|
+
const elementType = fieldType.slice(6, -1);
|
|
631
|
+
throw new ManagedArrayElementTypeNotAllowedError(fieldName, elementType);
|
|
632
|
+
}
|
|
633
|
+
arrayMeta = deepFreeze(
|
|
634
|
+
parsed.length === void 0 ? { elementType: parsed.elementType } : { elementType: parsed.elementType, length: parsed.length }
|
|
635
|
+
);
|
|
636
|
+
} else if (!isSchemaVocabKeyword(fieldType)) {
|
|
637
|
+
throw new SchemaUnsupportedFieldError(fieldName, fieldType);
|
|
638
|
+
}
|
|
639
|
+
schema[fieldName] = fieldType;
|
|
640
|
+
const row = {
|
|
641
|
+
type: fieldType
|
|
642
|
+
};
|
|
643
|
+
if (typeof spec !== "string") {
|
|
644
|
+
const desc = spec;
|
|
645
|
+
if ("default" in desc) {
|
|
646
|
+
row.default = desc.default;
|
|
647
|
+
collectedDefaults[fieldName] = desc.default;
|
|
648
|
+
}
|
|
649
|
+
if (desc.shape !== void 0) row.shape = desc.shape;
|
|
650
|
+
if (desc.meta !== void 0) {
|
|
651
|
+
Object.assign(collectedMeta, desc.meta);
|
|
652
|
+
}
|
|
653
|
+
if (desc.transient !== void 0) row.transient = desc.transient;
|
|
654
|
+
if (desc.labels !== void 0) row.labels = deepFreeze({ ...desc.labels });
|
|
655
|
+
}
|
|
656
|
+
if (arrayMeta !== void 0) row.arrayMeta = arrayMeta;
|
|
657
|
+
reflectedFields[fieldName] = Object.freeze(row);
|
|
658
|
+
}
|
|
659
|
+
const id = name === "Entity" ? 0 : ownerRegistry.nextId++;
|
|
660
|
+
const frozenDefaults = Object.keys(collectedDefaults).length === 0 ? void 0 : deepFreeze(collectedDefaults);
|
|
661
|
+
const frozenSchema = deepFreeze(schema);
|
|
662
|
+
const frozenFields = deepFreeze(reflectedFields);
|
|
663
|
+
const meta = collectedMeta;
|
|
664
|
+
const token = Object.freeze({ name, fields: frozenFields, storage });
|
|
665
|
+
ownerRegistry.ids.set(token, id);
|
|
666
|
+
ownerRegistry.schemas.set(token, frozenSchema);
|
|
667
|
+
registerComponentDefinition(token, {
|
|
668
|
+
fields: frozenFields,
|
|
669
|
+
defaults: frozenDefaults,
|
|
670
|
+
policy: {
|
|
671
|
+
transient: false,
|
|
672
|
+
meta,
|
|
673
|
+
requires: Object.freeze([...[]])
|
|
674
|
+
}
|
|
675
|
+
});
|
|
676
|
+
return Object.freeze(token);
|
|
677
|
+
}
|
|
678
|
+
var ENTITY_MAX_INDEX = MAX_SLOT;
|
|
463
679
|
var ENTITY_NULL_RAW = 4294967295;
|
|
680
|
+
function encodeEntity(index, generation) {
|
|
681
|
+
if (index < 0 || index > ENTITY_MAX_INDEX) {
|
|
682
|
+
throw new EntityIndexOverflowError(index);
|
|
683
|
+
}
|
|
684
|
+
return pack(index, generation);
|
|
685
|
+
}
|
|
686
|
+
function entityIndex(entity) {
|
|
687
|
+
return unpackSlot(entity);
|
|
688
|
+
}
|
|
464
689
|
|
|
465
690
|
// src/component-default-fallback.ts
|
|
466
691
|
function typeDefault(fieldType) {
|
|
@@ -490,10 +715,210 @@ function fillComponentDefaults(token, raw) {
|
|
|
490
715
|
return out;
|
|
491
716
|
}
|
|
492
717
|
|
|
493
|
-
// src/
|
|
494
|
-
|
|
495
|
-
|
|
718
|
+
// src/entity.ts
|
|
719
|
+
var Entity = defineComponent("Entity", {
|
|
720
|
+
// Layer-2 default is never observed: `world.spawn` always overwrites `self`
|
|
721
|
+
// with the freshly encoded handle for the row. `null` is the type-correct
|
|
722
|
+
// "no handle yet" placeholder (`'entity'` decodes to `EntityHandle | null`).
|
|
723
|
+
self: { type: "entity", default: null }
|
|
724
|
+
});
|
|
725
|
+
defineComponent("Disabled", {});
|
|
726
|
+
Object.freeze([
|
|
727
|
+
componentId(Entity)
|
|
728
|
+
]);
|
|
729
|
+
|
|
730
|
+
// src/storage/change-detection.ts
|
|
731
|
+
var PROJECTION_BLOCK_SIZE = 256;
|
|
732
|
+
|
|
733
|
+
// src/projection/state-projection.ts
|
|
734
|
+
var StateProjectionExpiredError = class extends Error {
|
|
735
|
+
code = "state-projection-expired";
|
|
736
|
+
expected = "an unmodified source and the latest valid projection candidate";
|
|
737
|
+
hint = "Read and apply the current state again before accepting the candidate.";
|
|
738
|
+
constructor() {
|
|
739
|
+
super("State projection candidate expired; read and apply the current state again.");
|
|
740
|
+
this.name = "StateProjectionExpiredError";
|
|
741
|
+
}
|
|
742
|
+
};
|
|
743
|
+
function createStateProjection(world, components, candidates = components) {
|
|
744
|
+
const owner = world[worldInternal];
|
|
745
|
+
const graph = owner.getGraph();
|
|
746
|
+
const ids = components.map(componentId);
|
|
747
|
+
const candidateIds = candidates.map(componentId);
|
|
748
|
+
const sparseCandidates = candidates.some((component) => component.storage === "sparse");
|
|
749
|
+
const baseline = /* @__PURE__ */ new Map();
|
|
750
|
+
let acceptedEpoch = -1;
|
|
751
|
+
let acceptedStructure = -1;
|
|
752
|
+
let invalid = true;
|
|
753
|
+
let readToken = 0;
|
|
754
|
+
let stamp = new Uint32Array(64);
|
|
755
|
+
let serial = 0;
|
|
756
|
+
const work = [];
|
|
757
|
+
function enqueue(index) {
|
|
758
|
+
if (index >= stamp.length) {
|
|
759
|
+
let size = stamp.length;
|
|
760
|
+
while (size <= index) size *= 2;
|
|
761
|
+
const next = new Uint32Array(size);
|
|
762
|
+
next.set(stamp);
|
|
763
|
+
stamp = next;
|
|
764
|
+
}
|
|
765
|
+
if (stamp[index] === serial) return;
|
|
766
|
+
stamp[index] = serial;
|
|
767
|
+
work.push(index);
|
|
768
|
+
}
|
|
769
|
+
return {
|
|
770
|
+
isCurrent() {
|
|
771
|
+
return world.execution.health !== "poisoned" && !invalid && acceptedEpoch === owner.getMutationEpoch() && acceptedStructure === owner.getStructureEpoch();
|
|
772
|
+
},
|
|
773
|
+
entity(index) {
|
|
774
|
+
const record = owner.getRecords()[index];
|
|
775
|
+
if (record === void 0 || record.archetypeId < 0) return void 0;
|
|
776
|
+
return encodeEntity(index, record.generation);
|
|
777
|
+
},
|
|
778
|
+
changed(entity, component) {
|
|
779
|
+
const id = componentId(component);
|
|
780
|
+
if ((owner.getComponentMutationEpochs()[id] ?? 0) <= acceptedEpoch) return false;
|
|
781
|
+
return (owner.getComponentChange(entity, id)?.changed ?? -1) > acceptedEpoch;
|
|
782
|
+
},
|
|
783
|
+
invalidate() {
|
|
784
|
+
readToken++;
|
|
785
|
+
invalid = true;
|
|
786
|
+
},
|
|
787
|
+
read() {
|
|
788
|
+
if (world.execution.health === "poisoned")
|
|
789
|
+
throw new WorldPoisonedError(world.identity, world.execution.fault);
|
|
790
|
+
const token = ++readToken;
|
|
791
|
+
const epoch = owner.getMutationEpoch();
|
|
792
|
+
const structure = owner.getStructureEpoch();
|
|
793
|
+
const changedComponents = components.filter(
|
|
794
|
+
(component) => (owner.getComponentMutationEpochs()[componentId(component)] ?? 0) > acceptedEpoch
|
|
795
|
+
);
|
|
796
|
+
const membershipChanged = invalid || structure !== acceptedStructure;
|
|
797
|
+
const changedRoots = invalid || structure !== acceptedStructure || ids.some((id) => (owner.getComponentMutationEpochs()[id] ?? 0) > acceptedEpoch);
|
|
798
|
+
work.length = 0;
|
|
799
|
+
serial = serial + 1 >>> 0;
|
|
800
|
+
if (serial === 0) {
|
|
801
|
+
stamp.fill(0);
|
|
802
|
+
serial = 1;
|
|
803
|
+
}
|
|
804
|
+
let scannedRows = 0;
|
|
805
|
+
let checkedBlocks = 0;
|
|
806
|
+
const updates = [];
|
|
807
|
+
const visited = /* @__PURE__ */ new Set();
|
|
808
|
+
if (changedRoots) {
|
|
809
|
+
const tables = /* @__PURE__ */ new Set();
|
|
810
|
+
if (sparseCandidates) {
|
|
811
|
+
for (const table of graph.activeTables) tables.add(table);
|
|
812
|
+
} else {
|
|
813
|
+
for (const id of candidateIds)
|
|
814
|
+
for (const table of graph.activeTablesByComponent.get(id) ?? []) tables.add(table);
|
|
815
|
+
}
|
|
816
|
+
for (const table of tables) {
|
|
817
|
+
visited.add(table);
|
|
818
|
+
const prior = baseline.get(table);
|
|
819
|
+
const entities = table.storage.get(componentId(Entity))?.fields.get("self")?.view;
|
|
820
|
+
if (entities === void 0) continue;
|
|
821
|
+
const columns = ids.flatMap((id) => {
|
|
822
|
+
const epochs = table.storage.get(id)?.epochs;
|
|
823
|
+
return epochs === void 0 ? [] : [epochs];
|
|
824
|
+
});
|
|
825
|
+
const blocks = Math.ceil(table.size / PROJECTION_BLOCK_SIZE);
|
|
826
|
+
for (let block = 0; block < blocks; block++) {
|
|
827
|
+
checkedBlocks++;
|
|
828
|
+
const previous = prior?.get(block);
|
|
829
|
+
const membership = table.membership[block] ?? 0;
|
|
830
|
+
const start = block * PROJECTION_BLOCK_SIZE;
|
|
831
|
+
const end = Math.min(table.size, start + PROJECTION_BLOCK_SIZE);
|
|
832
|
+
if (invalid || previous === void 0 || previous.membership !== membership) {
|
|
833
|
+
if (previous !== void 0) {
|
|
834
|
+
for (const index of previous.ids) enqueue(index);
|
|
835
|
+
scannedRows += previous.ids.length;
|
|
836
|
+
}
|
|
837
|
+
const current = new Uint32Array(end - start);
|
|
838
|
+
for (let row = start; row < end; row++) {
|
|
839
|
+
const index = entityIndex(entities[row]);
|
|
840
|
+
current[row - start] = index;
|
|
841
|
+
enqueue(index);
|
|
842
|
+
}
|
|
843
|
+
scannedRows += end - start;
|
|
844
|
+
updates.push({ table, block, value: { ids: current, membership } });
|
|
845
|
+
} else {
|
|
846
|
+
let changedBlock = false;
|
|
847
|
+
for (const column of columns) {
|
|
848
|
+
if ((column.blocks[block] ?? 0) <= acceptedEpoch) continue;
|
|
849
|
+
changedBlock = true;
|
|
850
|
+
for (let row = start; row < end; row++) {
|
|
851
|
+
if ((column.changed[row] ?? 0) > acceptedEpoch)
|
|
852
|
+
enqueue(entityIndex(entities[row]));
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
if (!changedBlock) continue;
|
|
856
|
+
scannedRows += end - start;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
if (prior !== void 0) {
|
|
860
|
+
for (const [block, previous] of prior) {
|
|
861
|
+
if (block < blocks) continue;
|
|
862
|
+
checkedBlocks++;
|
|
863
|
+
for (const index of previous.ids) enqueue(index);
|
|
864
|
+
scannedRows += previous.ids.length;
|
|
865
|
+
updates.push({ table, block, value: void 0 });
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
for (const [table, prior] of baseline) {
|
|
870
|
+
if (visited.has(table)) continue;
|
|
871
|
+
for (const [block, previous] of prior) {
|
|
872
|
+
checkedBlocks++;
|
|
873
|
+
for (const index of previous.ids) enqueue(index);
|
|
874
|
+
scannedRows += previous.ids.length;
|
|
875
|
+
updates.push({ table, block, value: void 0 });
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
let accepted = false;
|
|
880
|
+
const validate = () => {
|
|
881
|
+
if (world.execution.health === "poisoned")
|
|
882
|
+
throw new WorldPoisonedError(world.identity, world.execution.fault);
|
|
883
|
+
if (token !== readToken || epoch !== owner.getMutationEpoch() || structure !== owner.getStructureEpoch()) {
|
|
884
|
+
throw new StateProjectionExpiredError();
|
|
885
|
+
}
|
|
886
|
+
};
|
|
887
|
+
return {
|
|
888
|
+
indices: work,
|
|
889
|
+
epoch,
|
|
890
|
+
scannedRows,
|
|
891
|
+
checkedBlocks,
|
|
892
|
+
membershipChanged,
|
|
893
|
+
changedComponents,
|
|
894
|
+
validate,
|
|
895
|
+
accept() {
|
|
896
|
+
if (accepted) return;
|
|
897
|
+
validate();
|
|
898
|
+
for (const update of updates) {
|
|
899
|
+
let blocks = baseline.get(update.table);
|
|
900
|
+
if (update.value === void 0) {
|
|
901
|
+
blocks?.delete(update.block);
|
|
902
|
+
if (blocks?.size === 0) baseline.delete(update.table);
|
|
903
|
+
} else {
|
|
904
|
+
if (blocks === void 0) {
|
|
905
|
+
blocks = /* @__PURE__ */ new Map();
|
|
906
|
+
baseline.set(update.table, blocks);
|
|
907
|
+
}
|
|
908
|
+
blocks.set(update.block, update.value);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
acceptedEpoch = epoch;
|
|
912
|
+
acceptedStructure = structure;
|
|
913
|
+
invalid = false;
|
|
914
|
+
accepted = true;
|
|
915
|
+
}
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
};
|
|
496
919
|
}
|
|
920
|
+
|
|
921
|
+
// src/projection/index.ts
|
|
497
922
|
function readRenderArrayView(world, entity, component, fieldName) {
|
|
498
923
|
return world[worldInternal].getArrayView(entity, component, fieldName);
|
|
499
924
|
}
|
|
@@ -522,12 +947,10 @@ function readProjectionSpans(world, generation, request) {
|
|
|
522
947
|
}
|
|
523
948
|
return {
|
|
524
949
|
generation,
|
|
525
|
-
sharedRefEpoch: world[worldInternal].getSharedRefs().getMutationEpoch(),
|
|
526
950
|
spans: Object.freeze(spans)
|
|
527
951
|
};
|
|
528
952
|
}
|
|
529
953
|
function createRenderReadLease(world, token = {}) {
|
|
530
|
-
const sharedRefs = world[worldInternal].getSharedRefs();
|
|
531
954
|
let disposed = false;
|
|
532
955
|
const assertLive = () => {
|
|
533
956
|
if (disposed) throw new Error("RenderReadLease is disposed.");
|
|
@@ -535,8 +958,7 @@ function createRenderReadLease(world, token = {}) {
|
|
|
535
958
|
const captureVersion = () => {
|
|
536
959
|
return {
|
|
537
960
|
mutationEpoch: world[worldInternal].getMutationEpoch(),
|
|
538
|
-
structureEpoch: world[worldInternal].getStructureEpoch()
|
|
539
|
-
sharedRefEpoch: sharedRefs.getMutationEpoch()
|
|
961
|
+
structureEpoch: world[worldInternal].getStructureEpoch()
|
|
540
962
|
};
|
|
541
963
|
};
|
|
542
964
|
return {
|
|
@@ -562,20 +984,8 @@ function createRenderReadLease(world, token = {}) {
|
|
|
562
984
|
toEpoch,
|
|
563
985
|
changedComponentIds
|
|
564
986
|
};
|
|
565
|
-
const sharedRead = sharedRefs.readChangesSince(start.sharedRefEpoch);
|
|
566
987
|
const version = captureVersion();
|
|
567
|
-
|
|
568
|
-
if (structureChanged) {
|
|
569
|
-
return {
|
|
570
|
-
status: "rebuild",
|
|
571
|
-
version,
|
|
572
|
-
resync: true,
|
|
573
|
-
reason: "structure-changed",
|
|
574
|
-
world: worldRead,
|
|
575
|
-
sharedRefs: sharedRead
|
|
576
|
-
};
|
|
577
|
-
}
|
|
578
|
-
return { status: "ok", version, world: worldRead, sharedRefs: sharedRead };
|
|
988
|
+
return { version, world: worldRead };
|
|
579
989
|
},
|
|
580
990
|
querySpans(request) {
|
|
581
991
|
assertLive();
|
|
@@ -600,6 +1010,6 @@ function routeWorldError(world, error, context) {
|
|
|
600
1010
|
world[worldInternal].routeError(error, context);
|
|
601
1011
|
}
|
|
602
1012
|
|
|
603
|
-
export { ComponentNotDefinedError, InstanceTransformsStrideMismatchError, ManagedBufferOutOfBoundsError, ResourceInvalidValueError, SpawnLightInvalidBoundsError, SpriteAnimationInvalidError, SpriteInstancesCountMismatchError, SpriteInstancesMutuallyExclusiveWithInstancesError, SpriteInstancesRequiresSpriteShaderError, StaleEntityError, createRenderReadLease, fillComponentDefaults, readRenderArrayView,
|
|
1013
|
+
export { ComponentNotDefinedError, InstanceTransformsStrideMismatchError, ManagedBufferOutOfBoundsError, ResourceInvalidValueError, SpawnLightInvalidBoundsError, SpriteAnimationInvalidError, SpriteInstancesCountMismatchError, SpriteInstancesMutuallyExclusiveWithInstancesError, SpriteInstancesRequiresSpriteShaderError, StaleEntityError, StateProjectionExpiredError, createRenderReadLease, createStateProjection, fillComponentDefaults, readRenderArrayView, routeWorldError, setDerivedComponent };
|
|
604
1014
|
//# sourceMappingURL=index.mjs.map
|
|
605
1015
|
//# sourceMappingURL=index.mjs.map
|