@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
package/dist/index.mjs
CHANGED
|
@@ -1591,240 +1591,6 @@ function createWorldClock(policy) {
|
|
|
1591
1591
|
writer: { time, fixed }
|
|
1592
1592
|
};
|
|
1593
1593
|
}
|
|
1594
|
-
var SIZE_CLASSES = Object.freeze([
|
|
1595
|
-
16,
|
|
1596
|
-
64,
|
|
1597
|
-
256,
|
|
1598
|
-
1024,
|
|
1599
|
-
4096,
|
|
1600
|
-
16384,
|
|
1601
|
-
65536,
|
|
1602
|
-
262144
|
|
1603
|
-
]);
|
|
1604
|
-
var HAS_TRANSFER = typeof ArrayBuffer.prototype.transfer === "function";
|
|
1605
|
-
function bucketIndex(byteLength) {
|
|
1606
|
-
if (byteLength === 0) return -1;
|
|
1607
|
-
for (let i = 0; i < SIZE_CLASSES.length; i++) {
|
|
1608
|
-
const b = SIZE_CLASSES[i];
|
|
1609
|
-
if (b !== void 0 && byteLength <= b) return i;
|
|
1610
|
-
}
|
|
1611
|
-
return SIZE_CLASSES.length;
|
|
1612
|
-
}
|
|
1613
|
-
var BufferPool = class {
|
|
1614
|
-
slots = /* @__PURE__ */ new Map();
|
|
1615
|
-
/**
|
|
1616
|
-
* Per-bucket free-lists: `freeBuckets[i]` is a LIFO stack of slot ids
|
|
1617
|
-
* whose backing buffer is parked on bucket `i`. Released slots stay on
|
|
1618
|
-
* their original bucket's free-list - v1 never moves a slot between
|
|
1619
|
-
* buckets on release (D-7 no trim).
|
|
1620
|
-
*/
|
|
1621
|
-
freeBuckets = SIZE_CLASSES.map(() => []);
|
|
1622
|
-
nextId = 1;
|
|
1623
|
-
/**
|
|
1624
|
-
* Allocate a managed buffer slot of at least `byteLength` bytes.
|
|
1625
|
-
*
|
|
1626
|
-
* Routes:
|
|
1627
|
-
* - invalid byteLength -> structured out-of-bounds error.
|
|
1628
|
-
* - byteLength == 0 -> ok({ id, view: zero-length Uint8Array }) (no bucket).
|
|
1629
|
-
* - byteLength <= 262144 -> ok({ id, view }), bucket = smallest >= byteLength.
|
|
1630
|
-
* - byteLength > 262144 -> a dedicated allocation; allocation failure is structured.
|
|
1631
|
-
*
|
|
1632
|
-
* D-5: size classes are radix-4 (16 / 64 / 256 / 1K / 4K / 16K / 64K / 256K).
|
|
1633
|
-
* Free-list pop reuses the most recently released slot id at the same bucket;
|
|
1634
|
-
* miss falls through to a fresh allocation.
|
|
1635
|
-
*/
|
|
1636
|
-
alloc(byteLength) {
|
|
1637
|
-
if (byteLength === 0) {
|
|
1638
|
-
const id2 = this.nextId++;
|
|
1639
|
-
const buffer2 = new ArrayBuffer(0);
|
|
1640
|
-
const view2 = new Uint8Array(buffer2);
|
|
1641
|
-
this.slots.set(id2, { sizeClassIdx: -1, buffer: buffer2, view: view2, byteLength: 0, live: true });
|
|
1642
|
-
return ok({ id: id2, view: view2 });
|
|
1643
|
-
}
|
|
1644
|
-
if (!Number.isSafeInteger(byteLength) || byteLength < 0) {
|
|
1645
|
-
return err(new ManagedBufferOutOfBoundsError(byteLength, 0));
|
|
1646
|
-
}
|
|
1647
|
-
const idx = bucketIndex(byteLength);
|
|
1648
|
-
if (idx === SIZE_CLASSES.length) {
|
|
1649
|
-
try {
|
|
1650
|
-
const buffer2 = new ArrayBuffer(byteLength);
|
|
1651
|
-
const view2 = new Uint8Array(buffer2);
|
|
1652
|
-
const id2 = this.nextId++;
|
|
1653
|
-
this.slots.set(id2, { sizeClassIdx: idx, buffer: buffer2, view: view2, byteLength, live: true });
|
|
1654
|
-
return ok({ id: id2, view: view2 });
|
|
1655
|
-
} catch {
|
|
1656
|
-
return err(new ManagedBufferOutOfBoundsError(byteLength, 0));
|
|
1657
|
-
}
|
|
1658
|
-
}
|
|
1659
|
-
const bucketBytes = SIZE_CLASSES[idx];
|
|
1660
|
-
if (bucketBytes === void 0) {
|
|
1661
|
-
return err(new ManagedBufferOutOfBoundsError(byteLength, 0));
|
|
1662
|
-
}
|
|
1663
|
-
const free = this.freeBuckets[idx];
|
|
1664
|
-
if (free !== void 0 && free.length > 0) {
|
|
1665
|
-
const id2 = free.pop();
|
|
1666
|
-
const slot = this.slots.get(id2);
|
|
1667
|
-
if (slot === void 0) {
|
|
1668
|
-
return err(new ManagedBufferOutOfBoundsError(byteLength, 0));
|
|
1669
|
-
}
|
|
1670
|
-
slot.byteLength = byteLength;
|
|
1671
|
-
slot.view = new Uint8Array(slot.buffer, 0, byteLength);
|
|
1672
|
-
slot.live = true;
|
|
1673
|
-
new Uint8Array(slot.buffer).fill(0);
|
|
1674
|
-
return ok({ id: id2, view: slot.view });
|
|
1675
|
-
}
|
|
1676
|
-
const id = this.nextId++;
|
|
1677
|
-
const buffer = new ArrayBuffer(bucketBytes);
|
|
1678
|
-
const view = new Uint8Array(buffer, 0, byteLength);
|
|
1679
|
-
this.slots.set(id, {
|
|
1680
|
-
sizeClassIdx: idx,
|
|
1681
|
-
buffer,
|
|
1682
|
-
view,
|
|
1683
|
-
byteLength,
|
|
1684
|
-
live: true
|
|
1685
|
-
});
|
|
1686
|
-
return ok({ id, view });
|
|
1687
|
-
}
|
|
1688
|
-
/**
|
|
1689
|
-
* Grow slot `id` to `newBytes`. Returns the post-grow Uint8Array view.
|
|
1690
|
-
*
|
|
1691
|
-
* Routes (D-6 / D-7):
|
|
1692
|
-
* - newBytes < current -> err(managed-buffer-shrink-not-supported).
|
|
1693
|
-
* - newBytes == current -> ok(current view) (no-op, identity preserved).
|
|
1694
|
-
* - newBytes > current && same bucket -> ok(re-sliced view) (no transfer).
|
|
1695
|
-
* - newBytes > current && cross bucket -> ok(new view) backed by a fresh
|
|
1696
|
-
* bucket buffer; the prior ArrayBuffer is detached via transfer (ES2024)
|
|
1697
|
-
* or replaced via allocate-and-copy fallback. Old `view`s captured by
|
|
1698
|
-
* the caller become detached / orphaned - callers must use `pool.view(id)`
|
|
1699
|
-
* after grow to read the refreshed view (the `release` loop refreshes
|
|
1700
|
-
* automatically).
|
|
1701
|
-
* - newBytes beyond the last pooled class -> dedicated allocation.
|
|
1702
|
-
*/
|
|
1703
|
-
grow(id, newBytes) {
|
|
1704
|
-
const slot = this.slots.get(id);
|
|
1705
|
-
if (slot === void 0) {
|
|
1706
|
-
return err(new ManagedBufferOutOfBoundsError(newBytes, 0));
|
|
1707
|
-
}
|
|
1708
|
-
if (newBytes < slot.byteLength) {
|
|
1709
|
-
return err(new ManagedBufferShrinkNotSupportedError(newBytes, slot.byteLength));
|
|
1710
|
-
}
|
|
1711
|
-
if (newBytes === slot.byteLength) {
|
|
1712
|
-
return ok(slot.view);
|
|
1713
|
-
}
|
|
1714
|
-
if (!Number.isSafeInteger(newBytes) || newBytes < 0) {
|
|
1715
|
-
return err(new ManagedBufferOutOfBoundsError(newBytes, slot.buffer.byteLength));
|
|
1716
|
-
}
|
|
1717
|
-
const newIdx = bucketIndex(newBytes);
|
|
1718
|
-
if (newBytes <= slot.buffer.byteLength) {
|
|
1719
|
-
slot.byteLength = newBytes;
|
|
1720
|
-
slot.view = new Uint8Array(slot.buffer, 0, newBytes);
|
|
1721
|
-
return ok(slot.view);
|
|
1722
|
-
}
|
|
1723
|
-
const newBucketBytes = SIZE_CLASSES[newIdx] ?? Math.max(newBytes, slot.buffer.byteLength * 2);
|
|
1724
|
-
const oldByteLength = slot.byteLength;
|
|
1725
|
-
let nextBuffer;
|
|
1726
|
-
try {
|
|
1727
|
-
if (HAS_TRANSFER) {
|
|
1728
|
-
nextBuffer = slot.buffer.transfer(newBucketBytes);
|
|
1729
|
-
} else {
|
|
1730
|
-
nextBuffer = new ArrayBuffer(newBucketBytes);
|
|
1731
|
-
new Uint8Array(nextBuffer).set(new Uint8Array(slot.buffer, 0, oldByteLength));
|
|
1732
|
-
}
|
|
1733
|
-
} catch {
|
|
1734
|
-
return err(new ManagedBufferOutOfBoundsError(newBytes, slot.buffer.byteLength));
|
|
1735
|
-
}
|
|
1736
|
-
slot.sizeClassIdx = newIdx;
|
|
1737
|
-
slot.buffer = nextBuffer;
|
|
1738
|
-
slot.byteLength = newBytes;
|
|
1739
|
-
slot.view = new Uint8Array(nextBuffer, 0, newBytes);
|
|
1740
|
-
return ok(slot.view);
|
|
1741
|
-
}
|
|
1742
|
-
/**
|
|
1743
|
-
* Release slot `id` back to its bucket's free-list. Releasing an unknown id
|
|
1744
|
-
* is a no-op - World's release loop drives this and idempotency keeps the
|
|
1745
|
-
* despawn chain free of bookkeeping noise. Bucket free-lists are NEVER
|
|
1746
|
-
* trimmed in v1 (D-7 no trim).
|
|
1747
|
-
*/
|
|
1748
|
-
release(id) {
|
|
1749
|
-
const slot = this.slots.get(id);
|
|
1750
|
-
if (slot === void 0) return ok(void 0);
|
|
1751
|
-
if (!slot.live) return ok(void 0);
|
|
1752
|
-
slot.live = false;
|
|
1753
|
-
if (slot.sizeClassIdx >= 0 && slot.sizeClassIdx < SIZE_CLASSES.length) {
|
|
1754
|
-
const bucket = this.freeBuckets[slot.sizeClassIdx];
|
|
1755
|
-
if (bucket !== void 0) bucket.push(id);
|
|
1756
|
-
} else {
|
|
1757
|
-
this.slots.delete(id);
|
|
1758
|
-
}
|
|
1759
|
-
return ok(void 0);
|
|
1760
|
-
}
|
|
1761
|
-
/**
|
|
1762
|
-
* Return the live view for slot `id`. Used by World after `grow` to
|
|
1763
|
-
* refresh the column's stored view reference. Returns a zero-length view
|
|
1764
|
-
* for unknown / released ids so callers never crash on use-after-release.
|
|
1765
|
-
*/
|
|
1766
|
-
view(id) {
|
|
1767
|
-
const slot = this.slots.get(id);
|
|
1768
|
-
if (slot === void 0 || !slot.live) return new Uint8Array(0);
|
|
1769
|
-
return slot.view;
|
|
1770
|
-
}
|
|
1771
|
-
/**
|
|
1772
|
-
* Return the bucket-rounded byte capacity for slot `id` --- i.e.
|
|
1773
|
-
* `SIZE_CLASSES[slot.sizeClassIdx]`. Used by managed-buffer view callers
|
|
1774
|
-
* (D-4 no-cache: re-queried per accessor). Returns `0` for the zero-length
|
|
1775
|
-
* (`alloc(0)`) slot; returns `0` for unknown / released ids (mirrors
|
|
1776
|
-
* `view(id)` use-after-release semantics).
|
|
1777
|
-
*/
|
|
1778
|
-
byteCapacity(id) {
|
|
1779
|
-
const slot = this.slots.get(id);
|
|
1780
|
-
if (slot === void 0 || !slot.live) return 0;
|
|
1781
|
-
if (slot.sizeClassIdx < 0) return 0;
|
|
1782
|
-
return slot.buffer.byteLength;
|
|
1783
|
-
}
|
|
1784
|
-
/**
|
|
1785
|
-
* Reset the logical byteLength of slot `id` to `newByteLength` while
|
|
1786
|
-
* keeping the same bucket allocation (no transfer, no release). Only
|
|
1787
|
-
* legal when `newByteLength <= bucketBytes`; the slot's bucket index
|
|
1788
|
-
* does not move (D-7 v1 forbids cross-bucket shrink). Used by managed-
|
|
1789
|
-
* buffer clear paths so the slot retains its `byteCapacity` while the
|
|
1790
|
-
* live view becomes zero-length. Bytes past the new logical length are
|
|
1791
|
-
* zero-filled defensively.
|
|
1792
|
-
*
|
|
1793
|
-
* Returns `err(ManagedBufferShrinkNotSupportedError)` when called on a
|
|
1794
|
-
* `sizeClassIdx === -1` slot (alloc(0)) with `newByteLength > 0`, or
|
|
1795
|
-
* when `newByteLength` exceeds the bucket capacity.
|
|
1796
|
-
*/
|
|
1797
|
-
setLogicalLength(id, newByteLength) {
|
|
1798
|
-
const slot = this.slots.get(id);
|
|
1799
|
-
if (slot === void 0) {
|
|
1800
|
-
return err(new ManagedBufferOutOfBoundsError(newByteLength, 0));
|
|
1801
|
-
}
|
|
1802
|
-
if (slot.sizeClassIdx < 0) {
|
|
1803
|
-
if (newByteLength === 0) {
|
|
1804
|
-
slot.byteLength = 0;
|
|
1805
|
-
slot.view = new Uint8Array(slot.buffer);
|
|
1806
|
-
return ok(slot.view);
|
|
1807
|
-
}
|
|
1808
|
-
return err(new ManagedBufferOutOfBoundsError(newByteLength, 0));
|
|
1809
|
-
}
|
|
1810
|
-
const bucketBytes = slot.buffer.byteLength;
|
|
1811
|
-
if (newByteLength > bucketBytes) {
|
|
1812
|
-
return err(new ManagedBufferOutOfBoundsError(newByteLength, bucketBytes));
|
|
1813
|
-
}
|
|
1814
|
-
if (newByteLength < slot.byteLength) {
|
|
1815
|
-
new Uint8Array(slot.buffer, newByteLength, slot.byteLength - newByteLength).fill(0);
|
|
1816
|
-
}
|
|
1817
|
-
slot.byteLength = newByteLength;
|
|
1818
|
-
slot.view = new Uint8Array(slot.buffer, 0, newByteLength);
|
|
1819
|
-
return ok(slot.view);
|
|
1820
|
-
}
|
|
1821
|
-
/** @internal Diagnostic count of live slots. Exposed for tests + inspector. */
|
|
1822
|
-
_liveCount() {
|
|
1823
|
-
let n = 0;
|
|
1824
|
-
for (const s of this.slots.values()) if (s.live) n += 1;
|
|
1825
|
-
return n;
|
|
1826
|
-
}
|
|
1827
|
-
};
|
|
1828
1594
|
var ENTITY_MAX_INDEX = MAX_SLOT;
|
|
1829
1595
|
var ENTITY_MAX_GENERATION = MAX_GEN;
|
|
1830
1596
|
var ENTITY_NULL_RAW = 4294967295;
|
|
@@ -1841,112 +1607,10 @@ function entityGeneration(entity) {
|
|
|
1841
1607
|
return unpackGen(entity);
|
|
1842
1608
|
}
|
|
1843
1609
|
|
|
1844
|
-
// src/
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
if (fieldType === "array<entity>") return [];
|
|
1849
|
-
return 0;
|
|
1850
|
-
}
|
|
1851
|
-
function fillComponentDefaults(token, raw) {
|
|
1852
|
-
const schema = componentSchema(token);
|
|
1853
|
-
const layer2 = componentDefinition(token).defaults;
|
|
1854
|
-
const out = /* @__PURE__ */ Object.create(null);
|
|
1855
|
-
const rawObj = raw ?? void 0;
|
|
1856
|
-
for (const fieldName of Object.keys(schema)) {
|
|
1857
|
-
const fieldType = schema[fieldName];
|
|
1858
|
-
if (fieldType === void 0) continue;
|
|
1859
|
-
if (rawObj !== void 0 && fieldName in rawObj) {
|
|
1860
|
-
out[fieldName] = rawObj[fieldName];
|
|
1861
|
-
continue;
|
|
1862
|
-
}
|
|
1863
|
-
if (layer2 !== void 0 && fieldName in layer2) {
|
|
1864
|
-
out[fieldName] = layer2[fieldName];
|
|
1865
|
-
continue;
|
|
1866
|
-
}
|
|
1867
|
-
out[fieldName] = typeDefault(fieldType);
|
|
1868
|
-
}
|
|
1869
|
-
return out;
|
|
1870
|
-
}
|
|
1871
|
-
function validateComponentDataKeys(token, raw) {
|
|
1872
|
-
if (raw === void 0) return null;
|
|
1873
|
-
const schema = componentSchema(token);
|
|
1874
|
-
const rawObj = raw;
|
|
1875
|
-
for (const fieldName of Object.keys(rawObj)) {
|
|
1876
|
-
if (!(fieldName in schema)) {
|
|
1877
|
-
return new SpawnDataUnknownFieldError(token.name, fieldName, Object.keys(schema));
|
|
1878
|
-
}
|
|
1879
|
-
}
|
|
1880
|
-
return null;
|
|
1881
|
-
}
|
|
1882
|
-
|
|
1883
|
-
// src/component-value-validate.ts
|
|
1884
|
-
function isNumericHandle(v) {
|
|
1885
|
-
return typeof v === "number";
|
|
1886
|
-
}
|
|
1887
|
-
function isSharedScalarType(fieldType) {
|
|
1888
|
-
return fieldType.startsWith("shared<") && fieldType.endsWith(">");
|
|
1889
|
-
}
|
|
1890
|
-
function isSharedArrayType(fieldType) {
|
|
1891
|
-
if (!fieldType.startsWith("array<") || !fieldType.endsWith(">")) return false;
|
|
1892
|
-
const inner = fieldType.slice(6, -1);
|
|
1893
|
-
const head = inner.indexOf(",") === -1 ? inner : inner.slice(0, inner.indexOf(",")).trim();
|
|
1894
|
-
return head.startsWith("shared<") && head.endsWith(">");
|
|
1895
|
-
}
|
|
1896
|
-
function isArrayPayload(value) {
|
|
1897
|
-
if (Array.isArray(value)) return true;
|
|
1898
|
-
return ArrayBuffer.isView(value) && typeof value.length === "number";
|
|
1899
|
-
}
|
|
1900
|
-
function validateManagedArrayValues(token, raw) {
|
|
1901
|
-
if (raw === void 0) return null;
|
|
1902
|
-
const fields = componentDefinition(token).fields;
|
|
1903
|
-
const rawObj = raw;
|
|
1904
|
-
for (const fieldName of Object.keys(rawObj)) {
|
|
1905
|
-
const reflection = fields[fieldName];
|
|
1906
|
-
if (reflection?.arrayMeta === void 0) continue;
|
|
1907
|
-
const value = rawObj[fieldName];
|
|
1908
|
-
if (value === 0) continue;
|
|
1909
|
-
if (value === void 0 || value === null || isArrayPayload(value)) continue;
|
|
1910
|
-
return new ManagedArrayInvalidValueError(token.name, fieldName, reflection.type, value);
|
|
1911
|
-
}
|
|
1912
|
-
return null;
|
|
1913
|
-
}
|
|
1914
|
-
function validateSharedFieldValues(token, raw) {
|
|
1915
|
-
if (raw === void 0) return null;
|
|
1916
|
-
const schema = componentSchema(token);
|
|
1917
|
-
const rawObj = raw;
|
|
1918
|
-
for (const fieldName of Object.keys(rawObj)) {
|
|
1919
|
-
const fieldType = schema[fieldName];
|
|
1920
|
-
if (fieldType === void 0) continue;
|
|
1921
|
-
const value = rawObj[fieldName];
|
|
1922
|
-
if (value === void 0 || value === null) continue;
|
|
1923
|
-
if (isSharedScalarType(fieldType)) {
|
|
1924
|
-
if (!isNumericHandle(value)) {
|
|
1925
|
-
return new SharedFieldInvalidValueError(token.name, fieldName, fieldType, value);
|
|
1926
|
-
}
|
|
1927
|
-
} else if (isSharedArrayType(fieldType)) {
|
|
1928
|
-
if (!Array.isArray(value)) continue;
|
|
1929
|
-
for (let i = 0; i < value.length; i++) {
|
|
1930
|
-
const el = value[i];
|
|
1931
|
-
if (el === void 0 || el === null) continue;
|
|
1932
|
-
if (!isNumericHandle(el)) {
|
|
1933
|
-
return new SharedFieldInvalidValueError(token.name, fieldName, fieldType, el, i);
|
|
1934
|
-
}
|
|
1935
|
-
}
|
|
1936
|
-
}
|
|
1937
|
-
}
|
|
1938
|
-
return null;
|
|
1939
|
-
}
|
|
1940
|
-
|
|
1941
|
-
// src/internal.ts
|
|
1942
|
-
var DERIVED_WRITER = /* @__PURE__ */ Symbol.for(
|
|
1943
|
-
"forgeax.ecs.query.derivedWriter"
|
|
1944
|
-
);
|
|
1945
|
-
|
|
1946
|
-
// src/storage/archetype.ts
|
|
1947
|
-
var INITIAL_CAPACITY = 64;
|
|
1948
|
-
function archetypeKey(componentIds) {
|
|
1949
|
-
return [...foldEssentials(componentIds)].sort((a, b) => a - b).join("+");
|
|
1610
|
+
// src/storage/archetype.ts
|
|
1611
|
+
var INITIAL_CAPACITY = 64;
|
|
1612
|
+
function archetypeKey(componentIds) {
|
|
1613
|
+
return [...foldEssentials(componentIds)].sort((a, b) => a - b).join("+");
|
|
1950
1614
|
}
|
|
1951
1615
|
function createArchetype(components, id, tableId) {
|
|
1952
1616
|
const byId = /* @__PURE__ */ new Map();
|
|
@@ -2014,7 +1678,7 @@ function createColumn(fieldType, capacity, arity = 1, shared = false) {
|
|
|
2014
1678
|
const view = createFieldView(Ctor, buffer);
|
|
2015
1679
|
return { buffer, view, capacity, fieldType, arity };
|
|
2016
1680
|
}
|
|
2017
|
-
var
|
|
1681
|
+
var HAS_TRANSFER = typeof ArrayBuffer.prototype.transfer === "function";
|
|
2018
1682
|
function growColumn(col, newCapacity) {
|
|
2019
1683
|
const meta = TYPE_METADATA[col.fieldType];
|
|
2020
1684
|
if (!meta) throw new Error(`Missing TYPE_METADATA entry for scalar field type ${col.fieldType}`);
|
|
@@ -2028,7 +1692,7 @@ function growColumn(col, newCapacity) {
|
|
|
2028
1692
|
if (isSharedBuffer(col.buffer)) {
|
|
2029
1693
|
buffer = new SharedArrayBuffer(newByteLength);
|
|
2030
1694
|
new Uint8Array(buffer).set(new Uint8Array(col.buffer));
|
|
2031
|
-
} else if (
|
|
1695
|
+
} else if (HAS_TRANSFER) {
|
|
2032
1696
|
buffer = col.buffer.transfer(
|
|
2033
1697
|
newByteLength
|
|
2034
1698
|
);
|
|
@@ -2066,7 +1730,7 @@ function canonicalComponents(components) {
|
|
|
2066
1730
|
for (const component of components) byId.set(componentId(component), component);
|
|
2067
1731
|
return [...byId.values()].sort((a, b) => componentId(a) - componentId(b));
|
|
2068
1732
|
}
|
|
2069
|
-
function createTable(components, id, shared = false) {
|
|
1733
|
+
function createTable(components, id, shared = false, activeDirectories = [/* @__PURE__ */ new Set()]) {
|
|
2070
1734
|
const sortedComponents = canonicalComponents(components);
|
|
2071
1735
|
const capacity = INITIAL_CAPACITY2;
|
|
2072
1736
|
const storage = /* @__PURE__ */ new Map();
|
|
@@ -2110,19 +1774,26 @@ function createTable(components, id, shared = false) {
|
|
|
2110
1774
|
storage,
|
|
2111
1775
|
size: 0,
|
|
2112
1776
|
capacity,
|
|
2113
|
-
version: 0
|
|
1777
|
+
version: 0,
|
|
1778
|
+
membership: new Float64Array(Math.ceil(capacity / PROJECTION_BLOCK_SIZE)),
|
|
1779
|
+
activeDirectories
|
|
2114
1780
|
};
|
|
2115
1781
|
}
|
|
2116
|
-
function appendTableRow(table, entity) {
|
|
1782
|
+
function appendTableRow(table, entity, epoch = 0) {
|
|
2117
1783
|
if (table.size === table.capacity) growTable(table, table.capacity * 2);
|
|
2118
1784
|
const row = table.size;
|
|
2119
1785
|
const self = table.storage.get(componentId(Entity))?.fields.get("self");
|
|
2120
1786
|
if (self !== void 0) self.view[row] = entity;
|
|
2121
1787
|
table.size = row + 1;
|
|
1788
|
+
if (row === 0) for (const directory of table.activeDirectories) directory.add(table);
|
|
1789
|
+
markTableMembership(table, row, epoch);
|
|
2122
1790
|
return row;
|
|
2123
1791
|
}
|
|
2124
|
-
function removeTableRow(table, row) {
|
|
1792
|
+
function removeTableRow(table, row, epoch = 0) {
|
|
2125
1793
|
const lastRow = table.size - 1;
|
|
1794
|
+
markTableMembership(table, row, epoch);
|
|
1795
|
+
markTableMembership(table, lastRow, epoch);
|
|
1796
|
+
if (lastRow === 0) for (const directory of table.activeDirectories) directory.delete(table);
|
|
2126
1797
|
if (row === lastRow) {
|
|
2127
1798
|
table.size = lastRow;
|
|
2128
1799
|
return null;
|
|
@@ -2150,9 +1821,15 @@ function growTable(table, targetCapacity) {
|
|
|
2150
1821
|
componentStorage.fields = fields;
|
|
2151
1822
|
componentStorage.epochs = growComponentEpochColumns(componentStorage.epochs, capacity);
|
|
2152
1823
|
}
|
|
1824
|
+
const membership = new Float64Array(Math.ceil(capacity / PROJECTION_BLOCK_SIZE));
|
|
1825
|
+
membership.set(table.membership);
|
|
1826
|
+
table.membership = membership;
|
|
2153
1827
|
table.capacity = capacity;
|
|
2154
1828
|
table.version += 1;
|
|
2155
1829
|
}
|
|
1830
|
+
function markTableMembership(table, row, epoch) {
|
|
1831
|
+
table.membership[Math.floor(row / PROJECTION_BLOCK_SIZE)] = epoch;
|
|
1832
|
+
}
|
|
2156
1833
|
|
|
2157
1834
|
// src/storage/archetype-graph.ts
|
|
2158
1835
|
function createArchetypeGraph(shared = false) {
|
|
@@ -2161,6 +1838,8 @@ function createArchetypeGraph(shared = false) {
|
|
|
2161
1838
|
dedupByKey: /* @__PURE__ */ new Map(),
|
|
2162
1839
|
generation: 0,
|
|
2163
1840
|
tables: [],
|
|
1841
|
+
activeTables: /* @__PURE__ */ new Set(),
|
|
1842
|
+
activeTablesByComponent: /* @__PURE__ */ new Map(),
|
|
2164
1843
|
tableDedupByKey: /* @__PURE__ */ new Map(),
|
|
2165
1844
|
tableGeneration: 0,
|
|
2166
1845
|
sparseTags: /* @__PURE__ */ new Map(),
|
|
@@ -2179,7 +1858,17 @@ function getOrCreateTable(graph, components) {
|
|
|
2179
1858
|
const key = tableKey(tableComponents.map((component) => componentId(component)));
|
|
2180
1859
|
const existingId = graph.tableDedupByKey.get(key);
|
|
2181
1860
|
if (existingId !== void 0) return getTable(graph, existingId);
|
|
2182
|
-
const
|
|
1861
|
+
const directories = [graph.activeTables];
|
|
1862
|
+
for (const component of tableComponents) {
|
|
1863
|
+
const id = componentId(component);
|
|
1864
|
+
let directory = graph.activeTablesByComponent.get(id);
|
|
1865
|
+
if (directory === void 0) {
|
|
1866
|
+
directory = /* @__PURE__ */ new Set();
|
|
1867
|
+
graph.activeTablesByComponent.set(id, directory);
|
|
1868
|
+
}
|
|
1869
|
+
directories.push(directory);
|
|
1870
|
+
}
|
|
1871
|
+
const table = createTable(tableComponents, graph.tables.length, graph.shared, directories);
|
|
2183
1872
|
graph.tables.push(table);
|
|
2184
1873
|
graph.tableDedupByKey.set(key, table.id);
|
|
2185
1874
|
graph.tableGeneration += 1;
|
|
@@ -2215,164 +1904,523 @@ function getAddEdge(graph, src, componentId2, component) {
|
|
|
2215
1904
|
return target2;
|
|
2216
1905
|
}
|
|
2217
1906
|
}
|
|
2218
|
-
const newIds = [...src.components.map((c) => componentId(c)), componentId2];
|
|
2219
|
-
const newComponents = [...src.components, component];
|
|
2220
|
-
const target = getOrCreateArchetype(graph, newIds, newComponents);
|
|
2221
|
-
src.addEdges.set(componentId2, target.id);
|
|
2222
|
-
return target;
|
|
1907
|
+
const newIds = [...src.components.map((c) => componentId(c)), componentId2];
|
|
1908
|
+
const newComponents = [...src.components, component];
|
|
1909
|
+
const target = getOrCreateArchetype(graph, newIds, newComponents);
|
|
1910
|
+
src.addEdges.set(componentId2, target.id);
|
|
1911
|
+
return target;
|
|
1912
|
+
}
|
|
1913
|
+
function getRemoveEdge(graph, src, componentId2) {
|
|
1914
|
+
const cached = src.removeEdges.get(componentId2);
|
|
1915
|
+
if (cached !== void 0) {
|
|
1916
|
+
const target2 = graph.archetypes[cached];
|
|
1917
|
+
if (target2) {
|
|
1918
|
+
return target2;
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
const newIds = src.components.map((c) => componentId(c)).filter((id) => id !== componentId2);
|
|
1922
|
+
const newComponents = src.components.filter((c) => componentId(c) !== componentId2);
|
|
1923
|
+
const target = getOrCreateArchetype(graph, newIds, newComponents);
|
|
1924
|
+
src.removeEdges.set(componentId2, target.id);
|
|
1925
|
+
return target;
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
// src/storage/change-detection.ts
|
|
1929
|
+
var PROJECTION_BLOCK_SIZE = 256;
|
|
1930
|
+
var INITIAL_SPARSE_CAPACITY = 64;
|
|
1931
|
+
function createComponentEpochColumns(capacity) {
|
|
1932
|
+
return {
|
|
1933
|
+
added: new Float64Array(capacity),
|
|
1934
|
+
changed: new Float64Array(capacity),
|
|
1935
|
+
blocks: new Float64Array(Math.ceil(capacity / PROJECTION_BLOCK_SIZE))
|
|
1936
|
+
};
|
|
1937
|
+
}
|
|
1938
|
+
function growComponentEpochColumns(columns, capacity) {
|
|
1939
|
+
const added = new Float64Array(capacity);
|
|
1940
|
+
const changed = new Float64Array(capacity);
|
|
1941
|
+
added.set(columns.added);
|
|
1942
|
+
changed.set(columns.changed);
|
|
1943
|
+
const blocks = new Float64Array(Math.ceil(capacity / PROJECTION_BLOCK_SIZE));
|
|
1944
|
+
blocks.set(columns.blocks);
|
|
1945
|
+
return { added, changed, blocks };
|
|
1946
|
+
}
|
|
1947
|
+
function copyComponentEpoch(source, sourceRow, target, targetRow) {
|
|
1948
|
+
target.added[targetRow] = source.added[sourceRow] ?? 0;
|
|
1949
|
+
target.changed[targetRow] = source.changed[sourceRow] ?? 0;
|
|
1950
|
+
const block = Math.floor(targetRow / PROJECTION_BLOCK_SIZE);
|
|
1951
|
+
target.blocks[block] = Math.max(target.blocks[block] ?? 0, target.changed[targetRow] ?? 0);
|
|
1952
|
+
}
|
|
1953
|
+
function createSparseTagSet(component) {
|
|
1954
|
+
const sparse = new Int32Array(INITIAL_SPARSE_CAPACITY);
|
|
1955
|
+
sparse.fill(-1);
|
|
1956
|
+
return {
|
|
1957
|
+
component,
|
|
1958
|
+
sparse,
|
|
1959
|
+
dense: new Uint32Array(INITIAL_SPARSE_CAPACITY),
|
|
1960
|
+
added: new Float64Array(INITIAL_SPARSE_CAPACITY),
|
|
1961
|
+
changed: new Float64Array(INITIAL_SPARSE_CAPACITY),
|
|
1962
|
+
size: 0
|
|
1963
|
+
};
|
|
1964
|
+
}
|
|
1965
|
+
function sparseTagIndex(set, entity) {
|
|
1966
|
+
const denseIndex = set.sparse[entityIndex(entity)] ?? -1;
|
|
1967
|
+
return denseIndex >= 0 && set.dense[denseIndex] === entity ? denseIndex : -1;
|
|
1968
|
+
}
|
|
1969
|
+
function insertSparseTag(set, entity, epoch) {
|
|
1970
|
+
const present = sparseTagIndex(set, entity);
|
|
1971
|
+
if (present >= 0) {
|
|
1972
|
+
set.changed[present] = epoch;
|
|
1973
|
+
return present;
|
|
1974
|
+
}
|
|
1975
|
+
growSparseSlots(set, entityIndex(entity) + 1);
|
|
1976
|
+
if (set.size === set.dense.length) growSparseDense(set, set.size + 1);
|
|
1977
|
+
const denseIndex = set.size;
|
|
1978
|
+
set.dense[denseIndex] = entity;
|
|
1979
|
+
set.added[denseIndex] = epoch;
|
|
1980
|
+
set.changed[denseIndex] = epoch;
|
|
1981
|
+
set.sparse[entityIndex(entity)] = denseIndex;
|
|
1982
|
+
set.size += 1;
|
|
1983
|
+
return denseIndex;
|
|
1984
|
+
}
|
|
1985
|
+
function removeSparseTag(set, entity) {
|
|
1986
|
+
const denseIndex = sparseTagIndex(set, entity);
|
|
1987
|
+
if (denseIndex < 0) return false;
|
|
1988
|
+
const lastIndex = set.size - 1;
|
|
1989
|
+
set.sparse[entityIndex(entity)] = -1;
|
|
1990
|
+
if (denseIndex !== lastIndex) {
|
|
1991
|
+
const movedEntity = set.dense[lastIndex];
|
|
1992
|
+
set.dense[denseIndex] = movedEntity;
|
|
1993
|
+
set.added[denseIndex] = set.added[lastIndex] ?? 0;
|
|
1994
|
+
set.changed[denseIndex] = set.changed[lastIndex] ?? 0;
|
|
1995
|
+
set.sparse[entityIndex(movedEntity)] = denseIndex;
|
|
1996
|
+
}
|
|
1997
|
+
set.size = lastIndex;
|
|
1998
|
+
return true;
|
|
1999
|
+
}
|
|
2000
|
+
function growSparseSlots(set, targetCapacity) {
|
|
2001
|
+
if (targetCapacity <= set.sparse.length) return;
|
|
2002
|
+
let capacity = set.sparse.length;
|
|
2003
|
+
while (capacity < targetCapacity) capacity *= 2;
|
|
2004
|
+
const sparse = new Int32Array(capacity);
|
|
2005
|
+
sparse.fill(-1);
|
|
2006
|
+
sparse.set(set.sparse);
|
|
2007
|
+
set.sparse = sparse;
|
|
2008
|
+
}
|
|
2009
|
+
function growSparseDense(set, targetCapacity) {
|
|
2010
|
+
let capacity = set.dense.length;
|
|
2011
|
+
while (capacity < targetCapacity) capacity *= 2;
|
|
2012
|
+
const dense = new Uint32Array(capacity);
|
|
2013
|
+
dense.set(set.dense);
|
|
2014
|
+
set.dense = dense;
|
|
2015
|
+
const added = new Float64Array(capacity);
|
|
2016
|
+
added.set(set.added);
|
|
2017
|
+
set.added = added;
|
|
2018
|
+
const changed = new Float64Array(capacity);
|
|
2019
|
+
changed.set(set.changed);
|
|
2020
|
+
set.changed = changed;
|
|
2021
|
+
}
|
|
2022
|
+
function readComponentChange(graph, location, entity, componentId2) {
|
|
2023
|
+
const sparseSet = graph.sparseTags.get(componentId2);
|
|
2024
|
+
if (sparseSet !== void 0) {
|
|
2025
|
+
const denseIndex = sparseTagIndex(sparseSet, entity);
|
|
2026
|
+
if (denseIndex < 0) return void 0;
|
|
2027
|
+
return {
|
|
2028
|
+
added: sparseSet.added[denseIndex] ?? 0,
|
|
2029
|
+
changed: sparseSet.changed[denseIndex] ?? 0
|
|
2030
|
+
};
|
|
2031
|
+
}
|
|
2032
|
+
const archetype = graph.archetypes[location.archetypeId];
|
|
2033
|
+
if (archetype === void 0) return void 0;
|
|
2034
|
+
const epochs = graph.tables[archetype.tableId]?.storage.get(componentId2)?.epochs;
|
|
2035
|
+
if (epochs === void 0) return void 0;
|
|
2036
|
+
const tableRow2 = archetype.rows[location.archetypeRow] ?? -1;
|
|
2037
|
+
return {
|
|
2038
|
+
added: epochs.added[tableRow2] ?? 0,
|
|
2039
|
+
changed: epochs.changed[tableRow2] ?? 0
|
|
2040
|
+
};
|
|
2041
|
+
}
|
|
2042
|
+
function markComponentsAdded(graph, location, entity, componentIds, epoch) {
|
|
2043
|
+
const archetype = graph.archetypes[location.archetypeId];
|
|
2044
|
+
const table = archetype === void 0 ? void 0 : graph.tables[archetype.tableId];
|
|
2045
|
+
const tableRow2 = archetype?.rows[location.archetypeRow] ?? -1;
|
|
2046
|
+
for (const componentId2 of componentIds) {
|
|
2047
|
+
const component = archetype?.components.find(
|
|
2048
|
+
(candidate) => componentId(candidate) === componentId2
|
|
2049
|
+
);
|
|
2050
|
+
if (component?.storage === "sparse") {
|
|
2051
|
+
insertSparseTag(getOrCreateSparseTagSet(graph, component), entity, epoch);
|
|
2052
|
+
continue;
|
|
2053
|
+
}
|
|
2054
|
+
const epochs = table?.storage.get(componentId2)?.epochs;
|
|
2055
|
+
if (epochs === void 0) continue;
|
|
2056
|
+
epochs.added[tableRow2] = epoch;
|
|
2057
|
+
publishComponentRange(epochs, tableRow2, 1, epoch);
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
function markComponentChanged(graph, location, entity, componentId2, epoch) {
|
|
2061
|
+
const sparseSet = graph.sparseTags.get(componentId2);
|
|
2062
|
+
if (sparseSet !== void 0) {
|
|
2063
|
+
const denseIndex = sparseTagIndex(sparseSet, entity);
|
|
2064
|
+
if (denseIndex >= 0) sparseSet.changed[denseIndex] = epoch();
|
|
2065
|
+
return;
|
|
2066
|
+
}
|
|
2067
|
+
const archetype = graph.archetypes[location.archetypeId];
|
|
2068
|
+
if (archetype === void 0) return;
|
|
2069
|
+
const epochs = graph.tables[archetype.tableId]?.storage.get(componentId2)?.epochs;
|
|
2070
|
+
if (epochs === void 0) return;
|
|
2071
|
+
const tableRow2 = archetype.rows[location.archetypeRow] ?? -1;
|
|
2072
|
+
publishComponentRange(epochs, tableRow2, 1, epoch());
|
|
2073
|
+
}
|
|
2074
|
+
function publishComponentRange(columns, start, count, epoch) {
|
|
2075
|
+
if (count === 0) return;
|
|
2076
|
+
if (count === 1) {
|
|
2077
|
+
columns.changed[start] = epoch;
|
|
2078
|
+
columns.blocks[Math.floor(start / PROJECTION_BLOCK_SIZE)] = epoch;
|
|
2079
|
+
return;
|
|
2080
|
+
}
|
|
2081
|
+
columns.changed.fill(epoch, start, start + count);
|
|
2082
|
+
columns.blocks.fill(
|
|
2083
|
+
epoch,
|
|
2084
|
+
Math.floor(start / PROJECTION_BLOCK_SIZE),
|
|
2085
|
+
Math.ceil((start + count) / PROJECTION_BLOCK_SIZE)
|
|
2086
|
+
);
|
|
2087
|
+
}
|
|
2088
|
+
var SIZE_CLASSES = Object.freeze([
|
|
2089
|
+
16,
|
|
2090
|
+
64,
|
|
2091
|
+
256,
|
|
2092
|
+
1024,
|
|
2093
|
+
4096,
|
|
2094
|
+
16384,
|
|
2095
|
+
65536,
|
|
2096
|
+
262144
|
|
2097
|
+
]);
|
|
2098
|
+
var HAS_TRANSFER2 = typeof ArrayBuffer.prototype.transfer === "function";
|
|
2099
|
+
function bucketIndex(byteLength) {
|
|
2100
|
+
if (byteLength === 0) return -1;
|
|
2101
|
+
for (let i = 0; i < SIZE_CLASSES.length; i++) {
|
|
2102
|
+
const b = SIZE_CLASSES[i];
|
|
2103
|
+
if (b !== void 0 && byteLength <= b) return i;
|
|
2104
|
+
}
|
|
2105
|
+
return SIZE_CLASSES.length;
|
|
2106
|
+
}
|
|
2107
|
+
var BufferPool = class {
|
|
2108
|
+
slots = /* @__PURE__ */ new Map();
|
|
2109
|
+
/**
|
|
2110
|
+
* Per-bucket free-lists: `freeBuckets[i]` is a LIFO stack of slot ids
|
|
2111
|
+
* whose backing buffer is parked on bucket `i`. Released slots stay on
|
|
2112
|
+
* their original bucket's free-list - v1 never moves a slot between
|
|
2113
|
+
* buckets on release (D-7 no trim).
|
|
2114
|
+
*/
|
|
2115
|
+
freeBuckets = SIZE_CLASSES.map(() => []);
|
|
2116
|
+
nextId = 1;
|
|
2117
|
+
/**
|
|
2118
|
+
* Allocate a managed buffer slot of at least `byteLength` bytes.
|
|
2119
|
+
*
|
|
2120
|
+
* Routes:
|
|
2121
|
+
* - invalid byteLength -> structured out-of-bounds error.
|
|
2122
|
+
* - byteLength == 0 -> ok({ id, view: zero-length Uint8Array }) (no bucket).
|
|
2123
|
+
* - byteLength <= 262144 -> ok({ id, view }), bucket = smallest >= byteLength.
|
|
2124
|
+
* - byteLength > 262144 -> a dedicated allocation; allocation failure is structured.
|
|
2125
|
+
*
|
|
2126
|
+
* D-5: size classes are radix-4 (16 / 64 / 256 / 1K / 4K / 16K / 64K / 256K).
|
|
2127
|
+
* Free-list pop reuses the most recently released slot id at the same bucket;
|
|
2128
|
+
* miss falls through to a fresh allocation.
|
|
2129
|
+
*/
|
|
2130
|
+
alloc(byteLength) {
|
|
2131
|
+
if (byteLength === 0) {
|
|
2132
|
+
const id2 = this.nextId++;
|
|
2133
|
+
const buffer2 = new ArrayBuffer(0);
|
|
2134
|
+
const view2 = new Uint8Array(buffer2);
|
|
2135
|
+
this.slots.set(id2, { sizeClassIdx: -1, buffer: buffer2, view: view2, byteLength: 0, live: true });
|
|
2136
|
+
return ok({ id: id2, view: view2 });
|
|
2137
|
+
}
|
|
2138
|
+
if (!Number.isSafeInteger(byteLength) || byteLength < 0) {
|
|
2139
|
+
return err(new ManagedBufferOutOfBoundsError(byteLength, 0));
|
|
2140
|
+
}
|
|
2141
|
+
const idx = bucketIndex(byteLength);
|
|
2142
|
+
if (idx === SIZE_CLASSES.length) {
|
|
2143
|
+
try {
|
|
2144
|
+
const buffer2 = new ArrayBuffer(byteLength);
|
|
2145
|
+
const view2 = new Uint8Array(buffer2);
|
|
2146
|
+
const id2 = this.nextId++;
|
|
2147
|
+
this.slots.set(id2, { sizeClassIdx: idx, buffer: buffer2, view: view2, byteLength, live: true });
|
|
2148
|
+
return ok({ id: id2, view: view2 });
|
|
2149
|
+
} catch {
|
|
2150
|
+
return err(new ManagedBufferOutOfBoundsError(byteLength, 0));
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
const bucketBytes = SIZE_CLASSES[idx];
|
|
2154
|
+
if (bucketBytes === void 0) {
|
|
2155
|
+
return err(new ManagedBufferOutOfBoundsError(byteLength, 0));
|
|
2156
|
+
}
|
|
2157
|
+
const free = this.freeBuckets[idx];
|
|
2158
|
+
if (free !== void 0 && free.length > 0) {
|
|
2159
|
+
const id2 = free.pop();
|
|
2160
|
+
const slot = this.slots.get(id2);
|
|
2161
|
+
if (slot === void 0) {
|
|
2162
|
+
return err(new ManagedBufferOutOfBoundsError(byteLength, 0));
|
|
2163
|
+
}
|
|
2164
|
+
slot.byteLength = byteLength;
|
|
2165
|
+
slot.view = new Uint8Array(slot.buffer, 0, byteLength);
|
|
2166
|
+
slot.live = true;
|
|
2167
|
+
new Uint8Array(slot.buffer).fill(0);
|
|
2168
|
+
return ok({ id: id2, view: slot.view });
|
|
2169
|
+
}
|
|
2170
|
+
const id = this.nextId++;
|
|
2171
|
+
const buffer = new ArrayBuffer(bucketBytes);
|
|
2172
|
+
const view = new Uint8Array(buffer, 0, byteLength);
|
|
2173
|
+
this.slots.set(id, {
|
|
2174
|
+
sizeClassIdx: idx,
|
|
2175
|
+
buffer,
|
|
2176
|
+
view,
|
|
2177
|
+
byteLength,
|
|
2178
|
+
live: true
|
|
2179
|
+
});
|
|
2180
|
+
return ok({ id, view });
|
|
2181
|
+
}
|
|
2182
|
+
/**
|
|
2183
|
+
* Grow slot `id` to `newBytes`. Returns the post-grow Uint8Array view.
|
|
2184
|
+
*
|
|
2185
|
+
* Routes (D-6 / D-7):
|
|
2186
|
+
* - newBytes < current -> err(managed-buffer-shrink-not-supported).
|
|
2187
|
+
* - newBytes == current -> ok(current view) (no-op, identity preserved).
|
|
2188
|
+
* - newBytes > current && same bucket -> ok(re-sliced view) (no transfer).
|
|
2189
|
+
* - newBytes > current && cross bucket -> ok(new view) backed by a fresh
|
|
2190
|
+
* bucket buffer; the prior ArrayBuffer is detached via transfer (ES2024)
|
|
2191
|
+
* or replaced via allocate-and-copy fallback. Old `view`s captured by
|
|
2192
|
+
* the caller become detached / orphaned - callers must use `pool.view(id)`
|
|
2193
|
+
* after grow to read the refreshed view (the `release` loop refreshes
|
|
2194
|
+
* automatically).
|
|
2195
|
+
* - newBytes beyond the last pooled class -> dedicated allocation.
|
|
2196
|
+
*/
|
|
2197
|
+
grow(id, newBytes) {
|
|
2198
|
+
const slot = this.slots.get(id);
|
|
2199
|
+
if (slot === void 0) {
|
|
2200
|
+
return err(new ManagedBufferOutOfBoundsError(newBytes, 0));
|
|
2201
|
+
}
|
|
2202
|
+
if (newBytes < slot.byteLength) {
|
|
2203
|
+
return err(new ManagedBufferShrinkNotSupportedError(newBytes, slot.byteLength));
|
|
2204
|
+
}
|
|
2205
|
+
if (newBytes === slot.byteLength) {
|
|
2206
|
+
return ok(slot.view);
|
|
2207
|
+
}
|
|
2208
|
+
if (!Number.isSafeInteger(newBytes) || newBytes < 0) {
|
|
2209
|
+
return err(new ManagedBufferOutOfBoundsError(newBytes, slot.buffer.byteLength));
|
|
2210
|
+
}
|
|
2211
|
+
const newIdx = bucketIndex(newBytes);
|
|
2212
|
+
if (newBytes <= slot.buffer.byteLength) {
|
|
2213
|
+
slot.byteLength = newBytes;
|
|
2214
|
+
slot.view = new Uint8Array(slot.buffer, 0, newBytes);
|
|
2215
|
+
return ok(slot.view);
|
|
2216
|
+
}
|
|
2217
|
+
const newBucketBytes = SIZE_CLASSES[newIdx] ?? Math.max(newBytes, slot.buffer.byteLength * 2);
|
|
2218
|
+
const oldByteLength = slot.byteLength;
|
|
2219
|
+
let nextBuffer;
|
|
2220
|
+
try {
|
|
2221
|
+
if (HAS_TRANSFER2) {
|
|
2222
|
+
nextBuffer = slot.buffer.transfer(newBucketBytes);
|
|
2223
|
+
} else {
|
|
2224
|
+
nextBuffer = new ArrayBuffer(newBucketBytes);
|
|
2225
|
+
new Uint8Array(nextBuffer).set(new Uint8Array(slot.buffer, 0, oldByteLength));
|
|
2226
|
+
}
|
|
2227
|
+
} catch {
|
|
2228
|
+
return err(new ManagedBufferOutOfBoundsError(newBytes, slot.buffer.byteLength));
|
|
2229
|
+
}
|
|
2230
|
+
slot.sizeClassIdx = newIdx;
|
|
2231
|
+
slot.buffer = nextBuffer;
|
|
2232
|
+
slot.byteLength = newBytes;
|
|
2233
|
+
slot.view = new Uint8Array(nextBuffer, 0, newBytes);
|
|
2234
|
+
return ok(slot.view);
|
|
2235
|
+
}
|
|
2236
|
+
/**
|
|
2237
|
+
* Release slot `id` back to its bucket's free-list. Releasing an unknown id
|
|
2238
|
+
* is a no-op - World's release loop drives this and idempotency keeps the
|
|
2239
|
+
* despawn chain free of bookkeeping noise. Bucket free-lists are NEVER
|
|
2240
|
+
* trimmed in v1 (D-7 no trim).
|
|
2241
|
+
*/
|
|
2242
|
+
release(id) {
|
|
2243
|
+
const slot = this.slots.get(id);
|
|
2244
|
+
if (slot === void 0) return ok(void 0);
|
|
2245
|
+
if (!slot.live) return ok(void 0);
|
|
2246
|
+
slot.live = false;
|
|
2247
|
+
if (slot.sizeClassIdx >= 0 && slot.sizeClassIdx < SIZE_CLASSES.length) {
|
|
2248
|
+
const bucket = this.freeBuckets[slot.sizeClassIdx];
|
|
2249
|
+
if (bucket !== void 0) bucket.push(id);
|
|
2250
|
+
} else {
|
|
2251
|
+
this.slots.delete(id);
|
|
2252
|
+
}
|
|
2253
|
+
return ok(void 0);
|
|
2254
|
+
}
|
|
2255
|
+
/**
|
|
2256
|
+
* Return the live view for slot `id`. Used by World after `grow` to
|
|
2257
|
+
* refresh the column's stored view reference. Returns a zero-length view
|
|
2258
|
+
* for unknown / released ids so callers never crash on use-after-release.
|
|
2259
|
+
*/
|
|
2260
|
+
view(id) {
|
|
2261
|
+
const slot = this.slots.get(id);
|
|
2262
|
+
if (slot === void 0 || !slot.live) return new Uint8Array(0);
|
|
2263
|
+
return slot.view;
|
|
2264
|
+
}
|
|
2265
|
+
/**
|
|
2266
|
+
* Return the bucket-rounded byte capacity for slot `id` --- i.e.
|
|
2267
|
+
* `SIZE_CLASSES[slot.sizeClassIdx]`. Used by managed-buffer view callers
|
|
2268
|
+
* (D-4 no-cache: re-queried per accessor). Returns `0` for the zero-length
|
|
2269
|
+
* (`alloc(0)`) slot; returns `0` for unknown / released ids (mirrors
|
|
2270
|
+
* `view(id)` use-after-release semantics).
|
|
2271
|
+
*/
|
|
2272
|
+
byteCapacity(id) {
|
|
2273
|
+
const slot = this.slots.get(id);
|
|
2274
|
+
if (slot === void 0 || !slot.live) return 0;
|
|
2275
|
+
if (slot.sizeClassIdx < 0) return 0;
|
|
2276
|
+
return slot.buffer.byteLength;
|
|
2277
|
+
}
|
|
2278
|
+
/**
|
|
2279
|
+
* Reset the logical byteLength of slot `id` to `newByteLength` while
|
|
2280
|
+
* keeping the same bucket allocation (no transfer, no release). Only
|
|
2281
|
+
* legal when `newByteLength <= bucketBytes`; the slot's bucket index
|
|
2282
|
+
* does not move (D-7 v1 forbids cross-bucket shrink). Used by managed-
|
|
2283
|
+
* buffer clear paths so the slot retains its `byteCapacity` while the
|
|
2284
|
+
* live view becomes zero-length. Bytes past the new logical length are
|
|
2285
|
+
* zero-filled defensively.
|
|
2286
|
+
*
|
|
2287
|
+
* Returns `err(ManagedBufferShrinkNotSupportedError)` when called on a
|
|
2288
|
+
* `sizeClassIdx === -1` slot (alloc(0)) with `newByteLength > 0`, or
|
|
2289
|
+
* when `newByteLength` exceeds the bucket capacity.
|
|
2290
|
+
*/
|
|
2291
|
+
setLogicalLength(id, newByteLength) {
|
|
2292
|
+
const slot = this.slots.get(id);
|
|
2293
|
+
if (slot === void 0) {
|
|
2294
|
+
return err(new ManagedBufferOutOfBoundsError(newByteLength, 0));
|
|
2295
|
+
}
|
|
2296
|
+
if (slot.sizeClassIdx < 0) {
|
|
2297
|
+
if (newByteLength === 0) {
|
|
2298
|
+
slot.byteLength = 0;
|
|
2299
|
+
slot.view = new Uint8Array(slot.buffer);
|
|
2300
|
+
return ok(slot.view);
|
|
2301
|
+
}
|
|
2302
|
+
return err(new ManagedBufferOutOfBoundsError(newByteLength, 0));
|
|
2303
|
+
}
|
|
2304
|
+
const bucketBytes = slot.buffer.byteLength;
|
|
2305
|
+
if (newByteLength > bucketBytes) {
|
|
2306
|
+
return err(new ManagedBufferOutOfBoundsError(newByteLength, bucketBytes));
|
|
2307
|
+
}
|
|
2308
|
+
if (newByteLength < slot.byteLength) {
|
|
2309
|
+
new Uint8Array(slot.buffer, newByteLength, slot.byteLength - newByteLength).fill(0);
|
|
2310
|
+
}
|
|
2311
|
+
slot.byteLength = newByteLength;
|
|
2312
|
+
slot.view = new Uint8Array(slot.buffer, 0, newByteLength);
|
|
2313
|
+
return ok(slot.view);
|
|
2314
|
+
}
|
|
2315
|
+
/** @internal Diagnostic count of live slots. Exposed for tests + inspector. */
|
|
2316
|
+
_liveCount() {
|
|
2317
|
+
let n = 0;
|
|
2318
|
+
for (const s of this.slots.values()) if (s.live) n += 1;
|
|
2319
|
+
return n;
|
|
2320
|
+
}
|
|
2321
|
+
};
|
|
2322
|
+
|
|
2323
|
+
// src/component-default-fallback.ts
|
|
2324
|
+
function typeDefault(fieldType) {
|
|
2325
|
+
if (fieldType === "bool") return false;
|
|
2326
|
+
if (fieldType === "entity") return ENTITY_NULL_RAW;
|
|
2327
|
+
if (fieldType === "array<entity>") return [];
|
|
2328
|
+
return 0;
|
|
2329
|
+
}
|
|
2330
|
+
function fillComponentDefaults(token, raw) {
|
|
2331
|
+
const schema = componentSchema(token);
|
|
2332
|
+
const layer2 = componentDefinition(token).defaults;
|
|
2333
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
2334
|
+
const rawObj = raw ?? void 0;
|
|
2335
|
+
for (const fieldName of Object.keys(schema)) {
|
|
2336
|
+
const fieldType = schema[fieldName];
|
|
2337
|
+
if (fieldType === void 0) continue;
|
|
2338
|
+
if (rawObj !== void 0 && fieldName in rawObj) {
|
|
2339
|
+
out[fieldName] = rawObj[fieldName];
|
|
2340
|
+
continue;
|
|
2341
|
+
}
|
|
2342
|
+
if (layer2 !== void 0 && fieldName in layer2) {
|
|
2343
|
+
out[fieldName] = layer2[fieldName];
|
|
2344
|
+
continue;
|
|
2345
|
+
}
|
|
2346
|
+
out[fieldName] = typeDefault(fieldType);
|
|
2347
|
+
}
|
|
2348
|
+
return out;
|
|
2223
2349
|
}
|
|
2224
|
-
function
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2350
|
+
function validateComponentDataKeys(token, raw) {
|
|
2351
|
+
if (raw === void 0) return null;
|
|
2352
|
+
const schema = componentSchema(token);
|
|
2353
|
+
const rawObj = raw;
|
|
2354
|
+
for (const fieldName of Object.keys(rawObj)) {
|
|
2355
|
+
if (!(fieldName in schema)) {
|
|
2356
|
+
return new SpawnDataUnknownFieldError(token.name, fieldName, Object.keys(schema));
|
|
2230
2357
|
}
|
|
2231
2358
|
}
|
|
2232
|
-
|
|
2233
|
-
const newComponents = src.components.filter((c) => componentId(c) !== componentId2);
|
|
2234
|
-
const target = getOrCreateArchetype(graph, newIds, newComponents);
|
|
2235
|
-
src.removeEdges.set(componentId2, target.id);
|
|
2236
|
-
return target;
|
|
2359
|
+
return null;
|
|
2237
2360
|
}
|
|
2238
2361
|
|
|
2239
|
-
// src/
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
return { added: new Float64Array(capacity), changed: new Float64Array(capacity) };
|
|
2243
|
-
}
|
|
2244
|
-
function growComponentEpochColumns(columns, capacity) {
|
|
2245
|
-
const added = new Float64Array(capacity);
|
|
2246
|
-
const changed = new Float64Array(capacity);
|
|
2247
|
-
added.set(columns.added);
|
|
2248
|
-
changed.set(columns.changed);
|
|
2249
|
-
return { added, changed };
|
|
2250
|
-
}
|
|
2251
|
-
function copyComponentEpoch(source, sourceRow, target, targetRow) {
|
|
2252
|
-
target.added[targetRow] = source.added[sourceRow] ?? 0;
|
|
2253
|
-
target.changed[targetRow] = source.changed[sourceRow] ?? 0;
|
|
2254
|
-
}
|
|
2255
|
-
function createSparseTagSet(component) {
|
|
2256
|
-
const sparse = new Int32Array(INITIAL_SPARSE_CAPACITY);
|
|
2257
|
-
sparse.fill(-1);
|
|
2258
|
-
return {
|
|
2259
|
-
component,
|
|
2260
|
-
sparse,
|
|
2261
|
-
dense: new Uint32Array(INITIAL_SPARSE_CAPACITY),
|
|
2262
|
-
added: new Float64Array(INITIAL_SPARSE_CAPACITY),
|
|
2263
|
-
changed: new Float64Array(INITIAL_SPARSE_CAPACITY),
|
|
2264
|
-
size: 0
|
|
2265
|
-
};
|
|
2266
|
-
}
|
|
2267
|
-
function sparseTagIndex(set, entity) {
|
|
2268
|
-
const denseIndex = set.sparse[entityIndex(entity)] ?? -1;
|
|
2269
|
-
return denseIndex >= 0 && set.dense[denseIndex] === entity ? denseIndex : -1;
|
|
2270
|
-
}
|
|
2271
|
-
function insertSparseTag(set, entity, epoch) {
|
|
2272
|
-
const present = sparseTagIndex(set, entity);
|
|
2273
|
-
if (present >= 0) {
|
|
2274
|
-
set.changed[present] = epoch;
|
|
2275
|
-
return present;
|
|
2276
|
-
}
|
|
2277
|
-
growSparseSlots(set, entityIndex(entity) + 1);
|
|
2278
|
-
if (set.size === set.dense.length) growSparseDense(set, set.size + 1);
|
|
2279
|
-
const denseIndex = set.size;
|
|
2280
|
-
set.dense[denseIndex] = entity;
|
|
2281
|
-
set.added[denseIndex] = epoch;
|
|
2282
|
-
set.changed[denseIndex] = epoch;
|
|
2283
|
-
set.sparse[entityIndex(entity)] = denseIndex;
|
|
2284
|
-
set.size += 1;
|
|
2285
|
-
return denseIndex;
|
|
2362
|
+
// src/component-value-validate.ts
|
|
2363
|
+
function isNumericHandle(v) {
|
|
2364
|
+
return typeof v === "number";
|
|
2286
2365
|
}
|
|
2287
|
-
function
|
|
2288
|
-
|
|
2289
|
-
if (denseIndex < 0) return false;
|
|
2290
|
-
const lastIndex = set.size - 1;
|
|
2291
|
-
set.sparse[entityIndex(entity)] = -1;
|
|
2292
|
-
if (denseIndex !== lastIndex) {
|
|
2293
|
-
const movedEntity = set.dense[lastIndex];
|
|
2294
|
-
set.dense[denseIndex] = movedEntity;
|
|
2295
|
-
set.added[denseIndex] = set.added[lastIndex] ?? 0;
|
|
2296
|
-
set.changed[denseIndex] = set.changed[lastIndex] ?? 0;
|
|
2297
|
-
set.sparse[entityIndex(movedEntity)] = denseIndex;
|
|
2298
|
-
}
|
|
2299
|
-
set.size = lastIndex;
|
|
2300
|
-
return true;
|
|
2366
|
+
function isSharedScalarType(fieldType) {
|
|
2367
|
+
return fieldType.startsWith("shared<") && fieldType.endsWith(">");
|
|
2301
2368
|
}
|
|
2302
|
-
function
|
|
2303
|
-
if (
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
sparse.fill(-1);
|
|
2308
|
-
sparse.set(set.sparse);
|
|
2309
|
-
set.sparse = sparse;
|
|
2369
|
+
function isSharedArrayType(fieldType) {
|
|
2370
|
+
if (!fieldType.startsWith("array<") || !fieldType.endsWith(">")) return false;
|
|
2371
|
+
const inner = fieldType.slice(6, -1);
|
|
2372
|
+
const head = inner.indexOf(",") === -1 ? inner : inner.slice(0, inner.indexOf(",")).trim();
|
|
2373
|
+
return head.startsWith("shared<") && head.endsWith(">");
|
|
2310
2374
|
}
|
|
2311
|
-
function
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
const dense = new Uint32Array(capacity);
|
|
2315
|
-
dense.set(set.dense);
|
|
2316
|
-
set.dense = dense;
|
|
2317
|
-
const added = new Float64Array(capacity);
|
|
2318
|
-
added.set(set.added);
|
|
2319
|
-
set.added = added;
|
|
2320
|
-
const changed = new Float64Array(capacity);
|
|
2321
|
-
changed.set(set.changed);
|
|
2322
|
-
set.changed = changed;
|
|
2375
|
+
function isArrayPayload(value) {
|
|
2376
|
+
if (Array.isArray(value)) return true;
|
|
2377
|
+
return ArrayBuffer.isView(value) && typeof value.length === "number";
|
|
2323
2378
|
}
|
|
2324
|
-
function
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2379
|
+
function validateManagedArrayValues(token, raw) {
|
|
2380
|
+
if (raw === void 0) return null;
|
|
2381
|
+
const fields = componentDefinition(token).fields;
|
|
2382
|
+
const rawObj = raw;
|
|
2383
|
+
for (const fieldName of Object.keys(rawObj)) {
|
|
2384
|
+
const reflection = fields[fieldName];
|
|
2385
|
+
if (reflection?.arrayMeta === void 0) continue;
|
|
2386
|
+
const value = rawObj[fieldName];
|
|
2387
|
+
if (value === 0) continue;
|
|
2388
|
+
if (value === void 0 || value === null || isArrayPayload(value)) continue;
|
|
2389
|
+
return new ManagedArrayInvalidValueError(token.name, fieldName, reflection.type, value);
|
|
2333
2390
|
}
|
|
2334
|
-
|
|
2335
|
-
if (archetype === void 0) return void 0;
|
|
2336
|
-
const epochs = graph.tables[archetype.tableId]?.storage.get(componentId2)?.epochs;
|
|
2337
|
-
if (epochs === void 0) return void 0;
|
|
2338
|
-
const tableRow2 = archetype.rows[location.archetypeRow] ?? -1;
|
|
2339
|
-
return {
|
|
2340
|
-
added: epochs.added[tableRow2] ?? 0,
|
|
2341
|
-
changed: epochs.changed[tableRow2] ?? 0
|
|
2342
|
-
};
|
|
2391
|
+
return null;
|
|
2343
2392
|
}
|
|
2344
|
-
function
|
|
2345
|
-
|
|
2346
|
-
const
|
|
2347
|
-
const
|
|
2348
|
-
for (const
|
|
2349
|
-
const
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
if (
|
|
2353
|
-
|
|
2354
|
-
|
|
2393
|
+
function validateSharedFieldValues(token, raw) {
|
|
2394
|
+
if (raw === void 0) return null;
|
|
2395
|
+
const schema = componentSchema(token);
|
|
2396
|
+
const rawObj = raw;
|
|
2397
|
+
for (const fieldName of Object.keys(rawObj)) {
|
|
2398
|
+
const fieldType = schema[fieldName];
|
|
2399
|
+
if (fieldType === void 0) continue;
|
|
2400
|
+
const value = rawObj[fieldName];
|
|
2401
|
+
if (value === void 0 || value === null) continue;
|
|
2402
|
+
if (isSharedScalarType(fieldType)) {
|
|
2403
|
+
if (!isNumericHandle(value)) {
|
|
2404
|
+
return new SharedFieldInvalidValueError(token.name, fieldName, fieldType, value);
|
|
2405
|
+
}
|
|
2406
|
+
} else if (isSharedArrayType(fieldType)) {
|
|
2407
|
+
if (!Array.isArray(value)) continue;
|
|
2408
|
+
for (let i = 0; i < value.length; i++) {
|
|
2409
|
+
const el = value[i];
|
|
2410
|
+
if (el === void 0 || el === null) continue;
|
|
2411
|
+
if (!isNumericHandle(el)) {
|
|
2412
|
+
return new SharedFieldInvalidValueError(token.name, fieldName, fieldType, el, i);
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2355
2415
|
}
|
|
2356
|
-
const epochs = table?.storage.get(componentId2)?.epochs;
|
|
2357
|
-
if (epochs === void 0) continue;
|
|
2358
|
-
epochs.added[tableRow2] = epoch;
|
|
2359
|
-
epochs.changed[tableRow2] = epoch;
|
|
2360
|
-
}
|
|
2361
|
-
}
|
|
2362
|
-
function markComponentChanged(graph, location, entity, componentId2, epoch) {
|
|
2363
|
-
const sparseSet = graph.sparseTags.get(componentId2);
|
|
2364
|
-
if (sparseSet !== void 0) {
|
|
2365
|
-
const denseIndex = sparseTagIndex(sparseSet, entity);
|
|
2366
|
-
if (denseIndex >= 0) sparseSet.changed[denseIndex] = epoch();
|
|
2367
|
-
return;
|
|
2368
2416
|
}
|
|
2369
|
-
|
|
2370
|
-
if (archetype === void 0) return;
|
|
2371
|
-
const epochs = graph.tables[archetype.tableId]?.storage.get(componentId2)?.epochs;
|
|
2372
|
-
if (epochs === void 0) return;
|
|
2373
|
-
const tableRow2 = archetype.rows[location.archetypeRow] ?? -1;
|
|
2374
|
-
epochs.changed[tableRow2] = epoch();
|
|
2417
|
+
return null;
|
|
2375
2418
|
}
|
|
2419
|
+
|
|
2420
|
+
// src/internal.ts
|
|
2421
|
+
var DERIVED_WRITER = /* @__PURE__ */ Symbol.for(
|
|
2422
|
+
"forgeax.ecs.query.derivedWriter"
|
|
2423
|
+
);
|
|
2376
2424
|
function createDerivedRangeWriter(world, component, source) {
|
|
2377
2425
|
let boundEpoch = -1;
|
|
2378
2426
|
let bindingTables = [];
|
|
@@ -2686,14 +2734,11 @@ var QueryRowFacade = class _QueryRowFacade {
|
|
|
2686
2734
|
return new _QueryRowFacade(this.world).bind(this.entity, this.archetype);
|
|
2687
2735
|
}
|
|
2688
2736
|
has(component) {
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
) === true;
|
|
2737
|
+
const id = componentId(component);
|
|
2738
|
+
return this.archetype?.components.some((candidate) => componentId(candidate) === id) === true;
|
|
2692
2739
|
}
|
|
2693
2740
|
get(component) {
|
|
2694
|
-
if (!this.
|
|
2695
|
-
(candidate) => componentId(candidate) === componentId(component)
|
|
2696
|
-
)) {
|
|
2741
|
+
if (!this.has(component)) {
|
|
2697
2742
|
return void 0;
|
|
2698
2743
|
}
|
|
2699
2744
|
const result = this.world[worldInternal].getQueryRow(this.entity, component);
|
|
@@ -2805,8 +2850,9 @@ var ExecutableQuery = class {
|
|
|
2805
2850
|
const structureEpoch = this.world[worldInternal].getStructureEpoch();
|
|
2806
2851
|
const upperBound = this.world[worldInternal].getMutationEpoch();
|
|
2807
2852
|
const row = new QueryRowFacade(this.world);
|
|
2808
|
-
|
|
2809
|
-
let
|
|
2853
|
+
const unchanged = this.hasUnchangedInput();
|
|
2854
|
+
let archetypeIndex = unchanged ? this.matchedArchetypes.length : 0;
|
|
2855
|
+
let tableIndex = unchanged ? this.matchedTables.length : 0;
|
|
2810
2856
|
let rowIndex = 0;
|
|
2811
2857
|
let finished = false;
|
|
2812
2858
|
const close = (commit) => {
|
|
@@ -2874,7 +2920,7 @@ var ExecutableQuery = class {
|
|
|
2874
2920
|
at(entity) {
|
|
2875
2921
|
const archetype = this.world[worldInternal].getEntityArchetype(entity);
|
|
2876
2922
|
if (archetype === void 0 || !this.archetypeMatches(archetype)) return void 0;
|
|
2877
|
-
return new QueryRowFacade(this.world).bind(entity, archetype)
|
|
2923
|
+
return new QueryRowFacade(this.world).bind(entity, archetype);
|
|
2878
2924
|
}
|
|
2879
2925
|
spans() {
|
|
2880
2926
|
const reason = this.spanUnavailableReason();
|
|
@@ -2886,7 +2932,8 @@ var ExecutableQuery = class {
|
|
|
2886
2932
|
query.refreshMatches();
|
|
2887
2933
|
const structureEpoch = query.world[worldInternal].getStructureEpoch();
|
|
2888
2934
|
const upperBound = query.world[worldInternal].getMutationEpoch();
|
|
2889
|
-
|
|
2935
|
+
const filterIds = [...query.compiled.changedIds, ...query.compiled.addedIds];
|
|
2936
|
+
let tableIndex = query.hasUnchangedInput() ? query.matchedTables.length : 0;
|
|
2890
2937
|
let rowIndex = 0;
|
|
2891
2938
|
let finished = false;
|
|
2892
2939
|
const close = (commit) => {
|
|
@@ -2921,7 +2968,11 @@ var ExecutableQuery = class {
|
|
|
2921
2968
|
};
|
|
2922
2969
|
}
|
|
2923
2970
|
while (rowIndex < table.size && !query.denseChangeMatches(table, rowIndex, upperBound)) {
|
|
2924
|
-
rowIndex
|
|
2971
|
+
const block = Math.floor(rowIndex / PROJECTION_BLOCK_SIZE);
|
|
2972
|
+
const unchanged = filterIds.some(
|
|
2973
|
+
(id) => (table.storage.get(id)?.epochs.blocks[block] ?? 0) <= query.lastObservedEpoch
|
|
2974
|
+
);
|
|
2975
|
+
rowIndex = unchanged ? (block + 1) * PROJECTION_BLOCK_SIZE : rowIndex + 1;
|
|
2925
2976
|
}
|
|
2926
2977
|
if (rowIndex >= table.size) {
|
|
2927
2978
|
tableIndex += 1;
|
|
@@ -3009,6 +3060,10 @@ var ExecutableQuery = class {
|
|
|
3009
3060
|
}
|
|
3010
3061
|
};
|
|
3011
3062
|
}
|
|
3063
|
+
hasUnchangedInput() {
|
|
3064
|
+
const epochs = this.world[worldInternal].getComponentMutationEpochs();
|
|
3065
|
+
return this.compiled.changedIds.some((id) => (epochs[id] ?? 0) <= this.lastObservedEpoch);
|
|
3066
|
+
}
|
|
3012
3067
|
beginIteration() {
|
|
3013
3068
|
if (this.active) throw new QueryIterationActiveError();
|
|
3014
3069
|
this.active = true;
|
|
@@ -3925,7 +3980,6 @@ function runSchedule(schedule, world, selectedNames, commandsBySystem = /* @__PU
|
|
|
3925
3980
|
if (!completed || finalDrain) abortOutstandingCommands(commandsBySystem);
|
|
3926
3981
|
}
|
|
3927
3982
|
}
|
|
3928
|
-
var SHARED_REF_RELEASE_EVIDENCE_CAPACITY = 4096;
|
|
3929
3983
|
var SharedRefStore = class {
|
|
3930
3984
|
payloads = /* @__PURE__ */ new Map();
|
|
3931
3985
|
refcounts = /* @__PURE__ */ new Map();
|
|
@@ -3933,10 +3987,7 @@ var SharedRefStore = class {
|
|
|
3933
3987
|
internedByTarget = /* @__PURE__ */ new Map();
|
|
3934
3988
|
internedKeys = /* @__PURE__ */ new Map();
|
|
3935
3989
|
nextSlot = BUILTIN_BASE;
|
|
3936
|
-
mutationEpoch = 0;
|
|
3937
3990
|
/** Latest published mutation epoch per live handle; not an event journal. */
|
|
3938
|
-
mutationEpochs = /* @__PURE__ */ new Map();
|
|
3939
|
-
releaseJournal = [];
|
|
3940
3991
|
/**
|
|
3941
3992
|
* Generation table indexed by slot (D-6). Each entry tracks the current
|
|
3942
3993
|
* generation for the slot — written to during alloc (welded into the
|
|
@@ -4032,32 +4083,6 @@ var SharedRefStore = class {
|
|
|
4032
4083
|
* projections of shared payload data compare the monotonic epoch and
|
|
4033
4084
|
* explicitly refresh instead of rescanning every payload each frame.
|
|
4034
4085
|
*/
|
|
4035
|
-
markChanged(handle) {
|
|
4036
|
-
const resolved = this.resolve(handle);
|
|
4037
|
-
if (!resolved.ok) return resolved;
|
|
4038
|
-
if (this.mutationEpoch >= Number.MAX_SAFE_INTEGER) {
|
|
4039
|
-
throw new RangeError("SharedRefStore mutation epoch exhausted");
|
|
4040
|
-
}
|
|
4041
|
-
this.mutationEpoch += 1;
|
|
4042
|
-
this.mutationEpochs.set(unwrapHandle(handle), this.mutationEpoch);
|
|
4043
|
-
return ok(void 0);
|
|
4044
|
-
}
|
|
4045
|
-
/** Current upper bound for explicitly published payload mutations. */
|
|
4046
|
-
getMutationEpoch() {
|
|
4047
|
-
return this.mutationEpoch;
|
|
4048
|
-
}
|
|
4049
|
-
/** Read each live handle whose latest published mutation is after `cursor`. */
|
|
4050
|
-
readChangesSince(cursor) {
|
|
4051
|
-
const records = [];
|
|
4052
|
-
for (const [handle, epoch] of this.mutationEpochs) {
|
|
4053
|
-
if (epoch > cursor) records.push({ epoch, handle });
|
|
4054
|
-
}
|
|
4055
|
-
records.sort((left, right) => left.epoch - right.epoch || left.handle - right.handle);
|
|
4056
|
-
return {
|
|
4057
|
-
cursor: this.mutationEpoch,
|
|
4058
|
-
records
|
|
4059
|
-
};
|
|
4060
|
-
}
|
|
4061
4086
|
/**
|
|
4062
4087
|
* Increment the refcount of a live shared handle. Returns
|
|
4063
4088
|
* `err(shared-ref-released)` when the handle is not live - retain MUST
|
|
@@ -4121,7 +4146,6 @@ var SharedRefStore = class {
|
|
|
4121
4146
|
}
|
|
4122
4147
|
this.refcounts.delete(raw);
|
|
4123
4148
|
this.payloads.delete(raw);
|
|
4124
|
-
this.mutationEpochs.delete(raw);
|
|
4125
4149
|
const generation = storeGen + 1;
|
|
4126
4150
|
this._generations[slot] = generation;
|
|
4127
4151
|
if (!isRetiredSlot(generation)) {
|
|
@@ -4133,23 +4157,8 @@ var SharedRefStore = class {
|
|
|
4133
4157
|
generation,
|
|
4134
4158
|
evidence: "released"
|
|
4135
4159
|
});
|
|
4136
|
-
this.releaseJournal.push(
|
|
4137
|
-
Object.freeze({
|
|
4138
|
-
handle: raw,
|
|
4139
|
-
refcount: 0,
|
|
4140
|
-
generation,
|
|
4141
|
-
evidence: "released"
|
|
4142
|
-
})
|
|
4143
|
-
);
|
|
4144
|
-
if (this.releaseJournal.length > SHARED_REF_RELEASE_EVIDENCE_CAPACITY) {
|
|
4145
|
-
this.releaseJournal.shift();
|
|
4146
|
-
}
|
|
4147
4160
|
return ok(evidence);
|
|
4148
4161
|
}
|
|
4149
|
-
/** Read bounded release metadata; payload ownership remains with the caller. */
|
|
4150
|
-
readReleaseEvidence() {
|
|
4151
|
-
return this.releaseJournal;
|
|
4152
|
-
}
|
|
4153
4162
|
/**
|
|
4154
4163
|
* Return the current refcount for `handle`. Returns 0 for a released
|
|
4155
4164
|
* (or never-allocated) slot. Primarily a debug + tests entry point;
|
|
@@ -4165,47 +4174,6 @@ var SharedRefStore = class {
|
|
|
4165
4174
|
return this.payloads.size;
|
|
4166
4175
|
}
|
|
4167
4176
|
};
|
|
4168
|
-
|
|
4169
|
-
// src/storage/structural-evidence.ts
|
|
4170
|
-
var StructuralEvidenceRing = class {
|
|
4171
|
-
constructor(capacity = 1024) {
|
|
4172
|
-
this.capacity = capacity;
|
|
4173
|
-
if (!Number.isSafeInteger(capacity) || capacity <= 0) {
|
|
4174
|
-
throw new RangeError("StructuralEvidenceRing capacity must be a positive safe integer");
|
|
4175
|
-
}
|
|
4176
|
-
this.events = new Array(capacity);
|
|
4177
|
-
}
|
|
4178
|
-
capacity;
|
|
4179
|
-
events;
|
|
4180
|
-
nextSequence = 1;
|
|
4181
|
-
get cursor() {
|
|
4182
|
-
return this.nextSequence - 1;
|
|
4183
|
-
}
|
|
4184
|
-
append(input) {
|
|
4185
|
-
const sequence = this.nextSequence;
|
|
4186
|
-
this.nextSequence += 1;
|
|
4187
|
-
this.events[(sequence - 1) % this.capacity] = { ...input, sequence };
|
|
4188
|
-
return sequence;
|
|
4189
|
-
}
|
|
4190
|
-
readAfter(cursor) {
|
|
4191
|
-
const latest = this.cursor;
|
|
4192
|
-
if (!Number.isSafeInteger(cursor) || cursor < 0 || cursor > latest) {
|
|
4193
|
-
throw new RangeError(`StructuralEvidenceRing cursor ${cursor} is outside 0..${latest}`);
|
|
4194
|
-
}
|
|
4195
|
-
const oldestAvailable = Math.max(1, latest - this.capacity + 1);
|
|
4196
|
-
if (cursor < oldestAvailable - 1)
|
|
4197
|
-
return { status: "overflow", cursor: latest, oldestAvailable };
|
|
4198
|
-
const events = [];
|
|
4199
|
-
for (let sequence = cursor + 1; sequence <= latest; sequence += 1) {
|
|
4200
|
-
const event = this.events[(sequence - 1) % this.capacity];
|
|
4201
|
-
if (event === void 0 || event.sequence !== sequence) {
|
|
4202
|
-
return { status: "overflow", cursor: latest, oldestAvailable };
|
|
4203
|
-
}
|
|
4204
|
-
events.push(event);
|
|
4205
|
-
}
|
|
4206
|
-
return { status: "ok", cursor: latest, events };
|
|
4207
|
-
}
|
|
4208
|
-
};
|
|
4209
4177
|
var UniqueRefStore = class {
|
|
4210
4178
|
payloads = /* @__PURE__ */ new Map();
|
|
4211
4179
|
freeSlots = [];
|
|
@@ -5057,7 +5025,6 @@ var World = class {
|
|
|
5057
5025
|
/** Per-World shared-ref store; public read-only for direct handle operations. */
|
|
5058
5026
|
sharedRefs = new SharedRefStore();
|
|
5059
5027
|
componentMutationEpochs = [];
|
|
5060
|
-
structuralEvidence = new StructuralEvidenceRing();
|
|
5061
5028
|
/** One packed reverse index per relationship source component. */
|
|
5062
5029
|
relationshipIndexes = /* @__PURE__ */ new Map();
|
|
5063
5030
|
mutationEpoch = 0;
|
|
@@ -5112,7 +5079,6 @@ var World = class {
|
|
|
5112
5079
|
getSchedules: () => this.schedules,
|
|
5113
5080
|
getSharedRefs: () => this.sharedRefs,
|
|
5114
5081
|
getStructureEpoch: this.getStructureEpoch.bind(this),
|
|
5115
|
-
getStructuralEvidence: () => this.structuralEvidence,
|
|
5116
5082
|
lookupAlive: this.lookupAlive.bind(this),
|
|
5117
5083
|
markComponentChanged: this.internalmarkComponentChanged.bind(this),
|
|
5118
5084
|
markComponentRangeChanged: this.internalmarkComponentRangeChanged.bind(this),
|
|
@@ -5177,9 +5143,6 @@ var World = class {
|
|
|
5177
5143
|
}
|
|
5178
5144
|
return worldScheduleUsesComponent(this, component);
|
|
5179
5145
|
}
|
|
5180
|
-
recordStructuralEvidence(evidence) {
|
|
5181
|
-
this.structuralEvidence.append(evidence);
|
|
5182
|
-
}
|
|
5183
5146
|
/** Resolve current logical identity for a packed entity handle. */
|
|
5184
5147
|
internalgetEntityArchetype(entity) {
|
|
5185
5148
|
const record = this.records[entityIndex(entity)];
|
|
@@ -5208,7 +5171,7 @@ var World = class {
|
|
|
5208
5171
|
if (epochs === void 0) {
|
|
5209
5172
|
throw new Error(`Derived component ${componentId2} is not in the table.`);
|
|
5210
5173
|
}
|
|
5211
|
-
epochs
|
|
5174
|
+
publishComponentRange(epochs, rowStart, rowCount, epoch);
|
|
5212
5175
|
this.componentMutationEpochs[componentId2] = epoch;
|
|
5213
5176
|
}
|
|
5214
5177
|
/** Record one successful structural mutation. */
|
|
@@ -5247,7 +5210,7 @@ var World = class {
|
|
|
5247
5210
|
const epochs = table.storage.get(componentId2)?.epochs;
|
|
5248
5211
|
if (epochs === void 0 || rowCount === 0) return;
|
|
5249
5212
|
const epoch = this.internalnextMutationEpoch();
|
|
5250
|
-
epochs
|
|
5213
|
+
publishComponentRange(epochs, rowStart, rowCount, epoch);
|
|
5251
5214
|
this.componentMutationEpochs[componentId2] = epoch;
|
|
5252
5215
|
}
|
|
5253
5216
|
/** Query facade write after the facade has already marked evidence. */
|
|
@@ -5871,11 +5834,6 @@ var World = class {
|
|
|
5871
5834
|
if (mirrorAdded) {
|
|
5872
5835
|
this.internalmarkComponentsAdded(target, [mirrorLocalId]);
|
|
5873
5836
|
this.advanceStructureEpoch();
|
|
5874
|
-
this.recordStructuralEvidence({
|
|
5875
|
-
kind: "component-added",
|
|
5876
|
-
entity: target,
|
|
5877
|
-
componentId: mirrorLocalId
|
|
5878
|
-
});
|
|
5879
5837
|
}
|
|
5880
5838
|
return ok(void 0);
|
|
5881
5839
|
}
|
|
@@ -6076,12 +6034,7 @@ var World = class {
|
|
|
6076
6034
|
data: value
|
|
6077
6035
|
});
|
|
6078
6036
|
if (!valuePreflight.ok) return valuePreflight;
|
|
6079
|
-
const
|
|
6080
|
-
const mergedValue = {
|
|
6081
|
-
...currentValue,
|
|
6082
|
-
...value
|
|
6083
|
-
};
|
|
6084
|
-
const enumError = validateEnumFieldValues(component, mergedValue, entity);
|
|
6037
|
+
const enumError = validateEnumFieldValues(component, value, entity);
|
|
6085
6038
|
if (enumError !== null) return err(enumError);
|
|
6086
6039
|
if (component.storage === "sparse") {
|
|
6087
6040
|
if (markChanged) this.markComponentChanged(entity, component);
|
|
@@ -6456,11 +6409,6 @@ var World = class {
|
|
|
6456
6409
|
if (!internal) {
|
|
6457
6410
|
this.internalmarkComponentsAdded(entity, [componentId(componentData.component)]);
|
|
6458
6411
|
this.advanceStructureEpoch();
|
|
6459
|
-
this.recordStructuralEvidence({
|
|
6460
|
-
kind: "component-added",
|
|
6461
|
-
entity,
|
|
6462
|
-
componentId: localId
|
|
6463
|
-
});
|
|
6464
6412
|
}
|
|
6465
6413
|
return ok(void 0);
|
|
6466
6414
|
}
|
|
@@ -6542,12 +6490,8 @@ var World = class {
|
|
|
6542
6490
|
this.migrateEntity(rec, srcArch, targetArch);
|
|
6543
6491
|
}
|
|
6544
6492
|
if (!internal) {
|
|
6493
|
+
this.internalnextMutationEpoch();
|
|
6545
6494
|
this.advanceStructureEpoch();
|
|
6546
|
-
this.recordStructuralEvidence({
|
|
6547
|
-
kind: "component-removed",
|
|
6548
|
-
entity,
|
|
6549
|
-
componentId: localId
|
|
6550
|
-
});
|
|
6551
6495
|
}
|
|
6552
6496
|
return ok(void 0);
|
|
6553
6497
|
}
|
|
@@ -6597,7 +6541,7 @@ var World = class {
|
|
|
6597
6541
|
record.archetypeId = arch.id;
|
|
6598
6542
|
storageTouched = true;
|
|
6599
6543
|
const table = this.table(arch);
|
|
6600
|
-
const tableRow2 = appendTableRow(table, entity);
|
|
6544
|
+
const tableRow2 = appendTableRow(table, entity, this.mutationEpoch + 1);
|
|
6601
6545
|
const archetypeRow = appendArchetypeRow(arch, tableRow2);
|
|
6602
6546
|
record.archetypeRow = archetypeRow;
|
|
6603
6547
|
for (const cd of componentDatas) {
|
|
@@ -6623,7 +6567,6 @@ var World = class {
|
|
|
6623
6567
|
...componentDatas.map((cd) => componentId(cd.component))
|
|
6624
6568
|
]);
|
|
6625
6569
|
this.advanceStructureEpoch();
|
|
6626
|
-
this.recordStructuralEvidence({ kind: "spawn", entity });
|
|
6627
6570
|
return ok(void 0);
|
|
6628
6571
|
} catch (error) {
|
|
6629
6572
|
if (storageTouched) this.poisonAfterEntityMutation("World.materializeEntity", error);
|
|
@@ -7003,6 +6946,18 @@ var World = class {
|
|
|
7003
6946
|
const elementBytes = meta.byteSize;
|
|
7004
6947
|
const isVariable = arrayMeta.length === void 0;
|
|
7005
6948
|
const fixedLength = arrayMeta.length ?? 0;
|
|
6949
|
+
if (!isVariable && metaKey !== "shared" && meta.viewCtor !== void 0 && col.view instanceof meta.viewCtor && (raw == null || Array.isArray(raw) || raw instanceof meta.viewCtor)) {
|
|
6950
|
+
const start = row * col.arity;
|
|
6951
|
+
const count = raw == null ? 0 : Math.min(raw.length, col.arity);
|
|
6952
|
+
if (Array.isArray(raw)) {
|
|
6953
|
+
for (let i = 0; i < count; i++)
|
|
6954
|
+
col.view[start + i] = typeof raw[i] === "number" ? raw[i] : 0;
|
|
6955
|
+
} else if (raw != null) {
|
|
6956
|
+
col.view.set(raw.length <= col.arity ? raw : raw.subarray(0, count), start);
|
|
6957
|
+
}
|
|
6958
|
+
col.view.fill(0, start + count, start + col.arity);
|
|
6959
|
+
return;
|
|
6960
|
+
}
|
|
7006
6961
|
let payloadCount = 0;
|
|
7007
6962
|
let payloadBytes = null;
|
|
7008
6963
|
if (raw !== null && raw !== void 0) {
|
|
@@ -7141,7 +7096,7 @@ var World = class {
|
|
|
7141
7096
|
const srcTable = this.table(srcArch);
|
|
7142
7097
|
const targetTable = this.table(targetArch);
|
|
7143
7098
|
const entity = srcTable.storage.get(componentId(Entity))?.fields.get("self")?.view[oldTableRow] ?? 0;
|
|
7144
|
-
const newTableRow = appendTableRow(targetTable, entity);
|
|
7099
|
+
const newTableRow = appendTableRow(targetTable, entity, this.mutationEpoch + 1);
|
|
7145
7100
|
const newArchetypeRow = appendArchetypeRow(targetArch, newTableRow);
|
|
7146
7101
|
for (const [compId, srcComponentStorage] of srcTable.storage) {
|
|
7147
7102
|
const srcFieldCols = srcComponentStorage.fields;
|
|
@@ -7178,7 +7133,7 @@ var World = class {
|
|
|
7178
7133
|
movedRecord.archetypeRow = archetypeSwap.newRow;
|
|
7179
7134
|
}
|
|
7180
7135
|
}
|
|
7181
|
-
const tableSwap = removeTableRow(srcTable, oldTableRow);
|
|
7136
|
+
const tableSwap = removeTableRow(srcTable, oldTableRow, this.mutationEpoch + 1);
|
|
7182
7137
|
if (tableSwap !== null) {
|
|
7183
7138
|
const movedRecord = this.records[entityIndex(tableSwap.movedEntity)];
|
|
7184
7139
|
if (movedRecord?.generation === entityGeneration(tableSwap.movedEntity)) {
|
|
@@ -7208,6 +7163,7 @@ var World = class {
|
|
|
7208
7163
|
}
|
|
7209
7164
|
record.archetypeId = targetArch.id;
|
|
7210
7165
|
record.archetypeRow = appendArchetypeRow(targetArch, tableRow2);
|
|
7166
|
+
markTableMembership(table, tableRow2, this.mutationEpoch + 1);
|
|
7211
7167
|
}
|
|
7212
7168
|
/**
|
|
7213
7169
|
* Retire one live entity and any linked-spawn descendants. The complete
|
|
@@ -7254,7 +7210,7 @@ var World = class {
|
|
|
7254
7210
|
movedRecord.archetypeRow = archetypeSwap.newRow;
|
|
7255
7211
|
}
|
|
7256
7212
|
}
|
|
7257
|
-
const tableSwap = removeTableRow(table, tableRow2);
|
|
7213
|
+
const tableSwap = removeTableRow(table, tableRow2, this.mutationEpoch + 1);
|
|
7258
7214
|
if (tableSwap !== null) {
|
|
7259
7215
|
const movedRecord = this.records[entityIndex(tableSwap.movedEntity)];
|
|
7260
7216
|
if (movedRecord?.generation === entityGeneration(tableSwap.movedEntity)) {
|
|
@@ -7265,7 +7221,6 @@ var World = class {
|
|
|
7265
7221
|
}
|
|
7266
7222
|
}
|
|
7267
7223
|
}
|
|
7268
|
-
this.recordStructuralEvidence({ kind: "despawn", entity });
|
|
7269
7224
|
record.archetypeId = -1;
|
|
7270
7225
|
record.archetypeRow = -1;
|
|
7271
7226
|
record.generation += 1;
|
|
@@ -7274,6 +7229,7 @@ var World = class {
|
|
|
7274
7229
|
const childResult = this.despawnEntity(child, true);
|
|
7275
7230
|
if (!childResult.ok) return childResult;
|
|
7276
7231
|
}
|
|
7232
|
+
this.internalnextMutationEpoch();
|
|
7277
7233
|
this.advanceStructureEpoch();
|
|
7278
7234
|
return ok(void 0);
|
|
7279
7235
|
} catch (error) {
|