@forgeax/engine-ecs 0.1.21 → 0.1.23
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 +50 -16
- package/dist/__tests__/changed-block-summary.candidate.test.d.ts +2 -0
- package/dist/__tests__/changed-block-summary.candidate.test.d.ts.map +1 -0
- package/dist/__tests__/component-requirements.unit.test.d.ts +2 -0
- package/dist/__tests__/component-requirements.unit.test.d.ts.map +1 -0
- package/dist/__tests__/component-version-surface.test.d.ts +2 -0
- package/dist/__tests__/component-version-surface.test.d.ts.map +1 -0
- package/dist/__tests__/derived-range-writer.contract.test-d.d.ts +2 -0
- package/dist/__tests__/derived-range-writer.contract.test-d.d.ts.map +1 -0
- package/dist/__tests__/derived-range-writer.contract.test.d.ts +2 -0
- package/dist/__tests__/derived-range-writer.contract.test.d.ts.map +1 -0
- package/dist/__tests__/structural-evidence.contract.test-d.d.ts +2 -0
- package/dist/__tests__/structural-evidence.contract.test-d.d.ts.map +1 -0
- package/dist/__tests__/structural-evidence.contract.test.d.ts +2 -0
- package/dist/__tests__/structural-evidence.contract.test.d.ts.map +1 -0
- package/dist/component-schema.d.ts +17 -0
- package/dist/component-schema.d.ts.map +1 -1
- package/dist/component.d.ts +7 -1
- package/dist/component.d.ts.map +1 -1
- package/dist/errors/query-and-component-errors.d.ts +1 -1
- package/dist/errors/query-and-component-errors.d.ts.map +1 -1
- package/dist/errors/validation-errors.d.ts +24 -0
- package/dist/errors/validation-errors.d.ts.map +1 -1
- package/dist/errors.d.ts +14 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/externalization/index.mjs.map +1 -1
- package/dist/index.mjs +415 -128
- package/dist/index.mjs.map +1 -1
- package/dist/internal.d.ts +16 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.mjs +15 -2
- package/dist/internal.mjs.map +1 -1
- package/dist/projection/index.d.ts +27 -52
- package/dist/projection/index.d.ts.map +1 -1
- package/dist/projection/index.mjs +66 -76
- package/dist/projection/index.mjs.map +1 -1
- package/dist/query/derived-range-writer.d.ts +48 -0
- package/dist/query/derived-range-writer.d.ts.map +1 -0
- package/dist/query/query.d.ts +1 -1
- package/dist/query/query.d.ts.map +1 -1
- package/dist/relationship-index.d.ts +8 -0
- package/dist/relationship-index.d.ts.map +1 -1
- package/dist/shared-ref-store.d.ts +5 -9
- package/dist/shared-ref-store.d.ts.map +1 -1
- package/dist/shared.mjs.map +1 -1
- package/dist/storage/change-detection.d.ts +1 -3
- package/dist/storage/change-detection.d.ts.map +1 -1
- package/dist/storage/structural-evidence.d.ts +30 -0
- package/dist/storage/structural-evidence.d.ts.map +1 -0
- package/dist/world-component-access.d.ts +4 -3
- package/dist/world-component-access.d.ts.map +1 -1
- package/dist/world-core.d.ts +2 -2
- package/dist/world-core.d.ts.map +1 -1
- package/dist/world-entity-lifecycle.d.ts.map +1 -1
- package/dist/world-internal.d.ts +1 -1
- package/dist/world-internal.d.ts.map +1 -1
- package/dist/world.d.ts +10 -14
- package/dist/world.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/__tests__/changed-block-summary.candidate.test.ts +57 -0
- package/src/__tests__/component-requirements.unit.test.ts +76 -0
- package/src/__tests__/component-version-surface.test.ts +45 -0
- package/src/__tests__/derived-range-writer.contract.test-d.ts +35 -0
- package/src/__tests__/derived-range-writer.contract.test.ts +339 -0
- package/src/__tests__/errors.unit.test.ts +21 -5
- package/src/__tests__/externalization-render-read-lease.test-d.ts +1 -1
- package/src/__tests__/externalization-render-read-lease.unit.test.ts +15 -15
- package/src/__tests__/query-storage-trends.perf.test.ts +19 -0
- package/src/__tests__/query.unit.test.ts +10 -6
- package/src/__tests__/shared-ref-store.unit.test.ts +3 -8
- package/src/__tests__/structural-evidence.contract.test-d.ts +6 -0
- package/src/__tests__/structural-evidence.contract.test.ts +49 -0
- package/src/component-schema.ts +51 -0
- package/src/component.ts +12 -2
- package/src/errors/query-and-component-errors.ts +1 -1
- package/src/errors/validation-errors.ts +30 -0
- package/src/errors.ts +29 -5
- package/src/internal.ts +56 -0
- package/src/projection/index.ts +68 -149
- package/src/query/derived-range-writer.ts +332 -0
- package/src/query/query.ts +89 -15
- package/src/relationship-index.ts +13 -1
- package/src/shared-ref-store.ts +16 -24
- package/src/storage/change-detection.ts +0 -15
- package/src/storage/structural-evidence.ts +64 -0
- package/src/world-component-access.ts +53 -5
- package/src/world-core.ts +2 -2
- package/src/world-entity-lifecycle.ts +10 -1
- package/src/world-internal.ts +5 -5
- package/src/world.ts +45 -50
- package/dist/.tsbuildinfo +0 -1
- package/dist/__tests__/world-change-journal.unit.test.d.ts +0 -2
- package/dist/__tests__/world-change-journal.unit.test.d.ts.map +0 -1
- package/dist/__tests__/world-internal-identity.unit.test.d.ts +0 -2
- package/dist/__tests__/world-internal-identity.unit.test.d.ts.map +0 -1
- package/dist/world-change-journal.d.ts +0 -34
- package/dist/world-change-journal.d.ts.map +0 -1
- package/src/__tests__/world-change-journal.unit.test.ts +0 -97
- package/src/__tests__/world-internal-identity.unit.test.ts +0 -17
- package/src/world-change-journal.ts +0 -84
package/dist/index.mjs
CHANGED
|
@@ -21,6 +21,31 @@ function componentDefinition(component) {
|
|
|
21
21
|
}
|
|
22
22
|
return definition;
|
|
23
23
|
}
|
|
24
|
+
function componentRequirements(component) {
|
|
25
|
+
return definitions.definitions.get(component)?.policy.requires ?? [];
|
|
26
|
+
}
|
|
27
|
+
function expandComponentRequirements(componentDatas) {
|
|
28
|
+
let hasRequirements = false;
|
|
29
|
+
for (const entry of componentDatas) {
|
|
30
|
+
if (componentRequirements(entry.component).length !== 0) {
|
|
31
|
+
hasRequirements = true;
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (!hasRequirements) return componentDatas;
|
|
36
|
+
const expanded = [...componentDatas];
|
|
37
|
+
const seen = new Set(expanded.map((entry) => entry.component));
|
|
38
|
+
for (let index = 0; index < expanded.length; index++) {
|
|
39
|
+
const component = expanded[index]?.component;
|
|
40
|
+
if (component === void 0) continue;
|
|
41
|
+
for (const required of componentRequirements(component)) {
|
|
42
|
+
if (seen.has(required)) continue;
|
|
43
|
+
seen.add(required);
|
|
44
|
+
expanded.push({ component: required, data: {} });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return expanded;
|
|
48
|
+
}
|
|
24
49
|
function deepFreeze(value) {
|
|
25
50
|
if (value === null || typeof value !== "object" && typeof value !== "function") {
|
|
26
51
|
return value;
|
|
@@ -654,6 +679,17 @@ var ChangeEpochExhaustedError = class extends Error {
|
|
|
654
679
|
this.detail = { epoch };
|
|
655
680
|
}
|
|
656
681
|
};
|
|
682
|
+
var DerivedRangeOutOfBoundsError = class extends Error {
|
|
683
|
+
name = "DerivedRangeOutOfBoundsError";
|
|
684
|
+
code = "derived-range-out-of-bounds";
|
|
685
|
+
expected = "a non-negative span-relative range with start + count <= span.length";
|
|
686
|
+
hint = "check start and count against the QuerySpan length, then retry without changing World state";
|
|
687
|
+
detail;
|
|
688
|
+
constructor(start, count, spanLength) {
|
|
689
|
+
super(`Derived range [${start}, ${start + count}) exceeds QuerySpan length ${spanLength}.`);
|
|
690
|
+
this.detail = { start, count, spanLength };
|
|
691
|
+
}
|
|
692
|
+
};
|
|
657
693
|
var ProtectedResourceError = class extends Error {
|
|
658
694
|
name = "ProtectedResourceError";
|
|
659
695
|
code = "resource-protected";
|
|
@@ -1254,7 +1290,11 @@ function defineComponent(name, fields, options) {
|
|
|
1254
1290
|
registerComponentDefinition(token, {
|
|
1255
1291
|
fields: frozenFields,
|
|
1256
1292
|
defaults: frozenDefaults,
|
|
1257
|
-
policy: {
|
|
1293
|
+
policy: {
|
|
1294
|
+
transient: options?.transient ?? false,
|
|
1295
|
+
meta,
|
|
1296
|
+
requires: Object.freeze([...options?.requires ?? []])
|
|
1297
|
+
}
|
|
1258
1298
|
});
|
|
1259
1299
|
return Object.freeze(token);
|
|
1260
1300
|
}
|
|
@@ -1385,7 +1425,11 @@ function defineRelationship(options) {
|
|
|
1385
1425
|
};
|
|
1386
1426
|
const target = defineComponent(options.targetName, targetFields);
|
|
1387
1427
|
const sourceFields = { [options.sourceField]: { type: "entity" } };
|
|
1388
|
-
const source = defineComponent(
|
|
1428
|
+
const source = defineComponent(
|
|
1429
|
+
options.sourceName,
|
|
1430
|
+
sourceFields,
|
|
1431
|
+
options.sourceRequires === void 0 ? void 0 : { requires: options.sourceRequires }
|
|
1432
|
+
);
|
|
1389
1433
|
roles.set(source, {
|
|
1390
1434
|
kind: "source",
|
|
1391
1435
|
target,
|
|
@@ -1563,51 +1607,10 @@ function entityGeneration(entity) {
|
|
|
1563
1607
|
return unpackGen(entity);
|
|
1564
1608
|
}
|
|
1565
1609
|
|
|
1566
|
-
// src/
|
|
1567
|
-
var
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
this.capacity = capacity;
|
|
1571
|
-
if (!Number.isSafeInteger(capacity) || capacity <= 0) {
|
|
1572
|
-
throw new RangeError("WorldChangeJournal capacity must be a positive safe integer");
|
|
1573
|
-
}
|
|
1574
|
-
this.records = new Array(capacity);
|
|
1575
|
-
}
|
|
1576
|
-
capacity;
|
|
1577
|
-
records;
|
|
1578
|
-
nextSequence = 1;
|
|
1579
|
-
cursor() {
|
|
1580
|
-
return this.nextSequence - 1;
|
|
1581
|
-
}
|
|
1582
|
-
append(change) {
|
|
1583
|
-
const sequence = this.nextSequence;
|
|
1584
|
-
this.nextSequence += 1;
|
|
1585
|
-
this.records[(sequence - 1) % this.capacity] = { ...change, sequence };
|
|
1586
|
-
return sequence;
|
|
1587
|
-
}
|
|
1588
|
-
readAfter(cursor) {
|
|
1589
|
-
const latest = this.cursor();
|
|
1590
|
-
if (!Number.isSafeInteger(cursor) || cursor < 0 || cursor > latest) {
|
|
1591
|
-
throw new RangeError(`WorldChangeJournal cursor ${cursor} is outside 0..${latest}`);
|
|
1592
|
-
}
|
|
1593
|
-
const oldestAvailable = Math.max(1, latest - this.capacity + 1);
|
|
1594
|
-
if (cursor < oldestAvailable - 1) {
|
|
1595
|
-
return { status: "overflow", cursor: latest, oldestAvailable };
|
|
1596
|
-
}
|
|
1597
|
-
if (cursor === latest) {
|
|
1598
|
-
return { status: "ok", cursor: latest, records: EMPTY_CHANGES };
|
|
1599
|
-
}
|
|
1600
|
-
const records = [];
|
|
1601
|
-
for (let sequence = cursor + 1; sequence <= latest; sequence += 1) {
|
|
1602
|
-
const record = this.records[(sequence - 1) % this.capacity];
|
|
1603
|
-
if (record === void 0 || record.sequence !== sequence) {
|
|
1604
|
-
return { status: "overflow", cursor: latest, oldestAvailable };
|
|
1605
|
-
}
|
|
1606
|
-
records.push(record);
|
|
1607
|
-
}
|
|
1608
|
-
return { status: "ok", cursor: latest, records };
|
|
1609
|
-
}
|
|
1610
|
-
};
|
|
1610
|
+
// src/internal.ts
|
|
1611
|
+
var DERIVED_WRITER = /* @__PURE__ */ Symbol.for(
|
|
1612
|
+
"forgeax.ecs.query.derivedWriter"
|
|
1613
|
+
);
|
|
1611
1614
|
|
|
1612
1615
|
// src/storage/archetype.ts
|
|
1613
1616
|
var INITIAL_CAPACITY = 64;
|
|
@@ -2039,10 +2042,162 @@ function markComponentChanged(graph, location, entity, componentId2, epoch) {
|
|
|
2039
2042
|
const tableRow2 = archetype.rows[location.archetypeRow] ?? -1;
|
|
2040
2043
|
epochs.changed[tableRow2] = epoch();
|
|
2041
2044
|
}
|
|
2042
|
-
function
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2045
|
+
function createDerivedRangeWriter(world, component, source) {
|
|
2046
|
+
let boundEpoch = -1;
|
|
2047
|
+
let bindingTables = [];
|
|
2048
|
+
let bindings = [];
|
|
2049
|
+
let runStartBuffers = [];
|
|
2050
|
+
let runCountBuffers = [];
|
|
2051
|
+
const rebind = () => {
|
|
2052
|
+
const tables = source.tables();
|
|
2053
|
+
const nextBindings = [];
|
|
2054
|
+
const nextRunStartBuffers = [];
|
|
2055
|
+
const nextRunCountBuffers = [];
|
|
2056
|
+
for (const table of tables) {
|
|
2057
|
+
nextBindings.push({
|
|
2058
|
+
read: buildWholeColumnShape(table, source.readComponents),
|
|
2059
|
+
write: buildWholeColumnShape(table, source.writeComponents),
|
|
2060
|
+
rowCapacity: table.size
|
|
2061
|
+
});
|
|
2062
|
+
const runCapacity = Math.max(1, table.size);
|
|
2063
|
+
nextRunStartBuffers.push(new Int32Array(runCapacity));
|
|
2064
|
+
nextRunCountBuffers.push(new Int32Array(runCapacity));
|
|
2065
|
+
}
|
|
2066
|
+
bindingTables = tables;
|
|
2067
|
+
bindings = nextBindings;
|
|
2068
|
+
runStartBuffers = nextRunStartBuffers;
|
|
2069
|
+
runCountBuffers = nextRunCountBuffers;
|
|
2070
|
+
boundEpoch = source.structureEpoch();
|
|
2071
|
+
};
|
|
2072
|
+
rebind();
|
|
2073
|
+
const writer = {
|
|
2074
|
+
get bindings() {
|
|
2075
|
+
if (boundEpoch !== source.structureEpoch()) rebind();
|
|
2076
|
+
return bindings;
|
|
2077
|
+
},
|
|
2078
|
+
writeRange(bindingIndex, base, start, count, kernel, context) {
|
|
2079
|
+
if (world.execution.health === "poisoned") {
|
|
2080
|
+
return err(new WorldPoisonedError(world.identity, world.execution.fault));
|
|
2081
|
+
}
|
|
2082
|
+
if (boundEpoch !== source.structureEpoch()) rebind();
|
|
2083
|
+
const binding = bindings[bindingIndex];
|
|
2084
|
+
const table = bindingTables[bindingIndex];
|
|
2085
|
+
if (binding === void 0 || table === void 0 || !Number.isSafeInteger(bindingIndex) || !Number.isSafeInteger(base) || !Number.isSafeInteger(start) || !Number.isSafeInteger(count) || bindingIndex < 0 || base < 0 || start < 0 || count < 0 || base + start + count > binding.rowCapacity || table.storage.get(componentId(component)) === void 0) {
|
|
2086
|
+
return err(new DerivedRangeOutOfBoundsError(start, count, binding?.rowCapacity ?? 0));
|
|
2087
|
+
}
|
|
2088
|
+
if (count === 0) return ok(void 0);
|
|
2089
|
+
const previousEpoch = world[worldInternal].getMutationEpoch();
|
|
2090
|
+
let epoch;
|
|
2091
|
+
try {
|
|
2092
|
+
epoch = world[worldInternal].nextMutationEpoch();
|
|
2093
|
+
} catch (cause) {
|
|
2094
|
+
return err(cause);
|
|
2095
|
+
}
|
|
2096
|
+
try {
|
|
2097
|
+
kernel(binding, base, start, count, context);
|
|
2098
|
+
world[worldInternal].publishDerivedRange(
|
|
2099
|
+
table,
|
|
2100
|
+
componentId(component),
|
|
2101
|
+
base + start,
|
|
2102
|
+
count,
|
|
2103
|
+
epoch
|
|
2104
|
+
);
|
|
2105
|
+
return ok(void 0);
|
|
2106
|
+
} catch (cause) {
|
|
2107
|
+
world[worldInternal].restoreMutationEpoch(previousEpoch);
|
|
2108
|
+
world[worldInternal].poisonExecution({
|
|
2109
|
+
code: "shared-kernel-failed",
|
|
2110
|
+
kernelName: `derived-range:${component.name}`,
|
|
2111
|
+
cause,
|
|
2112
|
+
partialWrite: true,
|
|
2113
|
+
retryable: false
|
|
2114
|
+
});
|
|
2115
|
+
return err(new SharedKernelFailureError(component.name, world.identity, cause, true));
|
|
2116
|
+
}
|
|
2117
|
+
},
|
|
2118
|
+
probeAndCommitRange(bindingIndex, base, start, count, probe, commit, context) {
|
|
2119
|
+
if (world.execution.health === "poisoned") {
|
|
2120
|
+
return err(new WorldPoisonedError(world.identity, world.execution.fault));
|
|
2121
|
+
}
|
|
2122
|
+
if (boundEpoch !== source.structureEpoch()) rebind();
|
|
2123
|
+
const binding = bindings[bindingIndex];
|
|
2124
|
+
const table = bindingTables[bindingIndex];
|
|
2125
|
+
const runStarts = runStartBuffers[bindingIndex];
|
|
2126
|
+
const runCounts = runCountBuffers[bindingIndex];
|
|
2127
|
+
if (binding === void 0 || table === void 0 || runStarts === void 0 || runCounts === void 0 || !Number.isSafeInteger(bindingIndex) || !Number.isSafeInteger(base) || !Number.isSafeInteger(start) || !Number.isSafeInteger(count) || bindingIndex < 0 || base < 0 || start < 0 || count < 0 || base + start + count > binding.rowCapacity || table.storage.get(componentId(component)) === void 0) {
|
|
2128
|
+
return err(new DerivedRangeOutOfBoundsError(start, count, binding?.rowCapacity ?? 0));
|
|
2129
|
+
}
|
|
2130
|
+
if (count === 0) return ok(void 0);
|
|
2131
|
+
const previousEpoch = world[worldInternal].getMutationEpoch();
|
|
2132
|
+
let runCount = 0;
|
|
2133
|
+
let epoch = 0;
|
|
2134
|
+
let epochReserved = false;
|
|
2135
|
+
let runStart = -1;
|
|
2136
|
+
try {
|
|
2137
|
+
for (let offset = 0; offset < count; offset += 1) {
|
|
2138
|
+
const row = base + start + offset;
|
|
2139
|
+
const changed = probe(binding, row, context);
|
|
2140
|
+
if (changed) {
|
|
2141
|
+
if (!epochReserved) {
|
|
2142
|
+
try {
|
|
2143
|
+
epoch = world[worldInternal].nextMutationEpoch();
|
|
2144
|
+
epochReserved = true;
|
|
2145
|
+
} catch (cause) {
|
|
2146
|
+
return err(cause);
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
if (runStart < 0) runStart = row;
|
|
2150
|
+
commit(binding, row, context);
|
|
2151
|
+
} else if (runStart >= 0) {
|
|
2152
|
+
runStarts[runCount] = runStart;
|
|
2153
|
+
runCounts[runCount] = row - runStart;
|
|
2154
|
+
runCount += 1;
|
|
2155
|
+
runStart = -1;
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
if (runStart >= 0) {
|
|
2159
|
+
runStarts[runCount] = runStart;
|
|
2160
|
+
runCounts[runCount] = base + start + count - runStart;
|
|
2161
|
+
runCount += 1;
|
|
2162
|
+
}
|
|
2163
|
+
if (runCount > 0) {
|
|
2164
|
+
const componentIdentifier = componentId(component);
|
|
2165
|
+
for (let index = 0; index < runCount; index += 1) {
|
|
2166
|
+
const runStartValue = runStarts[index] ?? 0;
|
|
2167
|
+
const runLength = runCounts[index] ?? 0;
|
|
2168
|
+
world[worldInternal].publishDerivedRange(
|
|
2169
|
+
table,
|
|
2170
|
+
componentIdentifier,
|
|
2171
|
+
runStartValue,
|
|
2172
|
+
runLength,
|
|
2173
|
+
epoch
|
|
2174
|
+
);
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
return ok(void 0);
|
|
2178
|
+
} catch (cause) {
|
|
2179
|
+
if (epochReserved) world[worldInternal].restoreMutationEpoch(previousEpoch);
|
|
2180
|
+
world[worldInternal].poisonExecution({
|
|
2181
|
+
code: "shared-kernel-failed",
|
|
2182
|
+
kernelName: `derived-range:${component.name}:probe`,
|
|
2183
|
+
cause,
|
|
2184
|
+
partialWrite: true,
|
|
2185
|
+
retryable: false
|
|
2186
|
+
});
|
|
2187
|
+
return err(new SharedKernelFailureError(component.name, world.identity, cause, true));
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
};
|
|
2191
|
+
return writer;
|
|
2192
|
+
}
|
|
2193
|
+
function buildWholeColumnShape(table, components) {
|
|
2194
|
+
const shape = {};
|
|
2195
|
+
for (const component of components) {
|
|
2196
|
+
const fields = table.storage.get(componentId(component))?.fields;
|
|
2197
|
+
if (fields === void 0) continue;
|
|
2198
|
+
for (const [fieldName, column] of fields) shape[fieldName] = column.view;
|
|
2199
|
+
}
|
|
2200
|
+
return shape;
|
|
2046
2201
|
}
|
|
2047
2202
|
|
|
2048
2203
|
// src/query/query.ts
|
|
@@ -2154,7 +2309,7 @@ var QuerySpanFacade = class {
|
|
|
2154
2309
|
this.rowStart = rowStart;
|
|
2155
2310
|
this.length = length;
|
|
2156
2311
|
const entityColumn = table.storage.get(componentId(Entity))?.fields.get("self");
|
|
2157
|
-
this.entities = entityColumn
|
|
2312
|
+
this.entities = entityColumn === void 0 ? new Uint32Array(0) : rowStart === 0 && length === entityColumn.view.length ? entityColumn.view : entityColumn.view.subarray(rowStart, rowStart + length);
|
|
2158
2313
|
}
|
|
2159
2314
|
world;
|
|
2160
2315
|
table;
|
|
@@ -2185,7 +2340,7 @@ var QuerySpanFacade = class {
|
|
|
2185
2340
|
}
|
|
2186
2341
|
};
|
|
2187
2342
|
function makeManagedColumnReader(view, length, fieldType) {
|
|
2188
|
-
const slots = view.subarray(0, length);
|
|
2343
|
+
const slots = view.length === length ? view : view.subarray(0, length);
|
|
2189
2344
|
return Object.freeze({
|
|
2190
2345
|
length,
|
|
2191
2346
|
get(i) {
|
|
@@ -2201,7 +2356,7 @@ function buildColumnShape(table, component, rowStart, rowCount) {
|
|
|
2201
2356
|
for (const [fieldName, column] of fields) {
|
|
2202
2357
|
const start = rowStart * column.arity;
|
|
2203
2358
|
const end = start + rowCount * column.arity;
|
|
2204
|
-
const view = column.view.subarray(start, end);
|
|
2359
|
+
const view = start === 0 && end === column.view.length ? column.view : column.view.subarray(start, end);
|
|
2205
2360
|
const fieldType = componentSchema(component)[fieldName];
|
|
2206
2361
|
shape[fieldName] = fieldType !== void 0 && isManagedField(fieldType) ? makeManagedColumnReader(view, view.length, fieldType) : view;
|
|
2207
2362
|
}
|
|
@@ -2216,6 +2371,7 @@ var ExecutableQuery = class {
|
|
|
2216
2371
|
compiled;
|
|
2217
2372
|
matchedArchetypes = [];
|
|
2218
2373
|
matchedTables = [];
|
|
2374
|
+
matchedTableObjects = [];
|
|
2219
2375
|
lastGraphGeneration = -1;
|
|
2220
2376
|
lastObservedEpoch = 0;
|
|
2221
2377
|
active = false;
|
|
@@ -2306,7 +2462,8 @@ var ExecutableQuery = class {
|
|
|
2306
2462
|
query.refreshMatches();
|
|
2307
2463
|
const structureEpoch = query.world[worldInternal].getStructureEpoch();
|
|
2308
2464
|
const upperBound = query.world[worldInternal].getMutationEpoch();
|
|
2309
|
-
let
|
|
2465
|
+
let tableIndex = 0;
|
|
2466
|
+
let rowIndex = 0;
|
|
2310
2467
|
let finished = false;
|
|
2311
2468
|
const close = (commit) => {
|
|
2312
2469
|
if (finished) return;
|
|
@@ -2324,12 +2481,37 @@ var ExecutableQuery = class {
|
|
|
2324
2481
|
query.world[worldInternal].getStructureEpoch()
|
|
2325
2482
|
);
|
|
2326
2483
|
}
|
|
2327
|
-
while (
|
|
2328
|
-
const table = query.world[worldInternal].getGraph().tables[query.matchedTables[
|
|
2329
|
-
if (table === void 0 || table.size === 0)
|
|
2484
|
+
while (tableIndex < query.matchedTables.length) {
|
|
2485
|
+
const table = query.world[worldInternal].getGraph().tables[query.matchedTables[tableIndex] ?? -1];
|
|
2486
|
+
if (table === void 0 || table.size === 0) {
|
|
2487
|
+
tableIndex += 1;
|
|
2488
|
+
rowIndex = 0;
|
|
2489
|
+
continue;
|
|
2490
|
+
}
|
|
2491
|
+
if (query.compiled.changedIds.length === 0 && query.compiled.addedIds.length === 0) {
|
|
2492
|
+
tableIndex += 1;
|
|
2493
|
+
rowIndex = 0;
|
|
2494
|
+
return {
|
|
2495
|
+
done: false,
|
|
2496
|
+
value: new QuerySpanFacade(query.world, table, 0, table.size)
|
|
2497
|
+
};
|
|
2498
|
+
}
|
|
2499
|
+
while (rowIndex < table.size && !query.denseChangeMatches(table, rowIndex, upperBound)) {
|
|
2500
|
+
rowIndex += 1;
|
|
2501
|
+
}
|
|
2502
|
+
if (rowIndex >= table.size) {
|
|
2503
|
+
tableIndex += 1;
|
|
2504
|
+
rowIndex = 0;
|
|
2505
|
+
continue;
|
|
2506
|
+
}
|
|
2507
|
+
const start = rowIndex;
|
|
2508
|
+
rowIndex += 1;
|
|
2509
|
+
while (rowIndex < table.size && query.denseChangeMatches(table, rowIndex, upperBound)) {
|
|
2510
|
+
rowIndex += 1;
|
|
2511
|
+
}
|
|
2330
2512
|
return {
|
|
2331
2513
|
done: false,
|
|
2332
|
-
value: new QuerySpanFacade(query.world, table,
|
|
2514
|
+
value: new QuerySpanFacade(query.world, table, start, rowIndex - start)
|
|
2333
2515
|
};
|
|
2334
2516
|
}
|
|
2335
2517
|
close(true);
|
|
@@ -2343,6 +2525,25 @@ var ExecutableQuery = class {
|
|
|
2343
2525
|
}
|
|
2344
2526
|
});
|
|
2345
2527
|
}
|
|
2528
|
+
[DERIVED_WRITER](component) {
|
|
2529
|
+
const reason = this.spanUnavailableReason();
|
|
2530
|
+
if (reason !== void 0) return err(new QuerySpanUnavailableError(reason));
|
|
2531
|
+
return ok(
|
|
2532
|
+
createDerivedRangeWriter(
|
|
2533
|
+
this.world,
|
|
2534
|
+
component,
|
|
2535
|
+
{
|
|
2536
|
+
structureEpoch: () => this.world[worldInternal].getStructureEpoch(),
|
|
2537
|
+
tables: () => {
|
|
2538
|
+
this.refreshMatches();
|
|
2539
|
+
return this.matchedTableObjects;
|
|
2540
|
+
},
|
|
2541
|
+
readComponents: this.compiled.descriptor.read ?? [],
|
|
2542
|
+
writeComponents: this.compiled.descriptor.write ?? []
|
|
2543
|
+
}
|
|
2544
|
+
)
|
|
2545
|
+
);
|
|
2546
|
+
}
|
|
2346
2547
|
combinations(k = 2) {
|
|
2347
2548
|
const query = this;
|
|
2348
2549
|
return {
|
|
@@ -2393,6 +2594,10 @@ var ExecutableQuery = class {
|
|
|
2393
2594
|
if (this.lastGraphGeneration === graph.generation) return;
|
|
2394
2595
|
this.matchedArchetypes = graph.archetypes.filter((archetype) => this.archetypeMatches(archetype)).map((archetype) => archetype.id);
|
|
2395
2596
|
this.matchedTables = graph.tables.filter((table) => this.tableMatches(table)).map((table) => table.id);
|
|
2597
|
+
this.matchedTableObjects = this.matchedTables.flatMap((id) => {
|
|
2598
|
+
const table = graph.tables[id];
|
|
2599
|
+
return table === void 0 ? [] : [table];
|
|
2600
|
+
});
|
|
2396
2601
|
this.lastGraphGeneration = graph.generation;
|
|
2397
2602
|
}
|
|
2398
2603
|
archetypeMatches(archetype) {
|
|
@@ -2431,11 +2636,20 @@ var ExecutableQuery = class {
|
|
|
2431
2636
|
}
|
|
2432
2637
|
return true;
|
|
2433
2638
|
}
|
|
2639
|
+
denseChangeMatches(table, row, upperBound) {
|
|
2640
|
+
for (const componentId2 of this.compiled.changedIds) {
|
|
2641
|
+
const epoch = table.storage.get(componentId2)?.epochs.changed[row] ?? 0;
|
|
2642
|
+
if (epoch <= this.lastObservedEpoch || epoch > upperBound) return false;
|
|
2643
|
+
}
|
|
2644
|
+
for (const componentId2 of this.compiled.addedIds) {
|
|
2645
|
+
const epoch = table.storage.get(componentId2)?.epochs.added[row] ?? 0;
|
|
2646
|
+
if (epoch <= this.lastObservedEpoch || epoch > upperBound) return false;
|
|
2647
|
+
}
|
|
2648
|
+
return true;
|
|
2649
|
+
}
|
|
2434
2650
|
spanUnavailableReason() {
|
|
2435
2651
|
if (this.compiled.sparseRoute) return "sparse-component";
|
|
2436
2652
|
if ((this.compiled.descriptor.optional?.length ?? 0) > 0) return "optional-data";
|
|
2437
|
-
if ((this.compiled.descriptor.changed?.length ?? 0) > 0 || (this.compiled.descriptor.added?.length ?? 0) > 0)
|
|
2438
|
-
return "row-change-filter";
|
|
2439
2653
|
return void 0;
|
|
2440
2654
|
}
|
|
2441
2655
|
};
|
|
@@ -3394,8 +3608,8 @@ var ComponentStorage = class {
|
|
|
3394
3608
|
get sharedRefs() {
|
|
3395
3609
|
return this.state.sharedRefs;
|
|
3396
3610
|
}
|
|
3397
|
-
routeError(
|
|
3398
|
-
this.state.routeError(
|
|
3611
|
+
routeError(err12, ctx) {
|
|
3612
|
+
this.state.routeError(err12, ctx);
|
|
3399
3613
|
}
|
|
3400
3614
|
readArrayView(arch, component, row, fieldName) {
|
|
3401
3615
|
const fieldCols = this.table(arch).storage.get(componentId(component))?.fields;
|
|
@@ -3762,11 +3976,11 @@ var ComponentStorage = class {
|
|
|
3762
3976
|
* the EcsError contract; this helper only attaches the systemName context
|
|
3763
3977
|
* so AI users can correlate the error with the holder component / field.
|
|
3764
3978
|
*/
|
|
3765
|
-
routeArrayError(
|
|
3979
|
+
routeArrayError(err12, componentName, fieldName) {
|
|
3766
3980
|
const ctx = {
|
|
3767
3981
|
systemName: `World.write (${componentName}.${fieldName})`
|
|
3768
3982
|
};
|
|
3769
|
-
this.routeError(
|
|
3983
|
+
this.routeError(err12, ctx);
|
|
3770
3984
|
}
|
|
3771
3985
|
/**
|
|
3772
3986
|
* Write the value of an `array<T>` / `array<T,N>` field at `row` (D-3 +
|
|
@@ -4211,8 +4425,8 @@ var WorldComponentAccess = class {
|
|
|
4211
4425
|
get uniqueRefs() {
|
|
4212
4426
|
return this.state.uniqueRefs;
|
|
4213
4427
|
}
|
|
4214
|
-
routeError(
|
|
4215
|
-
this.state.routeError(
|
|
4428
|
+
routeError(err12, ctx) {
|
|
4429
|
+
this.state.routeError(err12, ctx);
|
|
4216
4430
|
}
|
|
4217
4431
|
relationshipIndex(component) {
|
|
4218
4432
|
if (relationshipRole(component)?.kind !== "source") return void 0;
|
|
@@ -4557,7 +4771,7 @@ var WorldComponentAccess = class {
|
|
|
4557
4771
|
* field does not exist, or the field is not an `array<...>` column.
|
|
4558
4772
|
*
|
|
4559
4773
|
* @internal Engine-internal fast path; AI users read the typed view through
|
|
4560
|
-
* `world.get(e,
|
|
4774
|
+
* `world.get(e, GlobalTransform).world`. The accessor is the zero-materialization
|
|
4561
4775
|
* route the propagate kernel and render walk use.
|
|
4562
4776
|
*/
|
|
4563
4777
|
_getArrayView(entity, component, fieldName) {
|
|
@@ -4884,11 +5098,11 @@ var WorldComponentAccess = class {
|
|
|
4884
5098
|
* (lazy mirror create or exclusive reparent).
|
|
4885
5099
|
* @internal
|
|
4886
5100
|
*/
|
|
4887
|
-
_addComponentCore(entity, componentData, internal) {
|
|
5101
|
+
_addComponentCore(entity, componentData, internal, resolveRequirements = true) {
|
|
4888
5102
|
const record = this.lookupAlive(entity, "addComponent", componentData.component.name);
|
|
4889
5103
|
if (!record.ok) return record;
|
|
4890
5104
|
const rec = record.value;
|
|
4891
|
-
|
|
5105
|
+
let srcArch = this.graph.archetypes[rec.archetypeId];
|
|
4892
5106
|
if (!srcArch) {
|
|
4893
5107
|
return err(
|
|
4894
5108
|
new StaleEntityError(entity, entityIndex(entity), entityGeneration(entity), {
|
|
@@ -4930,6 +5144,29 @@ var WorldComponentAccess = class {
|
|
|
4930
5144
|
if (enumErr !== null) {
|
|
4931
5145
|
return err(enumErr);
|
|
4932
5146
|
}
|
|
5147
|
+
if (resolveRequirements) {
|
|
5148
|
+
const required = expandComponentRequirements([componentData]).slice(1);
|
|
5149
|
+
for (const requirement of required) {
|
|
5150
|
+
if (srcArch.components.some(
|
|
5151
|
+
(candidate) => componentId(candidate) === componentId(requirement.component)
|
|
5152
|
+
)) {
|
|
5153
|
+
continue;
|
|
5154
|
+
}
|
|
5155
|
+
const added = this._addComponentCore(entity, requirement, internal, false);
|
|
5156
|
+
if (!added.ok) return added;
|
|
5157
|
+
srcArch = this.graph.archetypes[rec.archetypeId];
|
|
5158
|
+
if (!srcArch) {
|
|
5159
|
+
return err(
|
|
5160
|
+
new StaleEntityError(entity, entityIndex(entity), entityGeneration(entity), {
|
|
5161
|
+
operation: "addComponent",
|
|
5162
|
+
component: componentData.component.name,
|
|
5163
|
+
expectedGeneration: rec.generation,
|
|
5164
|
+
actualGeneration: rec.generation
|
|
5165
|
+
})
|
|
5166
|
+
);
|
|
5167
|
+
}
|
|
5168
|
+
}
|
|
5169
|
+
}
|
|
4933
5170
|
const localId = componentId(componentData.component);
|
|
4934
5171
|
if (srcArch.components.some((candidate) => componentId(candidate) === localId)) {
|
|
4935
5172
|
const role = relationshipRole(componentData.component);
|
|
@@ -4985,6 +5222,11 @@ var WorldComponentAccess = class {
|
|
|
4985
5222
|
if (!relationshipResult.ok) return relationshipResult;
|
|
4986
5223
|
}
|
|
4987
5224
|
this.markStructureChanged();
|
|
5225
|
+
this.state.recordStructuralEvidence({
|
|
5226
|
+
kind: "component-added",
|
|
5227
|
+
entity,
|
|
5228
|
+
componentId: localId
|
|
5229
|
+
});
|
|
4988
5230
|
return ok(void 0);
|
|
4989
5231
|
}
|
|
4990
5232
|
/**
|
|
@@ -5062,8 +5304,12 @@ var WorldComponentAccess = class {
|
|
|
5062
5304
|
} else {
|
|
5063
5305
|
this.storage.migrateEntity(rec, srcArch, targetArch);
|
|
5064
5306
|
}
|
|
5065
|
-
this.state.removeComponentChange(entity, componentId(component));
|
|
5066
5307
|
this.markStructureChanged();
|
|
5308
|
+
this.state.recordStructuralEvidence({
|
|
5309
|
+
kind: "component-removed",
|
|
5310
|
+
entity,
|
|
5311
|
+
componentId: localId
|
|
5312
|
+
});
|
|
5067
5313
|
return ok(void 0);
|
|
5068
5314
|
}
|
|
5069
5315
|
// ──────────────────────────────────────────────────────────────────────────
|
|
@@ -5097,6 +5343,7 @@ var WorldComponentAccess = class {
|
|
|
5097
5343
|
* Idempotent: a record with archetypeId !== -1 is already materialized.
|
|
5098
5344
|
*/
|
|
5099
5345
|
_materializePendingEntity(entity, componentDatas) {
|
|
5346
|
+
componentDatas = expandComponentRequirements(componentDatas);
|
|
5100
5347
|
const slot = entityIndex(entity);
|
|
5101
5348
|
const record = this.records[slot];
|
|
5102
5349
|
if (!record || record.archetypeId !== -1) return ok(void 0);
|
|
@@ -5131,6 +5378,10 @@ var WorldComponentAccess = class {
|
|
|
5131
5378
|
}
|
|
5132
5379
|
}
|
|
5133
5380
|
this.markStructureChanged();
|
|
5381
|
+
this.state.recordStructuralEvidence({
|
|
5382
|
+
kind: "spawn",
|
|
5383
|
+
entity
|
|
5384
|
+
});
|
|
5134
5385
|
return ok(void 0);
|
|
5135
5386
|
}
|
|
5136
5387
|
// ──────────────────────────────────────────────────────────────────────────
|
|
@@ -5411,7 +5662,7 @@ var BufferPool = class {
|
|
|
5411
5662
|
return n;
|
|
5412
5663
|
}
|
|
5413
5664
|
};
|
|
5414
|
-
var
|
|
5665
|
+
var SHARED_REF_RELEASE_EVIDENCE_CAPACITY = 4096;
|
|
5415
5666
|
var SharedRefStore = class {
|
|
5416
5667
|
payloads = /* @__PURE__ */ new Map();
|
|
5417
5668
|
refcounts = /* @__PURE__ */ new Map();
|
|
@@ -5420,7 +5671,8 @@ var SharedRefStore = class {
|
|
|
5420
5671
|
internedKeys = /* @__PURE__ */ new Map();
|
|
5421
5672
|
nextSlot = BUILTIN_BASE;
|
|
5422
5673
|
mutationEpoch = 0;
|
|
5423
|
-
|
|
5674
|
+
/** Latest published mutation epoch per live handle; not an event journal. */
|
|
5675
|
+
mutationEpochs = /* @__PURE__ */ new Map();
|
|
5424
5676
|
releaseJournal = [];
|
|
5425
5677
|
/**
|
|
5426
5678
|
* Generation table indexed by slot (D-6). Each entry tracks the current
|
|
@@ -5524,26 +5776,23 @@ var SharedRefStore = class {
|
|
|
5524
5776
|
throw new RangeError("SharedRefStore mutation epoch exhausted");
|
|
5525
5777
|
}
|
|
5526
5778
|
this.mutationEpoch += 1;
|
|
5527
|
-
this.
|
|
5528
|
-
if (this.mutationJournal.length > SHARED_REF_MUTATION_JOURNAL_CAPACITY) {
|
|
5529
|
-
this.mutationJournal.shift();
|
|
5530
|
-
}
|
|
5779
|
+
this.mutationEpochs.set(unwrapHandle(handle), this.mutationEpoch);
|
|
5531
5780
|
return ok(void 0);
|
|
5532
5781
|
}
|
|
5533
5782
|
/** Current upper bound for explicitly published payload mutations. */
|
|
5534
5783
|
getMutationEpoch() {
|
|
5535
5784
|
return this.mutationEpoch;
|
|
5536
5785
|
}
|
|
5537
|
-
/** Read
|
|
5786
|
+
/** Read each live handle whose latest published mutation is after `cursor`. */
|
|
5538
5787
|
readChangesSince(cursor) {
|
|
5539
|
-
const
|
|
5540
|
-
|
|
5541
|
-
|
|
5788
|
+
const records = [];
|
|
5789
|
+
for (const [handle, epoch] of this.mutationEpochs) {
|
|
5790
|
+
if (epoch > cursor) records.push({ epoch, handle });
|
|
5542
5791
|
}
|
|
5792
|
+
records.sort((left, right) => left.epoch - right.epoch || left.handle - right.handle);
|
|
5543
5793
|
return {
|
|
5544
|
-
status: "ok",
|
|
5545
5794
|
cursor: this.mutationEpoch,
|
|
5546
|
-
records
|
|
5795
|
+
records
|
|
5547
5796
|
};
|
|
5548
5797
|
}
|
|
5549
5798
|
/**
|
|
@@ -5609,6 +5858,7 @@ var SharedRefStore = class {
|
|
|
5609
5858
|
}
|
|
5610
5859
|
this.refcounts.delete(raw);
|
|
5611
5860
|
this.payloads.delete(raw);
|
|
5861
|
+
this.mutationEpochs.delete(raw);
|
|
5612
5862
|
const generation = storeGen + 1;
|
|
5613
5863
|
this._generations[slot] = generation;
|
|
5614
5864
|
if (!isRetiredSlot(generation)) {
|
|
@@ -5621,7 +5871,7 @@ var SharedRefStore = class {
|
|
|
5621
5871
|
evidence: "released"
|
|
5622
5872
|
});
|
|
5623
5873
|
this.releaseJournal.push(evidence);
|
|
5624
|
-
if (this.releaseJournal.length >
|
|
5874
|
+
if (this.releaseJournal.length > SHARED_REF_RELEASE_EVIDENCE_CAPACITY) {
|
|
5625
5875
|
this.releaseJournal.shift();
|
|
5626
5876
|
}
|
|
5627
5877
|
return ok(evidence);
|
|
@@ -5645,6 +5895,47 @@ var SharedRefStore = class {
|
|
|
5645
5895
|
return this.payloads.size;
|
|
5646
5896
|
}
|
|
5647
5897
|
};
|
|
5898
|
+
|
|
5899
|
+
// src/storage/structural-evidence.ts
|
|
5900
|
+
var StructuralEvidenceRing = class {
|
|
5901
|
+
constructor(capacity = 1024) {
|
|
5902
|
+
this.capacity = capacity;
|
|
5903
|
+
if (!Number.isSafeInteger(capacity) || capacity <= 0) {
|
|
5904
|
+
throw new RangeError("StructuralEvidenceRing capacity must be a positive safe integer");
|
|
5905
|
+
}
|
|
5906
|
+
this.events = new Array(capacity);
|
|
5907
|
+
}
|
|
5908
|
+
capacity;
|
|
5909
|
+
events;
|
|
5910
|
+
nextSequence = 1;
|
|
5911
|
+
get cursor() {
|
|
5912
|
+
return this.nextSequence - 1;
|
|
5913
|
+
}
|
|
5914
|
+
append(input) {
|
|
5915
|
+
const sequence = this.nextSequence;
|
|
5916
|
+
this.nextSequence += 1;
|
|
5917
|
+
this.events[(sequence - 1) % this.capacity] = { ...input, sequence };
|
|
5918
|
+
return sequence;
|
|
5919
|
+
}
|
|
5920
|
+
readAfter(cursor) {
|
|
5921
|
+
const latest = this.cursor;
|
|
5922
|
+
if (!Number.isSafeInteger(cursor) || cursor < 0 || cursor > latest) {
|
|
5923
|
+
throw new RangeError(`StructuralEvidenceRing cursor ${cursor} is outside 0..${latest}`);
|
|
5924
|
+
}
|
|
5925
|
+
const oldestAvailable = Math.max(1, latest - this.capacity + 1);
|
|
5926
|
+
if (cursor < oldestAvailable - 1)
|
|
5927
|
+
return { status: "overflow", cursor: latest, oldestAvailable };
|
|
5928
|
+
const events = [];
|
|
5929
|
+
for (let sequence = cursor + 1; sequence <= latest; sequence += 1) {
|
|
5930
|
+
const event = this.events[(sequence - 1) % this.capacity];
|
|
5931
|
+
if (event === void 0 || event.sequence !== sequence) {
|
|
5932
|
+
return { status: "overflow", cursor: latest, oldestAvailable };
|
|
5933
|
+
}
|
|
5934
|
+
events.push(event);
|
|
5935
|
+
}
|
|
5936
|
+
return { status: "ok", cursor: latest, events };
|
|
5937
|
+
}
|
|
5938
|
+
};
|
|
5648
5939
|
var UniqueRefStore = class {
|
|
5649
5940
|
payloads = /* @__PURE__ */ new Map();
|
|
5650
5941
|
freeSlots = [];
|
|
@@ -5801,7 +6092,7 @@ var WorldCore = class {
|
|
|
5801
6092
|
sharedRefs = new SharedRefStore();
|
|
5802
6093
|
storage;
|
|
5803
6094
|
componentMutationEpochs = [];
|
|
5804
|
-
|
|
6095
|
+
structuralEvidence = new StructuralEvidenceRing();
|
|
5805
6096
|
/** One packed reverse index per relationship source component. */
|
|
5806
6097
|
relationshipIndexes = /* @__PURE__ */ new Map();
|
|
5807
6098
|
mutationEpoch = 0;
|
|
@@ -5830,6 +6121,7 @@ function tableRow(world, record) {
|
|
|
5830
6121
|
return archetype?.rows[record.archetypeRow] ?? -1;
|
|
5831
6122
|
}
|
|
5832
6123
|
function spawnCore(world, componentDatas, internal) {
|
|
6124
|
+
componentDatas = expandComponentRequirements(componentDatas);
|
|
5833
6125
|
const filledData = [];
|
|
5834
6126
|
for (const cd of componentDatas) {
|
|
5835
6127
|
const preflight = world[worldInternal].preflightComponentData(null, cd);
|
|
@@ -5886,6 +6178,10 @@ function spawnCore(world, componentDatas, internal) {
|
|
|
5886
6178
|
}
|
|
5887
6179
|
}
|
|
5888
6180
|
world[worldInternal].markStructureChanged();
|
|
6181
|
+
world[worldInternal].recordStructuralEvidence({
|
|
6182
|
+
kind: "spawn",
|
|
6183
|
+
entity: spawnedEntity
|
|
6184
|
+
});
|
|
5889
6185
|
return ok(spawnedEntity);
|
|
5890
6186
|
}
|
|
5891
6187
|
function despawnCore(world, entity, internal) {
|
|
@@ -5936,8 +6232,11 @@ function despawnCore(world, entity, internal) {
|
|
|
5936
6232
|
}
|
|
5937
6233
|
}
|
|
5938
6234
|
}
|
|
6235
|
+
world[worldInternal].recordStructuralEvidence({
|
|
6236
|
+
kind: "despawn",
|
|
6237
|
+
entity
|
|
6238
|
+
});
|
|
5939
6239
|
if (record) {
|
|
5940
|
-
world[worldInternal].removeEntityChanges(entity);
|
|
5941
6240
|
record.archetypeId = -1;
|
|
5942
6241
|
record.archetypeRow = -1;
|
|
5943
6242
|
record.generation += 1;
|
|
@@ -6586,9 +6885,6 @@ var World = class {
|
|
|
6586
6885
|
get componentMutationEpochs() {
|
|
6587
6886
|
return this.core.componentMutationEpochs;
|
|
6588
6887
|
}
|
|
6589
|
-
get changeJournal() {
|
|
6590
|
-
return this.core.changeJournal;
|
|
6591
|
-
}
|
|
6592
6888
|
/** Free index slots (LIFO stack). */
|
|
6593
6889
|
get records() {
|
|
6594
6890
|
return this.core.records;
|
|
@@ -6668,8 +6964,8 @@ var World = class {
|
|
|
6668
6964
|
markComponentAdded: (entity, component) => this.internalmarkComponentAdded(entity, component),
|
|
6669
6965
|
markComponentsAdded: (entity, components) => this.internalmarkComponentsAdded(entity, components),
|
|
6670
6966
|
markComponentChanged: (entity, component) => this.internalmarkComponentChanged(entity, component),
|
|
6671
|
-
removeComponentChange: (entity, component) => this.internalremoveComponentChange(entity, component),
|
|
6672
6967
|
markStructureChanged: () => this.internalmarkStructureChanged(),
|
|
6968
|
+
recordStructuralEvidence: (evidence) => this.internalrecordStructuralEvidence(evidence),
|
|
6673
6969
|
routeError: (error, context) => this.internalrouteError(error, context)
|
|
6674
6970
|
});
|
|
6675
6971
|
this.clock = createWorldClock({ ...DEFAULT_TIME_POLICY, ...options.time });
|
|
@@ -6691,10 +6987,10 @@ var World = class {
|
|
|
6691
6987
|
despawnCore: this.internaldespawnCore.bind(this),
|
|
6692
6988
|
getArrayView: this.internalgetArrayView.bind(this),
|
|
6693
6989
|
getBufferPool: this.internalgetBufferPool.bind(this),
|
|
6694
|
-
getChangeCursor: this.internalgetChangeCursor.bind(this),
|
|
6695
6990
|
getClockWriter: this.internalgetClockWriter.bind(this),
|
|
6696
6991
|
getComponentChange: this.internalgetComponentChange.bind(this),
|
|
6697
6992
|
getComponentMutationEpoch: this.internalgetComponentMutationEpoch.bind(this),
|
|
6993
|
+
getComponentMutationEpochs: this.internalgetComponentMutationEpochs.bind(this),
|
|
6698
6994
|
getEntityArchetype: this.internalgetEntityArchetype.bind(this),
|
|
6699
6995
|
getFixedAccumulator: this.internalgetFixedAccumulator.bind(this),
|
|
6700
6996
|
getFreeIndices: this.internalgetFreeIndices.bind(this),
|
|
@@ -6709,28 +7005,28 @@ var World = class {
|
|
|
6709
7005
|
getSchedules: this.internalgetSchedules.bind(this),
|
|
6710
7006
|
getSharedRefs: this.internalgetSharedRefs.bind(this),
|
|
6711
7007
|
getStructureEpoch: this.internalgetStructureEpoch.bind(this),
|
|
7008
|
+
getStructuralEvidence: this.internalgetStructuralEvidence.bind(this),
|
|
6712
7009
|
getUniqueRefs: this.internalgetUniqueRefs.bind(this),
|
|
6713
7010
|
lookupAlive: this.internallookupAlive.bind(this),
|
|
6714
7011
|
markComponentAdded: this.internalmarkComponentAdded.bind(this),
|
|
6715
7012
|
markComponentChanged: this.internalmarkComponentChanged.bind(this),
|
|
6716
7013
|
markComponentRangeChanged: this.internalmarkComponentRangeChanged.bind(this),
|
|
6717
7014
|
markComponentsAdded: this.internalmarkComponentsAdded.bind(this),
|
|
6718
|
-
markDerivedComponentChanges: this.internalmarkDerivedComponentChanges.bind(this),
|
|
6719
7015
|
markStructureChanged: this.internalmarkStructureChanged.bind(this),
|
|
6720
7016
|
materializePendingEntity: this.internalmaterializePendingEntity.bind(this),
|
|
6721
7017
|
nextMutationEpoch: this.internalnextMutationEpoch.bind(this),
|
|
6722
7018
|
poisonExecution: this.internalpoisonExecution.bind(this),
|
|
7019
|
+
publishDerivedRange: this.internalpublishDerivedRange.bind(this),
|
|
6723
7020
|
preflightComponentData: this.internalpreflightComponentData.bind(this),
|
|
6724
|
-
readChangesSince: this.internalreadChangesSince.bind(this),
|
|
6725
7021
|
readRow: this.internalreadRow.bind(this),
|
|
7022
|
+
recordStructuralEvidence: this.internalrecordStructuralEvidence.bind(this),
|
|
6726
7023
|
recordIsLive: this.internalrecordIsLive.bind(this),
|
|
6727
7024
|
relationshipOnInsert: this.internalrelationshipOnInsert.bind(this),
|
|
6728
7025
|
relationshipOnRemove: this.internalrelationshipOnRemove.bind(this),
|
|
6729
7026
|
releaseManagedRefsOnRow: this.internalreleaseManagedRefsOnRow.bind(this),
|
|
6730
|
-
removeComponentChange: this.internalremoveComponentChange.bind(this),
|
|
6731
7027
|
removeComponentCore: this.internalremoveComponentCore.bind(this),
|
|
6732
|
-
removeEntityChanges: this.internalremoveEntityChanges.bind(this),
|
|
6733
7028
|
routeError: this.internalrouteError.bind(this),
|
|
7029
|
+
restoreMutationEpoch: this.internalrestoreMutationEpoch.bind(this),
|
|
6734
7030
|
setFixedAccumulator: this.internalsetFixedAccumulator.bind(this),
|
|
6735
7031
|
setQueryRow: this.internalsetQueryRow.bind(this),
|
|
6736
7032
|
spawnCore: this.internalspawnCore.bind(this),
|
|
@@ -6776,18 +7072,17 @@ var World = class {
|
|
|
6776
7072
|
internalgetMutationEpoch() {
|
|
6777
7073
|
return this.mutationEpoch;
|
|
6778
7074
|
}
|
|
6779
|
-
/** Current cursor for a persistent projection subscriber. */
|
|
6780
|
-
internalgetChangeCursor() {
|
|
6781
|
-
return this.changeJournal.cursor();
|
|
6782
|
-
}
|
|
6783
|
-
/** Read complete mutation evidence or an explicit rebuild signal. */
|
|
6784
|
-
internalreadChangesSince(cursor) {
|
|
6785
|
-
return this.changeJournal.readAfter(cursor);
|
|
6786
|
-
}
|
|
6787
7075
|
/** Structure snapshot used to invalidate borrowed query facades. */
|
|
6788
7076
|
internalgetStructureEpoch() {
|
|
6789
7077
|
return this.structureEpoch;
|
|
6790
7078
|
}
|
|
7079
|
+
/** Typed structural facts are produced by this World and consumed by projections. */
|
|
7080
|
+
internalgetStructuralEvidence() {
|
|
7081
|
+
return this.core.structuralEvidence;
|
|
7082
|
+
}
|
|
7083
|
+
internalrecordStructuralEvidence(evidence) {
|
|
7084
|
+
this.core.structuralEvidence.append(evidence);
|
|
7085
|
+
}
|
|
6791
7086
|
/** Resolve current logical identity for a packed entity handle. */
|
|
6792
7087
|
internalgetEntityArchetype(entity) {
|
|
6793
7088
|
const record = this.records[entityIndex(entity)];
|
|
@@ -6808,6 +7103,17 @@ var World = class {
|
|
|
6808
7103
|
this.mutationEpoch += 1;
|
|
6809
7104
|
return this.mutationEpoch;
|
|
6810
7105
|
}
|
|
7106
|
+
internalrestoreMutationEpoch(epoch) {
|
|
7107
|
+
this.mutationEpoch = epoch;
|
|
7108
|
+
}
|
|
7109
|
+
internalpublishDerivedRange(table, componentId2, rowStart, rowCount, epoch) {
|
|
7110
|
+
const epochs = table.storage.get(componentId2)?.epochs;
|
|
7111
|
+
if (epochs === void 0) {
|
|
7112
|
+
throw new Error(`Derived component ${componentId2} is not in the table.`);
|
|
7113
|
+
}
|
|
7114
|
+
epochs.changed.fill(epoch, rowStart, rowStart + rowCount);
|
|
7115
|
+
this.componentMutationEpochs[componentId2] = epoch;
|
|
7116
|
+
}
|
|
6811
7117
|
/** Record one successful structural mutation. */
|
|
6812
7118
|
internalmarkStructureChanged() {
|
|
6813
7119
|
this.structureEpoch += 1;
|
|
@@ -6828,7 +7134,6 @@ var World = class {
|
|
|
6828
7134
|
markComponentsAdded(this.graph, record, entity, componentIds, epoch);
|
|
6829
7135
|
for (const componentId2 of componentIds) {
|
|
6830
7136
|
this.componentMutationEpochs[componentId2] = epoch;
|
|
6831
|
-
this.changeJournal.append({ kind: "component-added", entity, componentId: componentId2 });
|
|
6832
7137
|
}
|
|
6833
7138
|
}
|
|
6834
7139
|
/** Mark an existing component as changed at the current tick. */
|
|
@@ -6842,7 +7147,6 @@ var World = class {
|
|
|
6842
7147
|
});
|
|
6843
7148
|
if (epoch !== void 0) {
|
|
6844
7149
|
this.componentMutationEpochs[componentId2] = epoch;
|
|
6845
|
-
this.changeJournal.append({ kind: "component-changed", entity, componentId: componentId2 });
|
|
6846
7150
|
}
|
|
6847
7151
|
}
|
|
6848
7152
|
/** Mark one contiguous component range with a single epoch. */
|
|
@@ -6852,18 +7156,15 @@ var World = class {
|
|
|
6852
7156
|
const epoch = this.internalnextMutationEpoch();
|
|
6853
7157
|
epochs.changed.fill(epoch, rowStart, rowStart + rowCount);
|
|
6854
7158
|
this.componentMutationEpochs[componentId2] = epoch;
|
|
6855
|
-
for (const entity of readTableEntityRange(table, rowStart, rowCount)) {
|
|
6856
|
-
this.changeJournal.append({
|
|
6857
|
-
kind: "component-changed",
|
|
6858
|
-
entity,
|
|
6859
|
-
componentId: componentId2
|
|
6860
|
-
});
|
|
6861
|
-
}
|
|
6862
7159
|
}
|
|
6863
7160
|
/** Latest mutation token for one component-owned projection. */
|
|
6864
7161
|
internalgetComponentMutationEpoch(componentId2) {
|
|
6865
7162
|
return this.componentMutationEpochs[componentId2] ?? 0;
|
|
6866
7163
|
}
|
|
7164
|
+
/** Borrow the component-version summary used to skip unrelated value columns. */
|
|
7165
|
+
internalgetComponentMutationEpochs() {
|
|
7166
|
+
return this.componentMutationEpochs;
|
|
7167
|
+
}
|
|
6867
7168
|
/** Read a materialized relationship target in O(1 + k). */
|
|
6868
7169
|
internalgetRelationshipTargetEntities(source, target) {
|
|
6869
7170
|
return this.componentAccess.relationshipTargetEntries(source, target);
|
|
@@ -6872,12 +7173,6 @@ var World = class {
|
|
|
6872
7173
|
internalgetRelationshipEpoch(source) {
|
|
6873
7174
|
return this.core.relationshipIndexes.get(componentId(source))?.epoch ?? 0;
|
|
6874
7175
|
}
|
|
6875
|
-
/** Publish changes to a value derived without an authored mutation epoch. */
|
|
6876
|
-
internalmarkDerivedComponentChanges(componentId2, entities) {
|
|
6877
|
-
for (const entity of entities) {
|
|
6878
|
-
this.changeJournal.append({ kind: "derived-component-changed", entity, componentId: componentId2 });
|
|
6879
|
-
}
|
|
6880
|
-
}
|
|
6881
7176
|
/** Query facade write after the facade has already marked evidence. */
|
|
6882
7177
|
internalsetQueryRow(entity, component, value) {
|
|
6883
7178
|
return this.componentAccess.set(entity, component, value, false);
|
|
@@ -6886,14 +7181,6 @@ var World = class {
|
|
|
6886
7181
|
internalgetQueryRow(entity, component) {
|
|
6887
7182
|
return this.componentAccess.get(entity, component);
|
|
6888
7183
|
}
|
|
6889
|
-
/** Remove one component's change state after archetype removal. */
|
|
6890
|
-
internalremoveComponentChange(entity, componentId2) {
|
|
6891
|
-
this.changeJournal.append({ kind: "component-removed", entity, componentId: componentId2 });
|
|
6892
|
-
}
|
|
6893
|
-
/** Remove all change state before an entity handle is retired. */
|
|
6894
|
-
internalremoveEntityChanges(entity) {
|
|
6895
|
-
this.changeJournal.append({ kind: "entity-removed", entity });
|
|
6896
|
-
}
|
|
6897
7184
|
/** Return resource change ticks for diagnostics and resource-driven systems. */
|
|
6898
7185
|
getResourceChange(name) {
|
|
6899
7186
|
const entry = this.resources.entries.get(name);
|
|
@@ -6910,8 +7197,8 @@ var World = class {
|
|
|
6910
7197
|
*
|
|
6911
7198
|
* Not part of the public API.
|
|
6912
7199
|
*/
|
|
6913
|
-
internalrouteError(
|
|
6914
|
-
console.error(`[${ctx?.systemName ?? "World"}]`,
|
|
7200
|
+
internalrouteError(err12, ctx) {
|
|
7201
|
+
console.error(`[${ctx?.systemName ?? "World"}]`, err12);
|
|
6915
7202
|
}
|
|
6916
7203
|
// ──────────────────────────────────────────────────────────────────────────
|
|
6917
7204
|
// accessors — M1 extraction seam
|