@modernrelay/orbit-core 0.2.0 → 0.13.5
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/dist/chunk-GUXIITKK.js +89 -0
- package/dist/chunk-GUXIITKK.js.map +1 -0
- package/dist/chunk-NIJX5NVJ.js +212 -0
- package/dist/chunk-NIJX5NVJ.js.map +1 -0
- package/dist/{chunk-Z7FOEASL.js → chunk-XAR262L3.js} +4 -5
- package/dist/chunk-XAR262L3.js.map +1 -0
- package/dist/engine.d.ts +1 -1
- package/dist/{index-BPjuELfY.d.ts → index-CFZMson3.d.ts} +201 -4
- package/dist/index.d.ts +364 -4
- package/dist/index.js +1690 -44
- package/dist/index.js.map +1 -1
- package/dist/{clusters-ExqnvobT.d.ts → lane-CHHLuxgq.d.ts} +95 -2
- package/dist/testing.d.ts +54 -3
- package/dist/testing.js +115 -7
- package/dist/testing.js.map +1 -1
- package/dist/worker/entry.d.ts +2 -0
- package/dist/worker/entry.js +12 -0
- package/dist/worker/entry.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-Z7FOEASL.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { DEFAULT_CLUSTER_CENTER_RADIUS, DEFAULT_LAYOUT_SEED,
|
|
1
|
+
import { deriveClusters, resolveClusterCenters, clusterCentroids } from './chunk-XAR262L3.js';
|
|
2
|
+
export { DEFAULT_CLUSTER_CENTER_RADIUS, DEFAULT_LAYOUT_SEED, clusterCentroids, deriveClusters, generateClusterCenters, resolveClusterCenters } from './chunk-XAR262L3.js';
|
|
3
|
+
import { DIAGNOSTIC_SAMPLE_CAP, encodeStringTable, collectTransfers, EnvelopeSequencer, RequestLedger } from './chunk-NIJX5NVJ.js';
|
|
4
|
+
export { DIAGNOSTIC_SAMPLE_CAP, acceptColumnar, collectTransfers, decodeStringTable, encodeStringTable, judgeEpoch } from './chunk-NIJX5NVJ.js';
|
|
3
5
|
import { createStore } from 'zustand/vanilla';
|
|
4
6
|
|
|
5
7
|
// src/errors.ts
|
|
@@ -1513,6 +1515,375 @@ function neighborsOf(adj, index) {
|
|
|
1513
1515
|
}
|
|
1514
1516
|
return adj.neighbors.subarray(adj.offsets[index], adj.offsets[index + 1]);
|
|
1515
1517
|
}
|
|
1518
|
+
function buildIncidence(links, pointCount) {
|
|
1519
|
+
if (!Number.isInteger(pointCount) || pointCount < 0) {
|
|
1520
|
+
throw new RangeError(
|
|
1521
|
+
`buildIncidence: pointCount must be a non-negative integer, got ${pointCount}`
|
|
1522
|
+
);
|
|
1523
|
+
}
|
|
1524
|
+
if ((links.length & 1) !== 0) {
|
|
1525
|
+
throw new RangeError(
|
|
1526
|
+
`buildIncidence: links length must be even ([src, tgt] pairs), got ${links.length}`
|
|
1527
|
+
);
|
|
1528
|
+
}
|
|
1529
|
+
const n = links.length;
|
|
1530
|
+
const offsets = new Uint32Array(pointCount + 1);
|
|
1531
|
+
for (let i = 0; i < n; i++) {
|
|
1532
|
+
const p = links[i];
|
|
1533
|
+
if (p >= pointCount) {
|
|
1534
|
+
throw new RangeError(
|
|
1535
|
+
`buildIncidence: link endpoint ${p} out of range (pointCount ${pointCount})`
|
|
1536
|
+
);
|
|
1537
|
+
}
|
|
1538
|
+
offsets[p + 1] = offsets[p + 1] + 1;
|
|
1539
|
+
}
|
|
1540
|
+
for (let i = 1; i <= pointCount; i++) {
|
|
1541
|
+
offsets[i] = offsets[i] + offsets[i - 1];
|
|
1542
|
+
}
|
|
1543
|
+
const edgeSlots = new Uint32Array(n);
|
|
1544
|
+
const cursor = offsets.slice(0, pointCount);
|
|
1545
|
+
for (let i = 0; i < n; i += 2) {
|
|
1546
|
+
const edge = i >>> 1;
|
|
1547
|
+
const a = links[i];
|
|
1548
|
+
const b = links[i + 1];
|
|
1549
|
+
const ca = cursor[a];
|
|
1550
|
+
edgeSlots[ca] = edge;
|
|
1551
|
+
cursor[a] = ca + 1;
|
|
1552
|
+
const cb = cursor[b];
|
|
1553
|
+
edgeSlots[cb] = edge;
|
|
1554
|
+
cursor[b] = cb + 1;
|
|
1555
|
+
}
|
|
1556
|
+
return { offsets, edgeSlots };
|
|
1557
|
+
}
|
|
1558
|
+
function incidentEdgesOf(inc, index) {
|
|
1559
|
+
const pointCount = inc.offsets.length - 1;
|
|
1560
|
+
if (!Number.isInteger(index) || index < 0 || index >= pointCount) {
|
|
1561
|
+
throw new RangeError(`incidentEdgesOf: index ${index} out of range (pointCount ${pointCount})`);
|
|
1562
|
+
}
|
|
1563
|
+
return inc.edgeSlots.subarray(inc.offsets[index], inc.offsets[index + 1]);
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
// src/alphaCompose.ts
|
|
1567
|
+
var IncrementalAlphaComposer = class {
|
|
1568
|
+
bufA = null;
|
|
1569
|
+
bufB = null;
|
|
1570
|
+
staleA = [];
|
|
1571
|
+
staleB = [];
|
|
1572
|
+
/** Which buffer the NEXT nextBuffer() call returns (0 = A, 1 = B). */
|
|
1573
|
+
next = 0;
|
|
1574
|
+
// --- seed key ---
|
|
1575
|
+
seededBase = null;
|
|
1576
|
+
seededCount = -1;
|
|
1577
|
+
seededDimAlpha = NaN;
|
|
1578
|
+
seededEpoch = void 0;
|
|
1579
|
+
/** Slots rewritten since the last resetStats (wave-5 gate instrument). */
|
|
1580
|
+
slotsRewritten = 0;
|
|
1581
|
+
/** Full reseeds performed (each is one O(n) masked pass over the pair). */
|
|
1582
|
+
reseeds = 0;
|
|
1583
|
+
resetStats() {
|
|
1584
|
+
this.slotsRewritten = 0;
|
|
1585
|
+
this.reseeds = 0;
|
|
1586
|
+
}
|
|
1587
|
+
/**
|
|
1588
|
+
* True when the composer is seeded for exactly this (base, count,
|
|
1589
|
+
* dimAlpha, extraEpoch) tuple; otherwise both buffers are rebuilt with a
|
|
1590
|
+
* full masked pass and the stale lists reset. Object.is on the alpha
|
|
1591
|
+
* handles the NaN sentinel.
|
|
1592
|
+
*/
|
|
1593
|
+
ensureSeeded(base, count, dimAlpha, extraEpoch, alphaOf) {
|
|
1594
|
+
if (this.seededBase === base && this.seededCount === count && Object.is(this.seededDimAlpha, dimAlpha) && this.seededEpoch === extraEpoch && this.bufA !== null && this.bufB !== null) {
|
|
1595
|
+
return true;
|
|
1596
|
+
}
|
|
1597
|
+
this.reseeds += 1;
|
|
1598
|
+
this.bufA = this.seedOne(base, count, alphaOf, this.bufA);
|
|
1599
|
+
this.bufB = this.seedOne(base, count, alphaOf, this.bufB);
|
|
1600
|
+
this.staleA.length = 0;
|
|
1601
|
+
this.staleB.length = 0;
|
|
1602
|
+
this.seededBase = base;
|
|
1603
|
+
this.seededCount = count;
|
|
1604
|
+
this.seededDimAlpha = dimAlpha;
|
|
1605
|
+
this.seededEpoch = extraEpoch;
|
|
1606
|
+
return false;
|
|
1607
|
+
}
|
|
1608
|
+
/** Record changed slots from one mask drain (call once per drain — the
|
|
1609
|
+
* drain arrays are reused by the mask, so this copies them out). */
|
|
1610
|
+
note(slots) {
|
|
1611
|
+
for (let i = 0; i < slots.length; i += 1) {
|
|
1612
|
+
this.staleA.push(slots[i]);
|
|
1613
|
+
this.staleB.push(slots[i]);
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
/**
|
|
1617
|
+
* Replay the target buffer's stale slots against the CURRENT mask state,
|
|
1618
|
+
* swap, and return it. Same output semantics as the naive composer: the
|
|
1619
|
+
* base RGB is untouched, alpha = base alpha × alphaOf(slot).
|
|
1620
|
+
*/
|
|
1621
|
+
nextBuffer(base, alphaOf) {
|
|
1622
|
+
const useA = this.next === 0;
|
|
1623
|
+
const buf = useA ? this.bufA : this.bufB;
|
|
1624
|
+
const stale = useA ? this.staleA : this.staleB;
|
|
1625
|
+
for (let i = 0; i < stale.length; i += 1) {
|
|
1626
|
+
const slot = stale[i];
|
|
1627
|
+
buf[4 * slot + 3] = base[4 * slot + 3] * alphaOf(slot);
|
|
1628
|
+
this.slotsRewritten += 1;
|
|
1629
|
+
}
|
|
1630
|
+
stale.length = 0;
|
|
1631
|
+
this.next = useA ? 1 : 0;
|
|
1632
|
+
return buf;
|
|
1633
|
+
}
|
|
1634
|
+
/** Drop the buffers entirely (scene teardown). */
|
|
1635
|
+
reset() {
|
|
1636
|
+
this.bufA = null;
|
|
1637
|
+
this.bufB = null;
|
|
1638
|
+
this.staleA.length = 0;
|
|
1639
|
+
this.staleB.length = 0;
|
|
1640
|
+
this.seededBase = null;
|
|
1641
|
+
this.seededCount = -1;
|
|
1642
|
+
this.seededDimAlpha = NaN;
|
|
1643
|
+
this.seededEpoch = void 0;
|
|
1644
|
+
this.next = 0;
|
|
1645
|
+
}
|
|
1646
|
+
seedOne(base, count, alphaOf, reuse) {
|
|
1647
|
+
const out = reuse !== null && reuse.length === base.length ? reuse : new Float32Array(base.length);
|
|
1648
|
+
out.set(base);
|
|
1649
|
+
for (let i = 0; i < count; i += 1) {
|
|
1650
|
+
const a = alphaOf(i);
|
|
1651
|
+
if (a !== 1) out[4 * i + 3] = base[4 * i + 3] * a;
|
|
1652
|
+
}
|
|
1653
|
+
return out;
|
|
1654
|
+
}
|
|
1655
|
+
};
|
|
1656
|
+
|
|
1657
|
+
// src/perf.ts
|
|
1658
|
+
var PRESSURE_WINDOW_MS = 250;
|
|
1659
|
+
var DROPPED_FRAME_MS = 34;
|
|
1660
|
+
var SLEEP_GAP_MS = 250;
|
|
1661
|
+
var PressureSampler = class {
|
|
1662
|
+
lastFrameAt = NaN;
|
|
1663
|
+
windowStart = NaN;
|
|
1664
|
+
windowSum = 0;
|
|
1665
|
+
windowCount = 0;
|
|
1666
|
+
ewma = NaN;
|
|
1667
|
+
worst = 0;
|
|
1668
|
+
frames = 0;
|
|
1669
|
+
dropped = 0;
|
|
1670
|
+
windows = 0;
|
|
1671
|
+
idleWakeups = 0;
|
|
1672
|
+
/**
|
|
1673
|
+
* Record one onFrame tick. `settled` marks a tick that arrived while the
|
|
1674
|
+
* scene was at rest (sim settled, no pending commit work) — the idle-
|
|
1675
|
+
* wakeup counter, which reads 0 when the ADR-005 gated clock is honest.
|
|
1676
|
+
*/
|
|
1677
|
+
noteFrame(timeMs, settled) {
|
|
1678
|
+
this.frames += 1;
|
|
1679
|
+
if (settled) this.idleWakeups += 1;
|
|
1680
|
+
const prev = this.lastFrameAt;
|
|
1681
|
+
this.lastFrameAt = timeMs;
|
|
1682
|
+
if (Number.isNaN(prev)) {
|
|
1683
|
+
this.windowStart = timeMs;
|
|
1684
|
+
return;
|
|
1685
|
+
}
|
|
1686
|
+
const delta = timeMs - prev;
|
|
1687
|
+
if (delta < 0) {
|
|
1688
|
+
this.windowStart = timeMs;
|
|
1689
|
+
this.windowSum = 0;
|
|
1690
|
+
this.windowCount = 0;
|
|
1691
|
+
return;
|
|
1692
|
+
}
|
|
1693
|
+
if (delta >= SLEEP_GAP_MS) {
|
|
1694
|
+
this.closeWindow();
|
|
1695
|
+
this.windowStart = timeMs;
|
|
1696
|
+
return;
|
|
1697
|
+
}
|
|
1698
|
+
if (delta > this.worst) this.worst = delta;
|
|
1699
|
+
if (delta >= DROPPED_FRAME_MS) this.dropped += 1;
|
|
1700
|
+
this.windowSum += delta;
|
|
1701
|
+
this.windowCount += 1;
|
|
1702
|
+
if (timeMs - this.windowStart >= PRESSURE_WINDOW_MS) {
|
|
1703
|
+
this.closeWindow();
|
|
1704
|
+
this.windowStart = timeMs;
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
snapshot() {
|
|
1708
|
+
return {
|
|
1709
|
+
frameEwmaMs: this.ewma,
|
|
1710
|
+
worstFrameMs: this.worst,
|
|
1711
|
+
frames: this.frames,
|
|
1712
|
+
droppedFrames: this.dropped,
|
|
1713
|
+
windows: this.windows,
|
|
1714
|
+
idleWakeups: this.idleWakeups
|
|
1715
|
+
};
|
|
1716
|
+
}
|
|
1717
|
+
/** Zero the accumulated counters (EWMA and frame anchor survive — the
|
|
1718
|
+
* smoothing is continuous; the counters are per-sample-period). */
|
|
1719
|
+
resetCounters() {
|
|
1720
|
+
this.worst = 0;
|
|
1721
|
+
this.frames = 0;
|
|
1722
|
+
this.dropped = 0;
|
|
1723
|
+
this.windows = 0;
|
|
1724
|
+
this.idleWakeups = 0;
|
|
1725
|
+
}
|
|
1726
|
+
closeWindow() {
|
|
1727
|
+
if (this.windowCount === 0) return;
|
|
1728
|
+
const mean = this.windowSum / this.windowCount;
|
|
1729
|
+
this.windows += 1;
|
|
1730
|
+
this.ewma = Number.isNaN(this.ewma) ? mean : this.ewma * 0.7 + mean * 0.3;
|
|
1731
|
+
this.windowSum = 0;
|
|
1732
|
+
this.windowCount = 0;
|
|
1733
|
+
}
|
|
1734
|
+
};
|
|
1735
|
+
|
|
1736
|
+
// src/degrade.ts
|
|
1737
|
+
var SCALE_LIMITS_DEFAULTS = Object.freeze({
|
|
1738
|
+
domLabelNodes: 1e5,
|
|
1739
|
+
pickingLinks: 25e4,
|
|
1740
|
+
histogramBatchNodes: 5e5,
|
|
1741
|
+
hysteresis: 0.1,
|
|
1742
|
+
minimumDwellMs: 1e3,
|
|
1743
|
+
resourceDegradationOrder: Object.freeze(["disable-transitions", "defer-images"])
|
|
1744
|
+
});
|
|
1745
|
+
var RESOURCE_STEPS = /* @__PURE__ */ new Set([
|
|
1746
|
+
"disable-transitions",
|
|
1747
|
+
"defer-images",
|
|
1748
|
+
"uniform-link-style"
|
|
1749
|
+
]);
|
|
1750
|
+
function resolveScaleLimits(input) {
|
|
1751
|
+
const warnings = [];
|
|
1752
|
+
const out = { ...SCALE_LIMITS_DEFAULTS };
|
|
1753
|
+
if (input === void 0) return { limits: out, warnings };
|
|
1754
|
+
const num3 = (key) => {
|
|
1755
|
+
const v = input[key];
|
|
1756
|
+
if (v === void 0) return;
|
|
1757
|
+
if (typeof v === "number" && Number.isFinite(v) && v >= 0) out[key] = v;
|
|
1758
|
+
else warnings.push(`limits.${key} must be a non-negative finite number (got ${String(v)})`);
|
|
1759
|
+
};
|
|
1760
|
+
num3("domLabelNodes");
|
|
1761
|
+
num3("pickingLinks");
|
|
1762
|
+
num3("histogramBatchNodes");
|
|
1763
|
+
num3("minimumDwellMs");
|
|
1764
|
+
if (input.hysteresis !== void 0) {
|
|
1765
|
+
const h = input.hysteresis;
|
|
1766
|
+
if (typeof h === "number" && Number.isFinite(h) && h >= 0 && h < 1) out.hysteresis = h;
|
|
1767
|
+
else warnings.push(`limits.hysteresis must be in [0, 1) (got ${String(h)})`);
|
|
1768
|
+
}
|
|
1769
|
+
if (input.resourceDegradationOrder !== void 0) {
|
|
1770
|
+
const order = input.resourceDegradationOrder;
|
|
1771
|
+
const valid = Array.isArray(order) && order.every((s) => RESOURCE_STEPS.has(s)) && new Set(order).size === order.length;
|
|
1772
|
+
if (valid) out.resourceDegradationOrder = Object.freeze([...order]);
|
|
1773
|
+
else {
|
|
1774
|
+
warnings.push(
|
|
1775
|
+
"limits.resourceDegradationOrder must be unique resource steps ('disable-transitions' | 'defer-images' | 'uniform-link-style')"
|
|
1776
|
+
);
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
return { limits: Object.freeze(out), warnings };
|
|
1780
|
+
}
|
|
1781
|
+
var DegradeController = class {
|
|
1782
|
+
constructor(limits, now) {
|
|
1783
|
+
this.limits = limits;
|
|
1784
|
+
this.now = now;
|
|
1785
|
+
}
|
|
1786
|
+
limits;
|
|
1787
|
+
now;
|
|
1788
|
+
steps = /* @__PURE__ */ new Map();
|
|
1789
|
+
activeCache = Object.freeze([]);
|
|
1790
|
+
isEngaged(step) {
|
|
1791
|
+
return this.steps.get(step)?.engaged === true;
|
|
1792
|
+
}
|
|
1793
|
+
/** Stable frozen list for GraphPerfSnapshot.activeDegradations. */
|
|
1794
|
+
activeSteps() {
|
|
1795
|
+
return this.activeCache;
|
|
1796
|
+
}
|
|
1797
|
+
/**
|
|
1798
|
+
* Edge-triggered count evaluation over the CURRENT visible counts.
|
|
1799
|
+
* Returns the events to emit (empty when nothing crossed its band or a
|
|
1800
|
+
* dwell is holding). Count-engaged steps disengage here; pressure- or
|
|
1801
|
+
* resource-engaged steps do NOT auto-disengage on counts — their signal
|
|
1802
|
+
* owns them (`clearPressure` / `releaseResourceSteps`).
|
|
1803
|
+
*/
|
|
1804
|
+
evaluateCounts(visible) {
|
|
1805
|
+
const events = [];
|
|
1806
|
+
const counts = [
|
|
1807
|
+
["cap-dom-labels", visible.nodes, this.limits.domLabelNodes],
|
|
1808
|
+
["defer-link-picking", visible.edges, this.limits.pickingLinks],
|
|
1809
|
+
["batch-histograms", visible.nodes, this.limits.histogramBatchNodes]
|
|
1810
|
+
];
|
|
1811
|
+
for (const [step, value, limit] of counts) {
|
|
1812
|
+
const state = this.stateOf(step);
|
|
1813
|
+
if (!state.engaged) {
|
|
1814
|
+
if (value > limit && this.dwellOver(state)) {
|
|
1815
|
+
events.push(this.transition(step, state, true, "count", visible));
|
|
1816
|
+
}
|
|
1817
|
+
} else if (state.reason === "count") {
|
|
1818
|
+
if (value < limit * (1 - this.limits.hysteresis) && this.dwellOver(state)) {
|
|
1819
|
+
events.push(this.transition(step, state, false, "count", visible));
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
return events;
|
|
1824
|
+
}
|
|
1825
|
+
/**
|
|
1826
|
+
* Pressure trigger (frame or input): engage a step EARLIER than its count
|
|
1827
|
+
* hint. No-op (null) when already engaged or dwell-held.
|
|
1828
|
+
*/
|
|
1829
|
+
engageForPressure(step, reason, visible) {
|
|
1830
|
+
const state = this.stateOf(step);
|
|
1831
|
+
if (state.engaged || !this.dwellOver(state)) return null;
|
|
1832
|
+
return this.transition(step, state, true, reason, visible);
|
|
1833
|
+
}
|
|
1834
|
+
/** Release a pressure-engaged step once its signal normalizes. */
|
|
1835
|
+
clearPressure(step, visible) {
|
|
1836
|
+
const state = this.stateOf(step);
|
|
1837
|
+
if (!state.engaged) return null;
|
|
1838
|
+
if (state.reason !== "frame-pressure" && state.reason !== "input-pressure") return null;
|
|
1839
|
+
if (!this.dwellOver(state)) return null;
|
|
1840
|
+
return this.transition(step, state, false, state.reason, visible);
|
|
1841
|
+
}
|
|
1842
|
+
/**
|
|
1843
|
+
* Resource-admission trigger: engage the NEXT not-yet-engaged step in the
|
|
1844
|
+
* declared order. Null when the order is exhausted — the caller must then
|
|
1845
|
+
* REJECT before allocating (§17: never silently erase semantic styling).
|
|
1846
|
+
*/
|
|
1847
|
+
engageNextResourceStep(visible) {
|
|
1848
|
+
for (const step of this.limits.resourceDegradationOrder) {
|
|
1849
|
+
const state = this.stateOf(step);
|
|
1850
|
+
if (state.engaged) continue;
|
|
1851
|
+
return this.transition(step, state, true, "resource-estimate", visible);
|
|
1852
|
+
}
|
|
1853
|
+
return null;
|
|
1854
|
+
}
|
|
1855
|
+
/** Release every resource-engaged step (pressure cleared / new budget). */
|
|
1856
|
+
releaseResourceSteps(visible) {
|
|
1857
|
+
const events = [];
|
|
1858
|
+
for (const step of this.limits.resourceDegradationOrder) {
|
|
1859
|
+
const state = this.stateOf(step);
|
|
1860
|
+
if (state.engaged && state.reason === "resource-estimate") {
|
|
1861
|
+
events.push(this.transition(step, state, false, "resource-estimate", visible));
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
return events;
|
|
1865
|
+
}
|
|
1866
|
+
stateOf(step) {
|
|
1867
|
+
let state = this.steps.get(step);
|
|
1868
|
+
if (state === void 0) {
|
|
1869
|
+
state = { engaged: false, changedAt: -Infinity, reason: null };
|
|
1870
|
+
this.steps.set(step, state);
|
|
1871
|
+
}
|
|
1872
|
+
return state;
|
|
1873
|
+
}
|
|
1874
|
+
dwellOver(state) {
|
|
1875
|
+
return this.now() - state.changedAt >= this.limits.minimumDwellMs || state.changedAt === -Infinity;
|
|
1876
|
+
}
|
|
1877
|
+
transition(step, state, engaged, reason, visible) {
|
|
1878
|
+
state.engaged = engaged;
|
|
1879
|
+
state.changedAt = this.now();
|
|
1880
|
+
state.reason = engaged ? reason : null;
|
|
1881
|
+
const active = [];
|
|
1882
|
+
for (const [s, st] of this.steps) if (st.engaged) active.push(s);
|
|
1883
|
+
this.activeCache = Object.freeze(active);
|
|
1884
|
+
return { step, engaged, reason, visible: { nodes: visible.nodes, edges: visible.edges } };
|
|
1885
|
+
}
|
|
1886
|
+
};
|
|
1516
1887
|
|
|
1517
1888
|
// src/linkPick.ts
|
|
1518
1889
|
var MEDIAN_SAMPLE_CAP = 1024;
|
|
@@ -2492,8 +2863,8 @@ function buildEntries(nodes, fields) {
|
|
|
2492
2863
|
fieldValues = [];
|
|
2493
2864
|
fieldLowers = [];
|
|
2494
2865
|
const attrs = node.attrs;
|
|
2495
|
-
for (const
|
|
2496
|
-
const raw = attrs?.[
|
|
2866
|
+
for (const field2 of fields) {
|
|
2867
|
+
const raw = attrs?.[field2];
|
|
2497
2868
|
if (raw === void 0 || raw === null) continue;
|
|
2498
2869
|
const value = String(raw);
|
|
2499
2870
|
fieldValues.push(value);
|
|
@@ -2595,11 +2966,392 @@ function createLocalSearchService(getBase) {
|
|
|
2595
2966
|
};
|
|
2596
2967
|
}
|
|
2597
2968
|
|
|
2969
|
+
// src/columnar.ts
|
|
2970
|
+
function isColumnarSnapshot(input) {
|
|
2971
|
+
return input.kind === "columnar";
|
|
2972
|
+
}
|
|
2973
|
+
var isTypedLength = (arr, n) => arr !== void 0 && arr.length === n;
|
|
2974
|
+
function checkStringColumn(col, rows, where, isIds, out) {
|
|
2975
|
+
if (col.kind !== "string" || !Array.isArray(col.dictionary) || !(col.codes instanceof Uint32Array)) {
|
|
2976
|
+
out.push({ where, problem: "not-a-string-column", detail: 'expected {kind:"string", dictionary, codes}' });
|
|
2977
|
+
return;
|
|
2978
|
+
}
|
|
2979
|
+
if (col.codes.length !== rows) {
|
|
2980
|
+
const detached = col.codes.length === 0 && col.codes.buffer.byteLength === 0;
|
|
2981
|
+
out.push({
|
|
2982
|
+
where,
|
|
2983
|
+
problem: "length-mismatch",
|
|
2984
|
+
detail: detached ? `codes buffer is DETACHED (a bufferOwnership:'transfer' snapshot is single-use)` : `codes.length ${col.codes.length} !== length ${rows}`
|
|
2985
|
+
});
|
|
2986
|
+
return;
|
|
2987
|
+
}
|
|
2988
|
+
if (col.nulls !== void 0) {
|
|
2989
|
+
if (col.nulls.length !== rows) {
|
|
2990
|
+
out.push({
|
|
2991
|
+
where,
|
|
2992
|
+
problem: "nulls-length-mismatch",
|
|
2993
|
+
detail: `nulls.length ${col.nulls.length} !== length ${rows}`
|
|
2994
|
+
});
|
|
2995
|
+
} else if (isIds) {
|
|
2996
|
+
for (let i = 0; i < rows; i++) {
|
|
2997
|
+
if (col.nulls[i] !== 0) {
|
|
2998
|
+
out.push({ where, problem: "null-id", detail: `row ${i}: ids may not be null` });
|
|
2999
|
+
break;
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
}
|
|
3004
|
+
const dictSize = col.dictionary.length;
|
|
3005
|
+
for (let i = 0; i < rows; i++) {
|
|
3006
|
+
if (col.codes[i] >= dictSize) {
|
|
3007
|
+
out.push({
|
|
3008
|
+
where,
|
|
3009
|
+
problem: "code-out-of-range",
|
|
3010
|
+
detail: `row ${i}: code ${col.codes[i]} >= dictionary size ${dictSize}`
|
|
3011
|
+
});
|
|
3012
|
+
break;
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
function checkColumn(col, rows, where, out) {
|
|
3017
|
+
switch (col.kind) {
|
|
3018
|
+
case "string":
|
|
3019
|
+
checkStringColumn(col, rows, where, false, out);
|
|
3020
|
+
return;
|
|
3021
|
+
case "f64":
|
|
3022
|
+
case "i32":
|
|
3023
|
+
case "u32":
|
|
3024
|
+
case "bool": {
|
|
3025
|
+
if (!isTypedLength(col.data, rows)) {
|
|
3026
|
+
out.push({
|
|
3027
|
+
where,
|
|
3028
|
+
problem: "length-mismatch",
|
|
3029
|
+
detail: `data.length ${col.data?.length ?? "missing"} !== length ${rows}`
|
|
3030
|
+
});
|
|
3031
|
+
}
|
|
3032
|
+
if (col.nulls !== void 0 && col.nulls.length !== rows) {
|
|
3033
|
+
out.push({
|
|
3034
|
+
where,
|
|
3035
|
+
problem: "nulls-length-mismatch",
|
|
3036
|
+
detail: `nulls.length ${col.nulls.length} !== length ${rows}`
|
|
3037
|
+
});
|
|
3038
|
+
}
|
|
3039
|
+
return;
|
|
3040
|
+
}
|
|
3041
|
+
default:
|
|
3042
|
+
out.push({
|
|
3043
|
+
where,
|
|
3044
|
+
problem: "bad-column-kind",
|
|
3045
|
+
detail: `unknown column kind '${String(col.kind)}'`
|
|
3046
|
+
});
|
|
3047
|
+
}
|
|
3048
|
+
}
|
|
3049
|
+
function validateColumnarStructure(snapshot) {
|
|
3050
|
+
const issues = [];
|
|
3051
|
+
const nodeRows = snapshot.nodes?.length;
|
|
3052
|
+
const edgeRows = snapshot.edges?.length;
|
|
3053
|
+
if (!Number.isInteger(nodeRows) || nodeRows < 0) {
|
|
3054
|
+
issues.push({ where: "nodes.length", problem: "bad-length", detail: String(nodeRows) });
|
|
3055
|
+
return issues;
|
|
3056
|
+
}
|
|
3057
|
+
if (!Number.isInteger(edgeRows) || edgeRows < 0) {
|
|
3058
|
+
issues.push({ where: "edges.length", problem: "bad-length", detail: String(edgeRows) });
|
|
3059
|
+
return issues;
|
|
3060
|
+
}
|
|
3061
|
+
checkStringColumn(snapshot.nodes.ids, nodeRows, "nodes.ids", true, issues);
|
|
3062
|
+
for (const [name, col] of Object.entries(snapshot.nodes.columns ?? {})) {
|
|
3063
|
+
checkColumn(col, nodeRows, `nodes.${name}`, issues);
|
|
3064
|
+
}
|
|
3065
|
+
checkStringColumn(snapshot.edges.ids, edgeRows, "edges.ids", true, issues);
|
|
3066
|
+
for (const [name, col] of Object.entries(snapshot.edges.columns ?? {})) {
|
|
3067
|
+
checkColumn(col, edgeRows, `edges.${name}`, issues);
|
|
3068
|
+
}
|
|
3069
|
+
const { source, target } = snapshot.edges;
|
|
3070
|
+
if (!(source instanceof Uint32Array) || source.length !== edgeRows) {
|
|
3071
|
+
issues.push({
|
|
3072
|
+
where: "edges.source",
|
|
3073
|
+
problem: "endpoint-length-mismatch",
|
|
3074
|
+
detail: `source.length ${source?.length ?? "missing"} !== length ${edgeRows}`
|
|
3075
|
+
});
|
|
3076
|
+
}
|
|
3077
|
+
if (!(target instanceof Uint32Array) || target.length !== edgeRows) {
|
|
3078
|
+
issues.push({
|
|
3079
|
+
where: "edges.target",
|
|
3080
|
+
problem: "endpoint-length-mismatch",
|
|
3081
|
+
detail: `target.length ${target?.length ?? "missing"} !== length ${edgeRows}`
|
|
3082
|
+
});
|
|
3083
|
+
}
|
|
3084
|
+
if (issues.length === 0) {
|
|
3085
|
+
for (let i = 0; i < edgeRows; i++) {
|
|
3086
|
+
if (source[i] >= nodeRows || target[i] >= nodeRows) {
|
|
3087
|
+
issues.push({
|
|
3088
|
+
where: "edges.endpoints",
|
|
3089
|
+
problem: "endpoint-out-of-range",
|
|
3090
|
+
detail: `row ${i}: (${source[i]}, ${target[i]}) with ${nodeRows} nodes`
|
|
3091
|
+
});
|
|
3092
|
+
break;
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
3096
|
+
return issues;
|
|
3097
|
+
}
|
|
3098
|
+
function columnValueAt(col, i) {
|
|
3099
|
+
if (col.nulls !== void 0 && col.nulls[i] !== 0) return null;
|
|
3100
|
+
switch (col.kind) {
|
|
3101
|
+
case "string":
|
|
3102
|
+
return col.dictionary[col.codes[i]];
|
|
3103
|
+
case "bool":
|
|
3104
|
+
return col.data[i] !== 0;
|
|
3105
|
+
default:
|
|
3106
|
+
return col.data[i];
|
|
3107
|
+
}
|
|
3108
|
+
}
|
|
3109
|
+
function materializeColumnarSnapshot(snapshot) {
|
|
3110
|
+
const nodeCols = Object.entries(snapshot.nodes.columns ?? {});
|
|
3111
|
+
const nodeIds = snapshot.nodes.ids;
|
|
3112
|
+
const nodes = new Array(snapshot.nodes.length);
|
|
3113
|
+
for (let i = 0; i < snapshot.nodes.length; i++) {
|
|
3114
|
+
const attrs = {};
|
|
3115
|
+
for (const [name, col] of nodeCols) attrs[name] = columnValueAt(col, i);
|
|
3116
|
+
nodes[i] = { id: nodeIds.dictionary[nodeIds.codes[i]], attrs };
|
|
3117
|
+
}
|
|
3118
|
+
const edgeCols = Object.entries(snapshot.edges.columns ?? {});
|
|
3119
|
+
const edgeIds = snapshot.edges.ids;
|
|
3120
|
+
const { source, target } = snapshot.edges;
|
|
3121
|
+
const edges = new Array(snapshot.edges.length);
|
|
3122
|
+
for (let i = 0; i < snapshot.edges.length; i++) {
|
|
3123
|
+
const attrs = {};
|
|
3124
|
+
for (const [name, col] of edgeCols) attrs[name] = columnValueAt(col, i);
|
|
3125
|
+
edges[i] = {
|
|
3126
|
+
id: edgeIds.dictionary[edgeIds.codes[i]],
|
|
3127
|
+
source: nodes[source[i]].id,
|
|
3128
|
+
target: nodes[target[i]].id,
|
|
3129
|
+
attrs
|
|
3130
|
+
};
|
|
3131
|
+
}
|
|
3132
|
+
return {
|
|
3133
|
+
datasetKey: snapshot.datasetKey,
|
|
3134
|
+
sourceRevision: snapshot.sourceRevision,
|
|
3135
|
+
nodes,
|
|
3136
|
+
edges
|
|
3137
|
+
};
|
|
3138
|
+
}
|
|
3139
|
+
function buildAcceptedFromColumnar(snapshot, acceptance) {
|
|
3140
|
+
const nodeCols = Object.entries(snapshot.nodes.columns ?? {});
|
|
3141
|
+
const nodeIds = snapshot.nodes.ids;
|
|
3142
|
+
const nodes = new Array(acceptance.acceptedNodeCount);
|
|
3143
|
+
const nodeIndex = /* @__PURE__ */ new Map();
|
|
3144
|
+
let outN = 0;
|
|
3145
|
+
for (let i = 0; i < snapshot.nodes.length; i++) {
|
|
3146
|
+
if (acceptance.keepNodes[i] !== 1) continue;
|
|
3147
|
+
const attrs = {};
|
|
3148
|
+
for (const [name, col] of nodeCols) attrs[name] = columnValueAt(col, i);
|
|
3149
|
+
const id = nodeIds.dictionary[nodeIds.codes[i]];
|
|
3150
|
+
nodeIndex.set(id, outN);
|
|
3151
|
+
nodes[outN] = { id, attrs };
|
|
3152
|
+
outN += 1;
|
|
3153
|
+
}
|
|
3154
|
+
const edgeCols = Object.entries(snapshot.edges.columns ?? {});
|
|
3155
|
+
const edgeIds = snapshot.edges.ids;
|
|
3156
|
+
const { source, target } = snapshot.edges;
|
|
3157
|
+
const edges = new Array(acceptance.acceptedEdgeCount);
|
|
3158
|
+
let outE = 0;
|
|
3159
|
+
for (let e = 0; e < snapshot.edges.length; e++) {
|
|
3160
|
+
if (acceptance.keepEdges[e] !== 1) continue;
|
|
3161
|
+
const attrs = {};
|
|
3162
|
+
for (const [name, col] of edgeCols) attrs[name] = columnValueAt(col, e);
|
|
3163
|
+
edges[outE] = {
|
|
3164
|
+
id: edgeIds.dictionary[edgeIds.codes[e]],
|
|
3165
|
+
// Endpoint STRINGS resolve through the original row (a dropped
|
|
3166
|
+
// duplicate row shares its survivor's id string by construction).
|
|
3167
|
+
source: nodeIds.dictionary[nodeIds.codes[source[e]]],
|
|
3168
|
+
target: nodeIds.dictionary[nodeIds.codes[target[e]]],
|
|
3169
|
+
attrs
|
|
3170
|
+
};
|
|
3171
|
+
outE += 1;
|
|
3172
|
+
}
|
|
3173
|
+
return {
|
|
3174
|
+
datasetKey: snapshot.datasetKey,
|
|
3175
|
+
sourceRevision: snapshot.sourceRevision,
|
|
3176
|
+
nodes,
|
|
3177
|
+
edges,
|
|
3178
|
+
nodeIndex,
|
|
3179
|
+
diagnostics: acceptance.diagnostics
|
|
3180
|
+
};
|
|
3181
|
+
}
|
|
3182
|
+
function columnarArrayBuffers(snapshot) {
|
|
3183
|
+
const buffers = /* @__PURE__ */ new Set();
|
|
3184
|
+
const add = (view) => {
|
|
3185
|
+
if (view !== void 0 && view.buffer instanceof ArrayBuffer) buffers.add(view.buffer);
|
|
3186
|
+
};
|
|
3187
|
+
const addColumn = (col) => {
|
|
3188
|
+
if (col.kind === "string") add(col.codes);
|
|
3189
|
+
else add(col.data);
|
|
3190
|
+
add(col.nulls);
|
|
3191
|
+
};
|
|
3192
|
+
addColumn(snapshot.nodes.ids);
|
|
3193
|
+
for (const col of Object.values(snapshot.nodes.columns ?? {})) addColumn(col);
|
|
3194
|
+
addColumn(snapshot.edges.ids);
|
|
3195
|
+
for (const col of Object.values(snapshot.edges.columns ?? {})) addColumn(col);
|
|
3196
|
+
add(snapshot.edges.source);
|
|
3197
|
+
add(snapshot.edges.target);
|
|
3198
|
+
return [...buffers];
|
|
3199
|
+
}
|
|
3200
|
+
function detachColumnarBuffers(snapshot) {
|
|
3201
|
+
let detached = 0;
|
|
3202
|
+
for (const buffer of columnarArrayBuffers(snapshot)) {
|
|
3203
|
+
if (buffer.byteLength === 0) continue;
|
|
3204
|
+
const transferable = buffer;
|
|
3205
|
+
if (typeof transferable.transfer === "function") {
|
|
3206
|
+
transferable.transfer();
|
|
3207
|
+
} else {
|
|
3208
|
+
structuredClone(buffer, { transfer: [buffer] });
|
|
3209
|
+
}
|
|
3210
|
+
detached += 1;
|
|
3211
|
+
}
|
|
3212
|
+
return detached;
|
|
3213
|
+
}
|
|
3214
|
+
|
|
3215
|
+
// src/worker/lane.ts
|
|
3216
|
+
function defaultWorkerUrl() {
|
|
3217
|
+
return new URL("./worker/entry.js", import.meta.url);
|
|
3218
|
+
}
|
|
3219
|
+
function transportFromWorker(worker) {
|
|
3220
|
+
return {
|
|
3221
|
+
post: (envelope, transfers) => worker.postMessage(envelope, [...transfers]),
|
|
3222
|
+
onReply: (cb) => {
|
|
3223
|
+
worker.onmessage = (ev) => cb(ev.data);
|
|
3224
|
+
},
|
|
3225
|
+
onError: (cb) => {
|
|
3226
|
+
worker.onerror = (ev) => cb(ev.message !== "" ? ev.message : "worker error");
|
|
3227
|
+
worker.onmessageerror = () => cb("worker messageerror (unclonable reply)");
|
|
3228
|
+
},
|
|
3229
|
+
terminate: () => worker.terminate()
|
|
3230
|
+
};
|
|
3231
|
+
}
|
|
3232
|
+
var WorkerLane = class {
|
|
3233
|
+
options;
|
|
3234
|
+
sequencer = new EnvelopeSequencer();
|
|
3235
|
+
ledger = new RequestLedger();
|
|
3236
|
+
pending = /* @__PURE__ */ new Map();
|
|
3237
|
+
transport = null;
|
|
3238
|
+
/** null = not yet booted; false = boot failed (permanent for this lane). */
|
|
3239
|
+
availableState = null;
|
|
3240
|
+
constructor(options = {}) {
|
|
3241
|
+
this.options = options;
|
|
3242
|
+
}
|
|
3243
|
+
available() {
|
|
3244
|
+
return this.availableState;
|
|
3245
|
+
}
|
|
3246
|
+
/** Synchronous boot probe for callers that must pick a lane NOW (the
|
|
3247
|
+
* instance's sync applyHostUpdate cannot await a rejected request). */
|
|
3248
|
+
ensureBooted() {
|
|
3249
|
+
return this.boot();
|
|
3250
|
+
}
|
|
3251
|
+
/** Boot lazily on first use. A throwing factory (no Worker global, CSP,
|
|
3252
|
+
* missing asset) marks the lane unavailable FOREVER — one diagnostic,
|
|
3253
|
+
* then the caller's main path owns every subsequent request. */
|
|
3254
|
+
boot() {
|
|
3255
|
+
if (this.availableState !== null) return this.availableState;
|
|
3256
|
+
try {
|
|
3257
|
+
let transport = this.options.transport ?? null;
|
|
3258
|
+
if (transport === null) {
|
|
3259
|
+
const factory = this.options.factory;
|
|
3260
|
+
const worker = factory !== void 0 && "create" in factory ? factory.create() : new Worker(
|
|
3261
|
+
factory !== void 0 && "url" in factory ? factory.url : defaultWorkerUrl(),
|
|
3262
|
+
{ type: "module" }
|
|
3263
|
+
);
|
|
3264
|
+
transport = transportFromWorker(worker);
|
|
3265
|
+
}
|
|
3266
|
+
transport.onReply((reply) => this.settle(reply));
|
|
3267
|
+
transport.onError?.((reason) => this.fail(reason));
|
|
3268
|
+
this.transport = transport;
|
|
3269
|
+
this.availableState = true;
|
|
3270
|
+
} catch (err) {
|
|
3271
|
+
this.availableState = false;
|
|
3272
|
+
this.options.onUnavailable?.(err instanceof Error ? err.message : String(err));
|
|
3273
|
+
}
|
|
3274
|
+
return this.availableState;
|
|
3275
|
+
}
|
|
3276
|
+
/** Async worker death (P1: a constructed-but-dead worker must strand
|
|
3277
|
+
* NOTHING): every pending request rejects as 'worker-failed' so callers
|
|
3278
|
+
* run their main-lane fallback, the lane goes permanently unavailable,
|
|
3279
|
+
* and the unavailability callback fires (the instance one-shots it). */
|
|
3280
|
+
fail(reason) {
|
|
3281
|
+
if (this.availableState === false) return;
|
|
3282
|
+
this.availableState = false;
|
|
3283
|
+
this.options.onUnavailable?.(reason);
|
|
3284
|
+
for (const [id, entry] of this.pending) {
|
|
3285
|
+
this.pending.delete(id);
|
|
3286
|
+
entry.reject(new Error("worker-failed"));
|
|
3287
|
+
}
|
|
3288
|
+
this.ledger.abortAll();
|
|
3289
|
+
this.transport?.terminate();
|
|
3290
|
+
this.transport = null;
|
|
3291
|
+
}
|
|
3292
|
+
settle(reply) {
|
|
3293
|
+
const original = this.ledger.settle(reply);
|
|
3294
|
+
if (reply.inReplyTo === void 0) return;
|
|
3295
|
+
const entry = this.pending.get(reply.inReplyTo);
|
|
3296
|
+
this.pending.delete(reply.inReplyTo);
|
|
3297
|
+
if (entry === void 0) return;
|
|
3298
|
+
if (original === null) {
|
|
3299
|
+
entry.reject(new Error("superseded"));
|
|
3300
|
+
return;
|
|
3301
|
+
}
|
|
3302
|
+
entry.resolve(reply);
|
|
3303
|
+
}
|
|
3304
|
+
/**
|
|
3305
|
+
* Send one request. Resolves with the reply envelope (op 'result' or
|
|
3306
|
+
* 'error' — protocol errors are DATA to the caller, not exceptions);
|
|
3307
|
+
* rejects only on supersession/abort/unavailability.
|
|
3308
|
+
*/
|
|
3309
|
+
request(epoch, entity, op, payload, transfers, klass, lane) {
|
|
3310
|
+
if (!this.boot()) {
|
|
3311
|
+
return Promise.reject(new Error("worker-unavailable"));
|
|
3312
|
+
}
|
|
3313
|
+
const envelope = this.sequencer.make(epoch, entity, op, payload);
|
|
3314
|
+
const signal = this.ledger.track(envelope, klass, lane);
|
|
3315
|
+
return new Promise((resolve, reject) => {
|
|
3316
|
+
if (signal.aborted) {
|
|
3317
|
+
reject(new Error("superseded"));
|
|
3318
|
+
return;
|
|
3319
|
+
}
|
|
3320
|
+
signal.addEventListener(
|
|
3321
|
+
"abort",
|
|
3322
|
+
() => {
|
|
3323
|
+
if (this.pending.delete(envelope.msgId)) reject(new Error("superseded"));
|
|
3324
|
+
},
|
|
3325
|
+
{ once: true }
|
|
3326
|
+
);
|
|
3327
|
+
this.pending.set(envelope.msgId, { resolve, reject });
|
|
3328
|
+
this.transport.post(envelope, transfers);
|
|
3329
|
+
});
|
|
3330
|
+
}
|
|
3331
|
+
/** Abort everything in flight (epoch advance / detach / dataset swap).
|
|
3332
|
+
* The ledger's controllers fire each pending promise's abort listener;
|
|
3333
|
+
* the sweep below catches anything tracked before a listener attached —
|
|
3334
|
+
* rejects stay idempotent through the delete guard. */
|
|
3335
|
+
abortAll() {
|
|
3336
|
+
this.ledger.abortAll();
|
|
3337
|
+
for (const [id, entry] of this.pending) {
|
|
3338
|
+
this.pending.delete(id);
|
|
3339
|
+
entry.reject(new Error("aborted"));
|
|
3340
|
+
}
|
|
3341
|
+
}
|
|
3342
|
+
terminate() {
|
|
3343
|
+
this.abortAll();
|
|
3344
|
+
this.transport?.terminate();
|
|
3345
|
+
this.transport = null;
|
|
3346
|
+
this.availableState = null;
|
|
3347
|
+
}
|
|
3348
|
+
};
|
|
3349
|
+
|
|
2598
3350
|
// src/filter.ts
|
|
2599
|
-
function resolveFilterField(item,
|
|
2600
|
-
if (
|
|
3351
|
+
function resolveFilterField(item, field2) {
|
|
3352
|
+
if (field2 === "id") return item.id;
|
|
2601
3353
|
const attrs = item.attrs;
|
|
2602
|
-
return attrs?.[
|
|
3354
|
+
return attrs?.[field2];
|
|
2603
3355
|
}
|
|
2604
3356
|
function filterValuesEqual(a, b) {
|
|
2605
3357
|
if (typeof a === "number" && typeof b === "number") {
|
|
@@ -2769,7 +3521,7 @@ function compileSelector(selector) {
|
|
|
2769
3521
|
};
|
|
2770
3522
|
}
|
|
2771
3523
|
let current = null;
|
|
2772
|
-
const resolve = (
|
|
3524
|
+
const resolve = (field2) => current === null ? void 0 : resolveFilterField(current, field2);
|
|
2773
3525
|
return {
|
|
2774
3526
|
errors,
|
|
2775
3527
|
test(item) {
|
|
@@ -3047,6 +3799,16 @@ var MetricStore = class {
|
|
|
3047
3799
|
/** Admitted async columns by metric name; NaN encodes null. */
|
|
3048
3800
|
columns = /* @__PURE__ */ new Map();
|
|
3049
3801
|
degreePasses = 0;
|
|
3802
|
+
/** §17 telemetry: estimated bytes of metric storage held (S13-T07). */
|
|
3803
|
+
estimatedBytes() {
|
|
3804
|
+
let bytes = 0;
|
|
3805
|
+
for (const col of this.columns.values()) bytes += col.byteLength;
|
|
3806
|
+
const dc = this.degreeCache;
|
|
3807
|
+
if (dc !== null) {
|
|
3808
|
+
bytes += dc.degree.byteLength + dc.inDegree.byteLength + dc.outDegree.byteLength;
|
|
3809
|
+
}
|
|
3810
|
+
return bytes;
|
|
3811
|
+
}
|
|
3050
3812
|
/** Number of combined degree-family compute passes (test observability). */
|
|
3051
3813
|
get degreeComputePasses() {
|
|
3052
3814
|
return this.degreePasses;
|
|
@@ -3278,6 +4040,7 @@ function resolveEnginePolicy(capabilities, requested) {
|
|
|
3278
4040
|
images: imagesNative ? "native" : "placeholder",
|
|
3279
4041
|
linkPicking: capabilities.linkPicking ? "native" : "cpu-fallback",
|
|
3280
4042
|
clusterForce: clusterForceNative ? "native" : "inert",
|
|
4043
|
+
quiescence: capabilities.idleFrames === "stops" ? "stops" : "free-running",
|
|
3281
4044
|
// Defensive snapshot: the policy must not alias the caller's array.
|
|
3282
4045
|
rangedChannels: new Set(capabilities.rangeUpdates),
|
|
3283
4046
|
degradations: Object.freeze(degradations)
|
|
@@ -3302,12 +4065,27 @@ function assertCapabilityMethodParity(engine) {
|
|
|
3302
4065
|
}
|
|
3303
4066
|
function normalizeCommitForCapabilities(commit, capabilities) {
|
|
3304
4067
|
const config = commit.config;
|
|
4068
|
+
const declaredRanged = new Set(capabilities.rangeUpdates);
|
|
4069
|
+
const undeclaredPatches = commit.bufferPatches !== void 0 ? Object.keys(commit.bufferPatches).filter(
|
|
4070
|
+
(ch) => !declaredRanged.has(ch)
|
|
4071
|
+
) : [];
|
|
3305
4072
|
const dropResources = capabilities.pointImages !== true && commit.resources !== void 0;
|
|
3306
4073
|
const dropLinkArrows = capabilities.edgeArrows !== true && config !== void 0 && config.linkArrows !== void 0;
|
|
3307
4074
|
const dropCluster = capabilities.clusterForce !== true && config !== void 0 && config.cluster !== void 0;
|
|
3308
|
-
if (!dropResources && !dropLinkArrows && !dropCluster
|
|
4075
|
+
if (!dropResources && !dropLinkArrows && !dropCluster && undeclaredPatches.length === 0) {
|
|
4076
|
+
return { commit, dropped: [] };
|
|
4077
|
+
}
|
|
3309
4078
|
const dropped = [];
|
|
3310
4079
|
const next = { ...commit };
|
|
4080
|
+
if (undeclaredPatches.length > 0) {
|
|
4081
|
+
const kept = { ...next.bufferPatches };
|
|
4082
|
+
for (const ch of undeclaredPatches) {
|
|
4083
|
+
delete kept[ch];
|
|
4084
|
+
dropped.push(`bufferPatches.${ch}`);
|
|
4085
|
+
}
|
|
4086
|
+
if (Object.keys(kept).length > 0) next.bufferPatches = kept;
|
|
4087
|
+
else delete next.bufferPatches;
|
|
4088
|
+
}
|
|
3311
4089
|
if (dropResources) {
|
|
3312
4090
|
delete next.resources;
|
|
3313
4091
|
dropped.push("resources");
|
|
@@ -3660,7 +4438,7 @@ var Lane = class {
|
|
|
3660
4438
|
}
|
|
3661
4439
|
};
|
|
3662
4440
|
function newMembership(capacity) {
|
|
3663
|
-
return { flags: new Uint8Array(capacity), list: [] };
|
|
4441
|
+
return { flags: new Uint8Array(capacity), list: [], holes: 0 };
|
|
3664
4442
|
}
|
|
3665
4443
|
function growMembership(mem, capacity) {
|
|
3666
4444
|
if (capacity <= mem.flags.length) return;
|
|
@@ -3686,6 +4464,12 @@ var SoftMask = class {
|
|
|
3686
4464
|
overflowedFlag = false;
|
|
3687
4465
|
/** Total memberships currently held across all sources and lanes. */
|
|
3688
4466
|
totalHeld = 0;
|
|
4467
|
+
/** §17 O(Δ) op counters (F10-02 gate instrumentation). */
|
|
4468
|
+
statsBox = {
|
|
4469
|
+
slotsVisited: 0,
|
|
4470
|
+
zeroCrossings: 0,
|
|
4471
|
+
cascadeEdgesVisited: 0
|
|
4472
|
+
};
|
|
3689
4473
|
constructor(nodeCapacity, edgeCapacity) {
|
|
3690
4474
|
checkCapacity(nodeCapacity, "nodeCapacity");
|
|
3691
4475
|
checkCapacity(edgeCapacity, "edgeCapacity");
|
|
@@ -3780,6 +4564,14 @@ var SoftMask = class {
|
|
|
3780
4564
|
this.applyMembership(this.edgeDimLane, state.edgeDim, dimIdx);
|
|
3781
4565
|
}
|
|
3782
4566
|
},
|
|
4567
|
+
updateNodeFailures: (addHide, removeHide, crossings) => {
|
|
4568
|
+
ensureAlive();
|
|
4569
|
+
this.applyMembershipDelta(this.nodeHideLane, state.nodeHide, addHide, removeHide, crossings);
|
|
4570
|
+
},
|
|
4571
|
+
updateEdgeFailures: (addHide, removeHide) => {
|
|
4572
|
+
ensureAlive();
|
|
4573
|
+
this.applyMembershipDelta(this.edgeHideLane, state.edgeHide, addHide, removeHide);
|
|
4574
|
+
},
|
|
3783
4575
|
clear: () => {
|
|
3784
4576
|
ensureAlive();
|
|
3785
4577
|
this.clearSource(state);
|
|
@@ -3833,6 +4625,34 @@ var SoftMask = class {
|
|
|
3833
4625
|
this.cascadeSource ??= this.acquire("\xA79.1 node\u2192edge cascade");
|
|
3834
4626
|
this.cascadeSource.setEdgeFailures(failing);
|
|
3835
4627
|
}
|
|
4628
|
+
/**
|
|
4629
|
+
* O(incident-edges) delta form of the §9.1 cascade (F10-02): for each node
|
|
4630
|
+
* whose HIDE visibility crossed zero, recompute only its incident edges'
|
|
4631
|
+
* cascade state from the CURRENT node counters and apply the delta through
|
|
4632
|
+
* the same internal cascade source the full form uses — the two compose
|
|
4633
|
+
* freely (the full form re-baselines). Edges shared by two crossed nodes
|
|
4634
|
+
* are visited twice; the membership delta is idempotent, so the second
|
|
4635
|
+
* visit is an O(1) no-op. `incidence` must describe the SAME `links`
|
|
4636
|
+
* buffer (edge slot i ↔ links[2i]/[2i+1]).
|
|
4637
|
+
*/
|
|
4638
|
+
applyNodeCascadeToEdgesDelta(links, incidence, crossedNodes) {
|
|
4639
|
+
if (crossedNodes.length === 0) return;
|
|
4640
|
+
const hide = this.nodeHideLane.counters;
|
|
4641
|
+
this.cascadeSource ??= this.acquire("\xA79.1 node\u2192edge cascade");
|
|
4642
|
+
const nowFailing = [];
|
|
4643
|
+
const nowClear = [];
|
|
4644
|
+
for (let k = 0; k < crossedNodes.length; k++) {
|
|
4645
|
+
const edges = incidentEdgesOf(incidence, crossedNodes[k]);
|
|
4646
|
+
for (let j = 0; j < edges.length; j++) {
|
|
4647
|
+
const edge = edges[j];
|
|
4648
|
+
this.statsBox.cascadeEdgesVisited += 1;
|
|
4649
|
+
const failing = hide[links[edge * 2]] !== 0 || hide[links[edge * 2 + 1]] !== 0;
|
|
4650
|
+
if (failing) nowFailing.push(edge);
|
|
4651
|
+
else nowClear.push(edge);
|
|
4652
|
+
}
|
|
4653
|
+
}
|
|
4654
|
+
this.cascadeSource.updateEdgeFailures(nowFailing, nowClear);
|
|
4655
|
+
}
|
|
3836
4656
|
/**
|
|
3837
4657
|
* Drains the zero-crossing dirty lists accumulated since the previous
|
|
3838
4658
|
* drain. Only NET flips are emitted (state compared against the previous
|
|
@@ -3848,6 +4668,27 @@ var SoftMask = class {
|
|
|
3848
4668
|
edgeVisibleCount: this.edgeHideLane.zeroCount
|
|
3849
4669
|
};
|
|
3850
4670
|
}
|
|
4671
|
+
/** §17 telemetry: estimated bytes of mask storage held (S13-T07):
|
|
4672
|
+
* four counter lanes (+pending trackers) and per-source flag columns. */
|
|
4673
|
+
estimatedBytes() {
|
|
4674
|
+
let bytes = 0;
|
|
4675
|
+
for (const lane of [this.nodeHideLane, this.nodeDimLane, this.edgeHideLane, this.edgeDimLane]) {
|
|
4676
|
+
bytes += lane.counters.byteLength + lane.pending.byteLength;
|
|
4677
|
+
}
|
|
4678
|
+
for (const src of this.sources) {
|
|
4679
|
+
bytes += src.nodeHide.flags.byteLength + src.nodeDim.flags.byteLength + src.edgeHide.flags.byteLength + src.edgeDim.flags.byteLength;
|
|
4680
|
+
}
|
|
4681
|
+
return bytes;
|
|
4682
|
+
}
|
|
4683
|
+
/** §17 O(Δ) op counters (live object — snapshot before comparing). */
|
|
4684
|
+
get stats() {
|
|
4685
|
+
return this.statsBox;
|
|
4686
|
+
}
|
|
4687
|
+
resetStats() {
|
|
4688
|
+
this.statsBox.slotsVisited = 0;
|
|
4689
|
+
this.statsBox.zeroCrossings = 0;
|
|
4690
|
+
this.statsBox.cascadeEdgesVisited = 0;
|
|
4691
|
+
}
|
|
3851
4692
|
visibleNodeCount() {
|
|
3852
4693
|
return this.nodeHideLane.zeroCount;
|
|
3853
4694
|
}
|
|
@@ -3912,15 +4753,62 @@ var SoftMask = class {
|
|
|
3912
4753
|
const prev = mem.list;
|
|
3913
4754
|
for (let i = 0; i < prev.length; i++) {
|
|
3914
4755
|
const slot = prev[i];
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
4756
|
+
const f = flags[slot];
|
|
4757
|
+
if ((f & 2) !== 0) continue;
|
|
4758
|
+
if ((f & 1) !== 0) this.decrement(lane, slot);
|
|
4759
|
+
flags[slot] = 0;
|
|
3919
4760
|
}
|
|
3920
4761
|
for (let i = 0; i < next.length; i++) flags[next[i]] = 1;
|
|
3921
4762
|
mem.list = next;
|
|
4763
|
+
mem.holes = 0;
|
|
3922
4764
|
}
|
|
3923
|
-
|
|
4765
|
+
/**
|
|
4766
|
+
* O(Δ) delta ops on one lane membership (F10-02). Adds and removes are
|
|
4767
|
+
* idempotent per slot (adding a member / removing a non-member is a
|
|
4768
|
+
* no-op); removed slots leave HOLES in `list` (compacted past 50%), so
|
|
4769
|
+
* replace/clear passes must honor the bit0 guard above. `crossings`, when
|
|
4770
|
+
* given, is cleared and then receives this CALL's hide zero-crossings.
|
|
4771
|
+
*/
|
|
4772
|
+
applyMembershipDelta(lane, mem, add, remove, crossings) {
|
|
4773
|
+
if (crossings !== void 0) {
|
|
4774
|
+
crossings.becameFailing.length = 0;
|
|
4775
|
+
crossings.becameClear.length = 0;
|
|
4776
|
+
}
|
|
4777
|
+
const capacity = lane.counters.length;
|
|
4778
|
+
const flags = mem.flags;
|
|
4779
|
+
const check = (slot) => {
|
|
4780
|
+
if (!Number.isInteger(slot) || slot < 0 || slot >= capacity) {
|
|
4781
|
+
throw new RangeError(`SoftMask: ${lane.label} slot ${slot} out of range [0, ${capacity})`);
|
|
4782
|
+
}
|
|
4783
|
+
};
|
|
4784
|
+
if (add !== null) for (let i = 0; i < add.length; i++) check(add[i]);
|
|
4785
|
+
if (remove !== null) for (let i = 0; i < remove.length; i++) check(remove[i]);
|
|
4786
|
+
if (add !== null) {
|
|
4787
|
+
for (let i = 0; i < add.length; i++) {
|
|
4788
|
+
const slot = add[i];
|
|
4789
|
+
this.statsBox.slotsVisited += 1;
|
|
4790
|
+
if ((flags[slot] & 1) !== 0) continue;
|
|
4791
|
+
flags[slot] = 1;
|
|
4792
|
+
mem.list.push(slot);
|
|
4793
|
+
this.increment(lane, slot, crossings);
|
|
4794
|
+
}
|
|
4795
|
+
}
|
|
4796
|
+
if (remove !== null) {
|
|
4797
|
+
for (let i = 0; i < remove.length; i++) {
|
|
4798
|
+
const slot = remove[i];
|
|
4799
|
+
this.statsBox.slotsVisited += 1;
|
|
4800
|
+
if ((flags[slot] & 1) === 0) continue;
|
|
4801
|
+
flags[slot] = 0;
|
|
4802
|
+
mem.holes += 1;
|
|
4803
|
+
this.decrement(lane, slot, crossings);
|
|
4804
|
+
}
|
|
4805
|
+
}
|
|
4806
|
+
if (mem.holes > mem.list.length >> 1) {
|
|
4807
|
+
mem.list = mem.list.filter((slot) => (flags[slot] & 1) !== 0);
|
|
4808
|
+
mem.holes = 0;
|
|
4809
|
+
}
|
|
4810
|
+
}
|
|
4811
|
+
increment(lane, slot, crossings) {
|
|
3924
4812
|
this.totalHeld += 1;
|
|
3925
4813
|
const before = lane.counters[slot];
|
|
3926
4814
|
if (before === COUNTER_MAX) {
|
|
@@ -3930,16 +4818,20 @@ var SoftMask = class {
|
|
|
3930
4818
|
lane.counters[slot] = before + 1;
|
|
3931
4819
|
if (before === 0) {
|
|
3932
4820
|
lane.zeroCount -= 1;
|
|
4821
|
+
this.statsBox.zeroCrossings += 1;
|
|
4822
|
+
crossings?.becameFailing.push(slot);
|
|
3933
4823
|
this.markDirty(lane, slot, true);
|
|
3934
4824
|
}
|
|
3935
4825
|
}
|
|
3936
|
-
decrement(lane, slot) {
|
|
4826
|
+
decrement(lane, slot, crossings) {
|
|
3937
4827
|
this.totalHeld -= 1;
|
|
3938
4828
|
const before = lane.counters[slot];
|
|
3939
4829
|
if (before === 0) return;
|
|
3940
4830
|
lane.counters[slot] = before - 1;
|
|
3941
4831
|
if (before === 1) {
|
|
3942
4832
|
lane.zeroCount += 1;
|
|
4833
|
+
this.statsBox.zeroCrossings += 1;
|
|
4834
|
+
crossings?.becameClear.push(slot);
|
|
3943
4835
|
this.markDirty(lane, slot, false);
|
|
3944
4836
|
}
|
|
3945
4837
|
}
|
|
@@ -4355,7 +5247,15 @@ function upperBound(values, sorted, target) {
|
|
|
4355
5247
|
}
|
|
4356
5248
|
var TypedColumnCrossfilter = class {
|
|
4357
5249
|
/** Test instrumentation; see CrossfilterStats. Reset with resetStats(). */
|
|
4358
|
-
stats = {
|
|
5250
|
+
stats = {
|
|
5251
|
+
slotsWalked: 0,
|
|
5252
|
+
fullSorts: 0,
|
|
5253
|
+
permutationMerges: 0,
|
|
5254
|
+
binUpdates: 0,
|
|
5255
|
+
filteredRecomputes: 0
|
|
5256
|
+
};
|
|
5257
|
+
/** Count of dims whose filtered layer is live-maintained (F10-02). */
|
|
5258
|
+
liveDims = 0;
|
|
4359
5259
|
dims = [];
|
|
4360
5260
|
byKey = /* @__PURE__ */ new Map();
|
|
4361
5261
|
specs = [];
|
|
@@ -4377,6 +5277,29 @@ var TypedColumnCrossfilter = class {
|
|
|
4377
5277
|
this.stats.slotsWalked = 0;
|
|
4378
5278
|
this.stats.fullSorts = 0;
|
|
4379
5279
|
this.stats.permutationMerges = 0;
|
|
5280
|
+
this.stats.binUpdates = 0;
|
|
5281
|
+
this.stats.filteredRecomputes = 0;
|
|
5282
|
+
}
|
|
5283
|
+
/** §17 telemetry: estimated bytes of typed-column storage held (S13-T07).
|
|
5284
|
+
* Documented components: per-dim value/permutation/bin/code/pass arrays,
|
|
5285
|
+
* the global failure counter, and the external mask. */
|
|
5286
|
+
estimatedBytes() {
|
|
5287
|
+
let bytes = this.failCount.byteLength + (this.externalMask?.byteLength ?? 0);
|
|
5288
|
+
for (const d of this.dims) {
|
|
5289
|
+
bytes += d.pass.byteLength;
|
|
5290
|
+
if (d.kind === "categorical") {
|
|
5291
|
+
bytes += d.codes.byteLength;
|
|
5292
|
+
} else {
|
|
5293
|
+
bytes += d.values.byteLength + d.sorted.byteLength + d.slotBin.byteLength;
|
|
5294
|
+
}
|
|
5295
|
+
}
|
|
5296
|
+
return bytes;
|
|
5297
|
+
}
|
|
5298
|
+
/** F10-02 live-layer bookkeeping — the ONLY writer of `filteredLive`. */
|
|
5299
|
+
setFilteredLive(dim, live) {
|
|
5300
|
+
if (dim.filteredLive === live) return;
|
|
5301
|
+
dim.filteredLive = live;
|
|
5302
|
+
this.liveDims += live ? 1 : -1;
|
|
4380
5303
|
}
|
|
4381
5304
|
/**
|
|
4382
5305
|
* (Re)initialize columns from scratch. Clears all brushes and the external
|
|
@@ -4426,7 +5349,9 @@ var TypedColumnCrossfilter = class {
|
|
|
4426
5349
|
this.applyRangeTransition(dim, normalized.brush, delta);
|
|
4427
5350
|
}
|
|
4428
5351
|
dim.brush = normalized.brush;
|
|
4429
|
-
for (const other of this.dims)
|
|
5352
|
+
for (const other of this.dims) {
|
|
5353
|
+
if (other !== dim && !other.filteredLive) other.filteredDirty = true;
|
|
5354
|
+
}
|
|
4430
5355
|
this.revision++;
|
|
4431
5356
|
this.notify();
|
|
4432
5357
|
return delta;
|
|
@@ -4452,7 +5377,10 @@ var TypedColumnCrossfilter = class {
|
|
|
4452
5377
|
if (unchanged) return;
|
|
4453
5378
|
this.externalMask = Uint8Array.from(passSlots);
|
|
4454
5379
|
}
|
|
4455
|
-
for (const d of this.dims)
|
|
5380
|
+
for (const d of this.dims) {
|
|
5381
|
+
this.setFilteredLive(d, false);
|
|
5382
|
+
d.filteredDirty = true;
|
|
5383
|
+
}
|
|
4456
5384
|
this.notify();
|
|
4457
5385
|
}
|
|
4458
5386
|
/**
|
|
@@ -4466,6 +5394,7 @@ var TypedColumnCrossfilter = class {
|
|
|
4466
5394
|
if (dim.filteredDirty) {
|
|
4467
5395
|
this.recomputeFiltered(dim);
|
|
4468
5396
|
dim.filteredDirty = false;
|
|
5397
|
+
this.setFilteredLive(dim, true);
|
|
4469
5398
|
}
|
|
4470
5399
|
const bins = [];
|
|
4471
5400
|
const categories = [];
|
|
@@ -4530,13 +5459,16 @@ var TypedColumnCrossfilter = class {
|
|
|
4530
5459
|
this.externalMask = em;
|
|
4531
5460
|
}
|
|
4532
5461
|
this.n = newN;
|
|
5462
|
+
for (const dim of this.dims) {
|
|
5463
|
+
this.setFilteredLive(dim, false);
|
|
5464
|
+
dim.filteredDirty = true;
|
|
5465
|
+
}
|
|
4533
5466
|
for (const dim of this.dims) {
|
|
4534
5467
|
const pass = new Uint8Array(newN).fill(1);
|
|
4535
5468
|
pass.set(dim.pass);
|
|
4536
5469
|
dim.pass = pass;
|
|
4537
5470
|
if (dim.kind === "categorical") this.appendCat(dim, newNodes, oldN);
|
|
4538
5471
|
else this.appendRange(dim, newNodes, oldN);
|
|
4539
|
-
dim.filteredDirty = true;
|
|
4540
5472
|
}
|
|
4541
5473
|
for (let s = oldN; s < newN; s++) {
|
|
4542
5474
|
(this.failCount[s] === 0 ? delta.shown : delta.hidden).push(s);
|
|
@@ -4600,6 +5532,7 @@ var TypedColumnCrossfilter = class {
|
|
|
4600
5532
|
return dim;
|
|
4601
5533
|
}
|
|
4602
5534
|
buildDims(nodes, specs) {
|
|
5535
|
+
this.liveDims = 0;
|
|
4603
5536
|
const seen = /* @__PURE__ */ new Set();
|
|
4604
5537
|
const dims = [];
|
|
4605
5538
|
for (const spec of specs) {
|
|
@@ -4655,6 +5588,7 @@ var TypedColumnCrossfilter = class {
|
|
|
4655
5588
|
invalidSlots,
|
|
4656
5589
|
brush: null,
|
|
4657
5590
|
filteredDirty: true,
|
|
5591
|
+
filteredLive: false,
|
|
4658
5592
|
filteredBins: []
|
|
4659
5593
|
};
|
|
4660
5594
|
this.rebinRange(dim);
|
|
@@ -4696,6 +5630,7 @@ var TypedColumnCrossfilter = class {
|
|
|
4696
5630
|
invalidSlots,
|
|
4697
5631
|
brush: null,
|
|
4698
5632
|
filteredDirty: true,
|
|
5633
|
+
filteredLive: false,
|
|
4699
5634
|
filteredCats: []
|
|
4700
5635
|
};
|
|
4701
5636
|
}
|
|
@@ -4744,14 +5679,59 @@ var TypedColumnCrossfilter = class {
|
|
|
4744
5679
|
const next = this.failCount[slot] - 1;
|
|
4745
5680
|
this.failCount[slot] = next;
|
|
4746
5681
|
if (next === 0) delta.shown.push(slot);
|
|
5682
|
+
if (this.liveDims > 0) this.maintainFiltered(dim, slot, next, 1);
|
|
4747
5683
|
} else {
|
|
4748
5684
|
if (dim.pass[slot] === 0) return;
|
|
4749
5685
|
dim.pass[slot] = 0;
|
|
4750
5686
|
const prev = this.failCount[slot];
|
|
4751
5687
|
this.failCount[slot] = prev + 1;
|
|
4752
5688
|
if (prev === 0) delta.hidden.push(slot);
|
|
5689
|
+
if (this.liveDims > 0) this.maintainFiltered(dim, slot, prev, -1);
|
|
4753
5690
|
}
|
|
4754
5691
|
}
|
|
5692
|
+
/**
|
|
5693
|
+
* F10-02 inline maintenance of LIVE filtered layers, dispatched from the
|
|
5694
|
+
* one place that knows the failCount transition. `boundary` is the
|
|
5695
|
+
* other-failures picture at the interesting side of the flip (after for
|
|
5696
|
+
* shown, before for hidden):
|
|
5697
|
+
* - 0 → the slot crossed the FULLY-VISIBLE boundary: every other live
|
|
5698
|
+
* layer counts it (own layers ignore the own-dim brush, so the brushed
|
|
5699
|
+
* dim's layer is provably unchanged by its own flip);
|
|
5700
|
+
* - 1 → exactly one OTHER dim still fails the slot: only that dim's
|
|
5701
|
+
* what-if-I-cleared-mine layer flips;
|
|
5702
|
+
* - ≥2 → no layer can change.
|
|
5703
|
+
* External-mask-excluded and hygiene-invalid slots contribute nothing
|
|
5704
|
+
* either way and are skipped.
|
|
5705
|
+
*/
|
|
5706
|
+
maintainFiltered(brushed, slot, boundary, sign) {
|
|
5707
|
+
if (boundary > 1) return;
|
|
5708
|
+
const ext = this.externalMask;
|
|
5709
|
+
if (ext !== null && ext[slot] === 0) return;
|
|
5710
|
+
if (boundary === 0) {
|
|
5711
|
+
for (const d of this.dims) {
|
|
5712
|
+
if (d === brushed || !d.filteredLive) continue;
|
|
5713
|
+
this.adjustLayer(d, slot, sign);
|
|
5714
|
+
}
|
|
5715
|
+
return;
|
|
5716
|
+
}
|
|
5717
|
+
for (const d of this.dims) {
|
|
5718
|
+
if (d === brushed || d.pass[slot] !== 0) continue;
|
|
5719
|
+
if (d.filteredLive) this.adjustLayer(d, slot, sign);
|
|
5720
|
+
break;
|
|
5721
|
+
}
|
|
5722
|
+
}
|
|
5723
|
+
adjustLayer(d, slot, sign) {
|
|
5724
|
+
if (d.kind === "categorical") {
|
|
5725
|
+
const c = d.codes[slot];
|
|
5726
|
+
if (c < 0) return;
|
|
5727
|
+
d.filteredCats[c] = (d.filteredCats[c] ?? 0) + sign;
|
|
5728
|
+
} else {
|
|
5729
|
+
const b = d.slotBin[slot];
|
|
5730
|
+
if (b < 0) return;
|
|
5731
|
+
d.filteredBins[b] = (d.filteredBins[b] ?? 0) + sign;
|
|
5732
|
+
}
|
|
5733
|
+
this.stats.binUpdates += 1;
|
|
5734
|
+
}
|
|
4755
5735
|
walkSorted(dim, from, to, nowPass, delta) {
|
|
4756
5736
|
for (let i = from; i < to; i++) this.flip(dim, dim.sorted[i], nowPass, delta);
|
|
4757
5737
|
}
|
|
@@ -4919,6 +5899,7 @@ var TypedColumnCrossfilter = class {
|
|
|
4919
5899
|
}
|
|
4920
5900
|
// --- lazy filtered layer -----------------------------------------------------
|
|
4921
5901
|
recomputeFiltered(dim) {
|
|
5902
|
+
this.stats.filteredRecomputes += 1;
|
|
4922
5903
|
const ext = this.externalMask;
|
|
4923
5904
|
const fc = this.failCount;
|
|
4924
5905
|
if (dim.kind === "categorical") {
|
|
@@ -5250,7 +6231,8 @@ var GRAPH_THEME_DARK = Object.freeze({
|
|
|
5250
6231
|
edgeDefault: "rgba(255,255,255,0.15)",
|
|
5251
6232
|
labelFg: "#e6e9f0",
|
|
5252
6233
|
accent: "#3b82f6",
|
|
5253
|
-
mutedAlpha: 0.15
|
|
6234
|
+
mutedAlpha: 0.15,
|
|
6235
|
+
emphasisRing: "#7aa2f7"
|
|
5254
6236
|
});
|
|
5255
6237
|
var GRAPH_THEME_LIGHT = Object.freeze({
|
|
5256
6238
|
background: "#ffffff",
|
|
@@ -5258,7 +6240,8 @@ var GRAPH_THEME_LIGHT = Object.freeze({
|
|
|
5258
6240
|
edgeDefault: "rgba(15,23,42,0.18)",
|
|
5259
6241
|
labelFg: "#0f172a",
|
|
5260
6242
|
accent: "#2563eb",
|
|
5261
|
-
mutedAlpha: 0.2
|
|
6243
|
+
mutedAlpha: 0.2,
|
|
6244
|
+
emphasisRing: "#2563eb"
|
|
5262
6245
|
});
|
|
5263
6246
|
function resolveTheme(input) {
|
|
5264
6247
|
const base = input !== void 0 && input.base === "light" ? GRAPH_THEME_LIGHT : GRAPH_THEME_DARK;
|
|
@@ -5270,10 +6253,11 @@ function resolveTheme(input) {
|
|
|
5270
6253
|
if (input.labelFg !== void 0) out.labelFg = input.labelFg;
|
|
5271
6254
|
if (input.accent !== void 0) out.accent = input.accent;
|
|
5272
6255
|
if (input.mutedAlpha !== void 0) out.mutedAlpha = input.mutedAlpha;
|
|
6256
|
+
if (input.emphasisRing !== void 0) out.emphasisRing = input.emphasisRing;
|
|
5273
6257
|
return out;
|
|
5274
6258
|
}
|
|
5275
6259
|
function sameTheme(a, b) {
|
|
5276
|
-
return a.background === b.background && a.nodeDefault === b.nodeDefault && a.edgeDefault === b.edgeDefault && a.labelFg === b.labelFg && a.accent === b.accent && a.mutedAlpha === b.mutedAlpha;
|
|
6260
|
+
return a.background === b.background && a.nodeDefault === b.nodeDefault && a.edgeDefault === b.edgeDefault && a.labelFg === b.labelFg && a.accent === b.accent && a.mutedAlpha === b.mutedAlpha && a.emphasisRing === b.emphasisRing;
|
|
5277
6261
|
}
|
|
5278
6262
|
var ZOOM_STEP = 1.5;
|
|
5279
6263
|
var EMPTY_IDS = [];
|
|
@@ -5300,6 +6284,9 @@ var TIMELINE_COALESCE_WINDOW_MS = Number.MAX_SAFE_INTEGER;
|
|
|
5300
6284
|
var DEV = Boolean(import.meta.env?.DEV);
|
|
5301
6285
|
var nowMs = () => Date.now();
|
|
5302
6286
|
var DEFAULT_RGBA = [0.66, 0.66, 0.66, 1];
|
|
6287
|
+
var PERF_SAMPLE_THROTTLE_MS = 1e3;
|
|
6288
|
+
var FRAME_PRESSURE_ENGAGE_MS = 40;
|
|
6289
|
+
var FRAME_PRESSURE_CLEAR_MS = 28;
|
|
5303
6290
|
var LABEL_RERANK_THROTTLE_MS = 100;
|
|
5304
6291
|
var SIM_HOT_REFRESH_MS = 500;
|
|
5305
6292
|
function sameIds(a, b) {
|
|
@@ -5372,6 +6359,7 @@ function createGraphInstance(opts) {
|
|
|
5372
6359
|
let overlayIdSeq = 0;
|
|
5373
6360
|
let edgeIndexById = EMPTY_INDEX2;
|
|
5374
6361
|
let adjacency = null;
|
|
6362
|
+
let sceneIncidence = null;
|
|
5375
6363
|
let lastLinkWidths = null;
|
|
5376
6364
|
let scopeSpec = null;
|
|
5377
6365
|
const scopeExtraIds = /* @__PURE__ */ new Set();
|
|
@@ -5505,6 +6493,8 @@ function createGraphInstance(opts) {
|
|
|
5505
6493
|
let theme = resolveTheme(void 0);
|
|
5506
6494
|
let edgeArrows = false;
|
|
5507
6495
|
let showLinks = true;
|
|
6496
|
+
let emphasisRingOn = true;
|
|
6497
|
+
let emphasizedNodeId = null;
|
|
5508
6498
|
let nodeImage;
|
|
5509
6499
|
let labelsConfig;
|
|
5510
6500
|
const metricStore = new MetricStore();
|
|
@@ -5534,11 +6524,127 @@ function createGraphInstance(opts) {
|
|
|
5534
6524
|
const positionSubs = /* @__PURE__ */ new Set();
|
|
5535
6525
|
let currentPlacements = [];
|
|
5536
6526
|
let labelPositionCache = null;
|
|
6527
|
+
let frameCadence = 0;
|
|
6528
|
+
let quiescenceAsserted = false;
|
|
6529
|
+
let quiescenceTimer = null;
|
|
5537
6530
|
let lastHotRefreshMs = null;
|
|
5538
6531
|
let rerankTimer = null;
|
|
5539
6532
|
let lastOverloadCount = 0;
|
|
5540
6533
|
const projectScratch = [0, 0];
|
|
5541
6534
|
let dataDiags = [];
|
|
6535
|
+
let columnarDiags = [];
|
|
6536
|
+
const executionMode = opts.execution ?? "main";
|
|
6537
|
+
let workerLane = null;
|
|
6538
|
+
let workerDiags = [];
|
|
6539
|
+
let workerUnavailableReported = false;
|
|
6540
|
+
let pendingDeriveToken = 0;
|
|
6541
|
+
function workerEligible() {
|
|
6542
|
+
if (executionMode === "main") return false;
|
|
6543
|
+
if (workerLane === null) {
|
|
6544
|
+
workerLane = new WorkerLane({
|
|
6545
|
+
...opts.workerFactory !== void 0 ? { factory: opts.workerFactory } : {},
|
|
6546
|
+
onUnavailable: (reason) => {
|
|
6547
|
+
if (workerUnavailableReported) return;
|
|
6548
|
+
workerUnavailableReported = true;
|
|
6549
|
+
workerDiags = [
|
|
6550
|
+
{
|
|
6551
|
+
code: "worker-unavailable",
|
|
6552
|
+
severity: executionMode === "worker" ? "error" : "info",
|
|
6553
|
+
count: 1,
|
|
6554
|
+
sampleIds: [],
|
|
6555
|
+
message: `worker lane unavailable (${reason}) \u2014 columnar acceptance runs on the main lane`
|
|
6556
|
+
}
|
|
6557
|
+
];
|
|
6558
|
+
}
|
|
6559
|
+
});
|
|
6560
|
+
}
|
|
6561
|
+
return workerLane.ensureBooted();
|
|
6562
|
+
}
|
|
6563
|
+
function scheduleWorkerAcceptance(columnar, deferredMetrics) {
|
|
6564
|
+
const lane = workerLane;
|
|
6565
|
+
pendingDeriveToken += 1;
|
|
6566
|
+
const token = pendingDeriveToken;
|
|
6567
|
+
const payload = {
|
|
6568
|
+
nodeIdTable: encodeStringTable(columnar.nodes.ids.dictionary),
|
|
6569
|
+
nodeIdCodes: columnar.nodes.ids.codes.slice(),
|
|
6570
|
+
nodeCount: columnar.nodes.length,
|
|
6571
|
+
edgeIdTable: encodeStringTable(columnar.edges.ids.dictionary),
|
|
6572
|
+
edgeIdCodes: columnar.edges.ids.codes.slice(),
|
|
6573
|
+
edgeSource: columnar.edges.source.slice(),
|
|
6574
|
+
edgeTarget: columnar.edges.target.slice(),
|
|
6575
|
+
edgeCount: columnar.edges.length
|
|
6576
|
+
};
|
|
6577
|
+
const transfers = collectTransfers([
|
|
6578
|
+
payload.nodeIdTable.offsets,
|
|
6579
|
+
payload.nodeIdTable.bytes,
|
|
6580
|
+
payload.nodeIdCodes,
|
|
6581
|
+
payload.edgeIdTable.offsets,
|
|
6582
|
+
payload.edgeIdTable.bytes,
|
|
6583
|
+
payload.edgeIdCodes,
|
|
6584
|
+
payload.edgeSource,
|
|
6585
|
+
payload.edgeTarget
|
|
6586
|
+
]);
|
|
6587
|
+
void lane.request(token, "scene", "derive-columnar", payload, transfers, "guaranteed", "derive").then((reply) => {
|
|
6588
|
+
if (destroyed || token !== pendingDeriveToken) return;
|
|
6589
|
+
if (reply.op !== "result") {
|
|
6590
|
+
workerDiags = [
|
|
6591
|
+
{
|
|
6592
|
+
code: "worker-unavailable",
|
|
6593
|
+
severity: "error",
|
|
6594
|
+
count: 1,
|
|
6595
|
+
sampleIds: [],
|
|
6596
|
+
message: `worker derive failed (${String(reply.payload.message)}) \u2014 snapshot dropped`
|
|
6597
|
+
}
|
|
6598
|
+
];
|
|
6599
|
+
publish({ diagnostics: composeDiagnostics() });
|
|
6600
|
+
return;
|
|
6601
|
+
}
|
|
6602
|
+
const acceptance = reply.payload;
|
|
6603
|
+
acceptanceQueue.admit(() => {
|
|
6604
|
+
if (destroyed || token !== pendingDeriveToken) return;
|
|
6605
|
+
const mutated = acceptance.keepNodes.length !== columnar.nodes.length || acceptance.keepEdges.length !== columnar.edges.length || validateColumnarStructure(columnar).length > 0;
|
|
6606
|
+
if (mutated) {
|
|
6607
|
+
columnarDiags = [
|
|
6608
|
+
{
|
|
6609
|
+
code: "invalid-columnar-snapshot",
|
|
6610
|
+
severity: "error",
|
|
6611
|
+
count: 1,
|
|
6612
|
+
sampleIds: [],
|
|
6613
|
+
message: "columnar snapshot mutated while worker acceptance was pending \u2014 rejected whole (\xA75: source coordinates are immutable; publish a new sourceRevision)"
|
|
6614
|
+
}
|
|
6615
|
+
];
|
|
6616
|
+
publish({ diagnostics: composeDiagnostics() });
|
|
6617
|
+
return;
|
|
6618
|
+
}
|
|
6619
|
+
const preAccepted = buildAcceptedFromColumnar(columnar, acceptance);
|
|
6620
|
+
applyHostUpdateInner(
|
|
6621
|
+
{
|
|
6622
|
+
data: {
|
|
6623
|
+
datasetKey: columnar.datasetKey,
|
|
6624
|
+
sourceRevision: columnar.sourceRevision,
|
|
6625
|
+
nodes: [],
|
|
6626
|
+
edges: []
|
|
6627
|
+
},
|
|
6628
|
+
...deferredMetrics !== void 0 ? { metrics: deferredMetrics } : {}
|
|
6629
|
+
},
|
|
6630
|
+
preAccepted
|
|
6631
|
+
);
|
|
6632
|
+
if (columnar.bufferOwnership === "transfer") detachColumnarBuffers(columnar);
|
|
6633
|
+
});
|
|
6634
|
+
}).catch((err) => {
|
|
6635
|
+
if (destroyed || token !== pendingDeriveToken) return;
|
|
6636
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6637
|
+
if (message !== "worker-unavailable" && message !== "worker-failed") return;
|
|
6638
|
+
acceptanceQueue.admit(() => {
|
|
6639
|
+
if (destroyed || token !== pendingDeriveToken) return;
|
|
6640
|
+
applyHostUpdateInner({
|
|
6641
|
+
data: materializeColumnarSnapshot(columnar),
|
|
6642
|
+
...deferredMetrics !== void 0 ? { metrics: deferredMetrics } : {}
|
|
6643
|
+
});
|
|
6644
|
+
if (columnar.bufferOwnership === "transfer") detachColumnarBuffers(columnar);
|
|
6645
|
+
});
|
|
6646
|
+
});
|
|
6647
|
+
}
|
|
5542
6648
|
let nodeColorDiags = [];
|
|
5543
6649
|
let nodeSizeDiags = [];
|
|
5544
6650
|
let linkColorDiags = [];
|
|
@@ -5602,6 +6708,46 @@ function createGraphInstance(opts) {
|
|
|
5602
6708
|
}
|
|
5603
6709
|
pendingHistoryDepths = null;
|
|
5604
6710
|
store.setState((prev) => ({ ...prev, ...patch }));
|
|
6711
|
+
if (patch.visible !== void 0 || patch.nodeCount !== void 0) {
|
|
6712
|
+
evaluateLadderCounts();
|
|
6713
|
+
}
|
|
6714
|
+
}
|
|
6715
|
+
function evaluateLadderCounts() {
|
|
6716
|
+
if (destroyed) return;
|
|
6717
|
+
const vis = store.getState().visible;
|
|
6718
|
+
const events = degradeController.evaluateCounts({ nodes: vis.nodes, edges: vis.edges });
|
|
6719
|
+
for (const event of events) applyDegradeEvent(event);
|
|
6720
|
+
}
|
|
6721
|
+
function applyDegradeEvent(event) {
|
|
6722
|
+
if (event.step === "cap-dom-labels") {
|
|
6723
|
+
if (event.engaged && !capLabelsNudged) {
|
|
6724
|
+
capLabelsNudged = true;
|
|
6725
|
+
console.warn(
|
|
6726
|
+
"orbit: cap-dom-labels engaged \u2014 DOM labels are hard-capped at the label budget above limits.domLabelNodes visible nodes (\xA717). A GPU label lane (labelStrategy 'sdf') is the planned scale path (S13-T05, deferred)."
|
|
6727
|
+
);
|
|
6728
|
+
}
|
|
6729
|
+
scheduleViewportRerank();
|
|
6730
|
+
}
|
|
6731
|
+
if (event.step === "batch-histograms" && !event.engaged) {
|
|
6732
|
+
flushCrossfilterNotify();
|
|
6733
|
+
}
|
|
6734
|
+
if (event.step === "defer-images" && !event.engaged && imageRefsDeferred) {
|
|
6735
|
+
pushImageRefs();
|
|
6736
|
+
}
|
|
6737
|
+
if (event.step === "disable-transitions") {
|
|
6738
|
+
const eng = engineIfReady();
|
|
6739
|
+
if (eng !== null) {
|
|
6740
|
+
const revisions = { ...store.getState().revisions };
|
|
6741
|
+
revisions.render += 1;
|
|
6742
|
+
commitToEngine(eng, {
|
|
6743
|
+
revision: revisions.render,
|
|
6744
|
+
config: { transitionDurationMs: event.engaged ? 0 : null }
|
|
6745
|
+
});
|
|
6746
|
+
revisions.appliedRender = eng.appliedRevision();
|
|
6747
|
+
store.setState((prev) => ({ ...prev, revisions }));
|
|
6748
|
+
}
|
|
6749
|
+
}
|
|
6750
|
+
emit("degrade", event);
|
|
5605
6751
|
}
|
|
5606
6752
|
function emitInstanceError(detail, phase, cause) {
|
|
5607
6753
|
engineDiags = [
|
|
@@ -6019,6 +7165,9 @@ function createGraphInstance(opts) {
|
|
|
6019
7165
|
return k === void 0 ? void 0 : accepted.edges[k];
|
|
6020
7166
|
}
|
|
6021
7167
|
function applyEdgeHover(edge, transitionsOnly) {
|
|
7168
|
+
if (edge !== null && degradeController.isEngaged("defer-link-picking") && store.getState().simulationRunning) {
|
|
7169
|
+
return;
|
|
7170
|
+
}
|
|
6022
7171
|
const edgeId = edge === null ? null : edge.id;
|
|
6023
7172
|
const hover = store.getState().hover;
|
|
6024
7173
|
const changed = hover.edgeId !== edgeId;
|
|
@@ -6061,6 +7210,8 @@ function createGraphInstance(opts) {
|
|
|
6061
7210
|
function composeDiagnostics() {
|
|
6062
7211
|
return [
|
|
6063
7212
|
...dataDiags,
|
|
7213
|
+
...columnarDiags,
|
|
7214
|
+
...workerDiags,
|
|
6064
7215
|
...ingestMergeDiags,
|
|
6065
7216
|
...ingestCommitDiags.map((e) => e.diag),
|
|
6066
7217
|
...nodeColorDiags,
|
|
@@ -6112,7 +7263,60 @@ function createGraphInstance(opts) {
|
|
|
6112
7263
|
baseLinkColors = null;
|
|
6113
7264
|
basePointColorsSynthesized = false;
|
|
6114
7265
|
baseLinkColorsSynthesized = false;
|
|
6115
|
-
|
|
7266
|
+
nodeAlphaComposer.reset();
|
|
7267
|
+
edgeAlphaComposer.reset();
|
|
7268
|
+
sceneIncidence = null;
|
|
7269
|
+
}
|
|
7270
|
+
const brushCrossings = { becameFailing: [], becameClear: [] };
|
|
7271
|
+
const brushSceneAdd = [];
|
|
7272
|
+
const brushSceneRemove = [];
|
|
7273
|
+
const nodeAlphaComposer = new IncrementalAlphaComposer();
|
|
7274
|
+
const edgeAlphaComposer = new IncrementalAlphaComposer();
|
|
7275
|
+
const PATCH_FULL_UPLOAD_RATIO = 0.5;
|
|
7276
|
+
function buildAlphaPatches(buf, slotLists, stride) {
|
|
7277
|
+
const slots = [];
|
|
7278
|
+
for (const list of slotLists) for (const slot of list) slots.push(slot);
|
|
7279
|
+
slots.sort((a, b) => a - b);
|
|
7280
|
+
const patches = [];
|
|
7281
|
+
let runStart = -1;
|
|
7282
|
+
let runEnd = -1;
|
|
7283
|
+
for (const slot of slots) {
|
|
7284
|
+
if (slot < runEnd) continue;
|
|
7285
|
+
if (slot === runEnd) {
|
|
7286
|
+
runEnd = slot + 1;
|
|
7287
|
+
continue;
|
|
7288
|
+
}
|
|
7289
|
+
if (runStart >= 0) {
|
|
7290
|
+
patches.push({
|
|
7291
|
+
start: runStart * stride,
|
|
7292
|
+
data: buf.subarray(runStart * stride, runEnd * stride)
|
|
7293
|
+
});
|
|
7294
|
+
}
|
|
7295
|
+
runStart = slot;
|
|
7296
|
+
runEnd = slot + 1;
|
|
7297
|
+
}
|
|
7298
|
+
if (runStart >= 0) {
|
|
7299
|
+
patches.push({
|
|
7300
|
+
start: runStart * stride,
|
|
7301
|
+
data: buf.subarray(runStart * stride, runEnd * stride)
|
|
7302
|
+
});
|
|
7303
|
+
}
|
|
7304
|
+
return patches;
|
|
7305
|
+
}
|
|
7306
|
+
const pressureSampler = new PressureSampler();
|
|
7307
|
+
const resolvedLimits = resolveScaleLimits(opts.limits);
|
|
7308
|
+
const degradeController = new DegradeController(resolvedLimits.limits, () => nowMs());
|
|
7309
|
+
let capLabelsNudged = false;
|
|
7310
|
+
let imageRefsDeferred = false;
|
|
7311
|
+
let lastCommitMs;
|
|
7312
|
+
let lastPerfSampleAt = -Infinity;
|
|
7313
|
+
const perfCounters = {
|
|
7314
|
+
brushSlotsTranslated: 0,
|
|
7315
|
+
fullBrushRefreshes: 0,
|
|
7316
|
+
fullCascades: 0,
|
|
7317
|
+
fullNodeRecomposes: 0,
|
|
7318
|
+
fullEdgeRecomposes: 0
|
|
7319
|
+
};
|
|
6116
7320
|
function evaluateFilterMembership() {
|
|
6117
7321
|
if (softMask === null || maskFilterSource === null) return;
|
|
6118
7322
|
const model = sceneModel();
|
|
@@ -6190,6 +7394,7 @@ function createGraphInstance(opts) {
|
|
|
6190
7394
|
}
|
|
6191
7395
|
function refreshBrushMembership() {
|
|
6192
7396
|
if (softMask === null || maskBrushSource === null || scene === null) return;
|
|
7397
|
+
perfCounters.fullBrushRefreshes += 1;
|
|
6193
7398
|
const slots = [];
|
|
6194
7399
|
for (const s of crossfilterHiddenBase) {
|
|
6195
7400
|
const id = crossfilterRowIds[s];
|
|
@@ -6199,8 +7404,45 @@ function createGraphInstance(opts) {
|
|
|
6199
7404
|
}
|
|
6200
7405
|
maskBrushSource.setNodeFailures(slots, null);
|
|
6201
7406
|
}
|
|
7407
|
+
function refreshBrushMembershipDelta(delta) {
|
|
7408
|
+
if (softMask === null || maskBrushSource === null || scene === null) return false;
|
|
7409
|
+
if (groupRewrite !== null) return false;
|
|
7410
|
+
const identity = accepted !== null && sceneModel() === accepted && crossfilterRowsRef === accepted.nodes;
|
|
7411
|
+
const sceneRef = scene;
|
|
7412
|
+
const translate = (baseSlots, out) => {
|
|
7413
|
+
out.length = 0;
|
|
7414
|
+
for (let i = 0; i < baseSlots.length; i++) {
|
|
7415
|
+
const s = baseSlots[i];
|
|
7416
|
+
perfCounters.brushSlotsTranslated += 1;
|
|
7417
|
+
if (identity) {
|
|
7418
|
+
if (s < sceneRef.count) out.push(s);
|
|
7419
|
+
continue;
|
|
7420
|
+
}
|
|
7421
|
+
const id = crossfilterRowIds[s];
|
|
7422
|
+
if (id === void 0) continue;
|
|
7423
|
+
const idx = sceneRef.indexById.get(id);
|
|
7424
|
+
if (idx !== void 0) out.push(idx);
|
|
7425
|
+
}
|
|
7426
|
+
};
|
|
7427
|
+
translate(delta.hidden, brushSceneAdd);
|
|
7428
|
+
translate(delta.shown, brushSceneRemove);
|
|
7429
|
+
maskBrushSource.updateNodeFailures(brushSceneAdd, brushSceneRemove, brushCrossings);
|
|
7430
|
+
return true;
|
|
7431
|
+
}
|
|
6202
7432
|
function cascadeNodeMask() {
|
|
6203
|
-
if (softMask !== null && scene !== null)
|
|
7433
|
+
if (softMask !== null && scene !== null) {
|
|
7434
|
+
perfCounters.fullCascades += 1;
|
|
7435
|
+
softMask.applyNodeCascadeToEdges(scene.links);
|
|
7436
|
+
}
|
|
7437
|
+
}
|
|
7438
|
+
function cascadeNodeMaskDelta() {
|
|
7439
|
+
if (softMask === null || scene === null) return;
|
|
7440
|
+
const failing = brushCrossings.becameFailing;
|
|
7441
|
+
const cleared = brushCrossings.becameClear;
|
|
7442
|
+
if (failing.length === 0 && cleared.length === 0) return;
|
|
7443
|
+
sceneIncidence ??= buildIncidence(scene.links, scene.count);
|
|
7444
|
+
const crossed = failing.length === 0 ? cleared : cleared.length === 0 ? failing : [...failing, ...cleared];
|
|
7445
|
+
softMask.applyNodeCascadeToEdgesDelta(scene.links, sceneIncidence, crossed);
|
|
6204
7446
|
}
|
|
6205
7447
|
function refreshGroupMaskMembership(hidden) {
|
|
6206
7448
|
if (softMask === null || maskGroupSource === null) return;
|
|
@@ -6289,6 +7531,8 @@ function createGraphInstance(opts) {
|
|
|
6289
7531
|
refreshGroupMaskMembership(hidden);
|
|
6290
7532
|
cascadeNodeMask();
|
|
6291
7533
|
softMask.drainDirty();
|
|
7534
|
+
nodeAlphaComposer.reset();
|
|
7535
|
+
edgeAlphaComposer.reset();
|
|
6292
7536
|
return filterDiags !== before;
|
|
6293
7537
|
}
|
|
6294
7538
|
function computeVisibleCounts() {
|
|
@@ -6332,6 +7576,7 @@ function createGraphInstance(opts) {
|
|
|
6332
7576
|
}
|
|
6333
7577
|
function composeNodeAlphaBuffer(base) {
|
|
6334
7578
|
if (softMask === null || scene === null) return base;
|
|
7579
|
+
perfCounters.fullNodeRecomposes += 1;
|
|
6335
7580
|
const out = new Float32Array(base);
|
|
6336
7581
|
const n = scene.count;
|
|
6337
7582
|
const dimAlpha = theme.mutedAlpha;
|
|
@@ -6343,6 +7588,7 @@ function createGraphInstance(opts) {
|
|
|
6343
7588
|
}
|
|
6344
7589
|
function composeEdgeAlphaBuffer(base) {
|
|
6345
7590
|
if (softMask === null && pathDimEdges === null || scene === null) return base;
|
|
7591
|
+
perfCounters.fullEdgeRecomposes += 1;
|
|
6346
7592
|
const out = new Float32Array(base);
|
|
6347
7593
|
const n = scene.linkCount;
|
|
6348
7594
|
const dimAlpha = theme.mutedAlpha;
|
|
@@ -6357,6 +7603,36 @@ function createGraphInstance(opts) {
|
|
|
6357
7603
|
}
|
|
6358
7604
|
return out;
|
|
6359
7605
|
}
|
|
7606
|
+
function composeNodeAlphaIncremental(drain) {
|
|
7607
|
+
const base = basePointColorBuffer();
|
|
7608
|
+
if (softMask === null || scene === null) return base;
|
|
7609
|
+
const mask = softMask;
|
|
7610
|
+
const dimAlpha = theme.mutedAlpha;
|
|
7611
|
+
const alphaOf = (i) => mask.nodeAlpha(i, dimAlpha);
|
|
7612
|
+
const seeded = nodeAlphaComposer.ensureSeeded(base, scene.count, dimAlpha, null, alphaOf);
|
|
7613
|
+
if (seeded) {
|
|
7614
|
+
nodeAlphaComposer.note(drain.nodes);
|
|
7615
|
+
nodeAlphaComposer.note(drain.nodesAlpha);
|
|
7616
|
+
}
|
|
7617
|
+
return nodeAlphaComposer.nextBuffer(base, alphaOf);
|
|
7618
|
+
}
|
|
7619
|
+
function composeEdgeAlphaIncremental(drain) {
|
|
7620
|
+
const base = baseLinkColorBuffer();
|
|
7621
|
+
if (softMask === null || scene === null) return base;
|
|
7622
|
+
if (pathDimEdges !== null) {
|
|
7623
|
+
edgeAlphaComposer.reset();
|
|
7624
|
+
return composeEdgeAlphaBuffer(base);
|
|
7625
|
+
}
|
|
7626
|
+
const mask = softMask;
|
|
7627
|
+
const dimAlpha = theme.mutedAlpha;
|
|
7628
|
+
const alphaOf = (k) => mask.edgeAlpha(k, dimAlpha);
|
|
7629
|
+
const seeded = edgeAlphaComposer.ensureSeeded(base, scene.linkCount, dimAlpha, null, alphaOf);
|
|
7630
|
+
if (seeded) {
|
|
7631
|
+
edgeAlphaComposer.note(drain.edges);
|
|
7632
|
+
edgeAlphaComposer.note(drain.edgesAlpha);
|
|
7633
|
+
}
|
|
7634
|
+
return edgeAlphaComposer.nextBuffer(base, alphaOf);
|
|
7635
|
+
}
|
|
6360
7636
|
function publishMaskFastPath(extraPatch, diagsChanged) {
|
|
6361
7637
|
const prev = store.getState();
|
|
6362
7638
|
const patch = { ...extraPatch };
|
|
@@ -6380,13 +7656,48 @@ function createGraphInstance(opts) {
|
|
|
6380
7656
|
};
|
|
6381
7657
|
const buffers = visDirty.nodeColor || visDirty.nodeSize ? { ...projectChannelBuffers(visDirty) ?? {} } : {};
|
|
6382
7658
|
if (visDirty.nodeColor || visDirty.nodeSize) diagsChanged = true;
|
|
7659
|
+
const maskPhaseT0 = performance.now();
|
|
7660
|
+
const rangedChannels = session !== null && session.policy !== null ? session.policy.rangedChannels : null;
|
|
7661
|
+
const bufferPatches = {};
|
|
6383
7662
|
if (nodesAffected && buffers.pointColor === void 0) {
|
|
6384
|
-
|
|
7663
|
+
const reseedsBefore = nodeAlphaComposer.reseeds;
|
|
7664
|
+
const composed = composeNodeAlphaIncremental(drain);
|
|
7665
|
+
const changed = drain.nodes.length + drain.nodesAlpha.length;
|
|
7666
|
+
if (rangedChannels !== null && rangedChannels.has("pointColor") && nodeAlphaComposer.reseeds === reseedsBefore && changed < scene.count * PATCH_FULL_UPLOAD_RATIO) {
|
|
7667
|
+
bufferPatches.pointColor = buildAlphaPatches(
|
|
7668
|
+
composed,
|
|
7669
|
+
[drain.nodes, drain.nodesAlpha],
|
|
7670
|
+
4
|
|
7671
|
+
);
|
|
7672
|
+
} else {
|
|
7673
|
+
buffers.pointColor = composed;
|
|
7674
|
+
}
|
|
6385
7675
|
}
|
|
6386
7676
|
if (edgesAffected && buffers.linkColor === void 0) {
|
|
6387
|
-
|
|
7677
|
+
const reseedsBefore = edgeAlphaComposer.reseeds;
|
|
7678
|
+
const composed = composeEdgeAlphaIncremental(drain);
|
|
7679
|
+
const changed = drain.edges.length + drain.edgesAlpha.length;
|
|
7680
|
+
if (rangedChannels !== null && rangedChannels.has("linkColor") && edgeAlphaComposer.reseeds === reseedsBefore && changed < scene.links.length / 2 * PATCH_FULL_UPLOAD_RATIO) {
|
|
7681
|
+
bufferPatches.linkColor = buildAlphaPatches(
|
|
7682
|
+
composed,
|
|
7683
|
+
[drain.edges, drain.edgesAlpha],
|
|
7684
|
+
4
|
|
7685
|
+
);
|
|
7686
|
+
} else {
|
|
7687
|
+
buffers.linkColor = composed;
|
|
7688
|
+
}
|
|
6388
7689
|
}
|
|
6389
|
-
|
|
7690
|
+
const maskUploadT0 = performance.now();
|
|
7691
|
+
const maskCommit = { revision: revisions.render, buffers };
|
|
7692
|
+
if (Object.keys(bufferPatches).length > 0) maskCommit.bufferPatches = bufferPatches;
|
|
7693
|
+
commitToEngine(eng, maskCommit);
|
|
7694
|
+
lastCommitMs = {
|
|
7695
|
+
kind: "mask",
|
|
7696
|
+
validate: 0,
|
|
7697
|
+
derive: 0,
|
|
7698
|
+
project: maskUploadT0 - maskPhaseT0,
|
|
7699
|
+
upload: performance.now() - maskUploadT0
|
|
7700
|
+
};
|
|
6390
7701
|
revisions.appliedRender = eng.appliedRevision();
|
|
6391
7702
|
}
|
|
6392
7703
|
patch.revisions = revisions;
|
|
@@ -6414,7 +7725,17 @@ function createGraphInstance(opts) {
|
|
|
6414
7725
|
crossfilterHiddenBase.clear();
|
|
6415
7726
|
}
|
|
6416
7727
|
const crossfilterFacadeListeners = /* @__PURE__ */ new Set();
|
|
7728
|
+
let crossfilterNotifyPending = false;
|
|
6417
7729
|
function notifyCrossfilterFacade() {
|
|
7730
|
+
if (degradeController.isEngaged("batch-histograms") && engineIfReady() !== null) {
|
|
7731
|
+
crossfilterNotifyPending = true;
|
|
7732
|
+
return;
|
|
7733
|
+
}
|
|
7734
|
+
for (const cb of crossfilterFacadeListeners) cb();
|
|
7735
|
+
}
|
|
7736
|
+
function flushCrossfilterNotify() {
|
|
7737
|
+
if (!crossfilterNotifyPending) return;
|
|
7738
|
+
crossfilterNotifyPending = false;
|
|
6418
7739
|
for (const cb of crossfilterFacadeListeners) cb();
|
|
6419
7740
|
}
|
|
6420
7741
|
function buildCrossfilterEngine() {
|
|
@@ -6540,9 +7861,13 @@ function createGraphInstance(opts) {
|
|
|
6540
7861
|
for (const s of delta.shown) crossfilterHiddenBase.delete(s);
|
|
6541
7862
|
if (membershipChanged && scene !== null) {
|
|
6542
7863
|
ensureMask();
|
|
6543
|
-
|
|
6544
|
-
|
|
6545
|
-
|
|
7864
|
+
if (refreshBrushMembershipDelta(delta)) {
|
|
7865
|
+
cascadeNodeMaskDelta();
|
|
7866
|
+
} else {
|
|
7867
|
+
refreshBrushMembership();
|
|
7868
|
+
refreshGroupMaskMembership(store.getState().hiddenNodeIds);
|
|
7869
|
+
cascadeNodeMask();
|
|
7870
|
+
}
|
|
6546
7871
|
}
|
|
6547
7872
|
publishMaskFastPath(extraPatch, false);
|
|
6548
7873
|
}
|
|
@@ -6781,6 +8106,7 @@ function createGraphInstance(opts) {
|
|
|
6781
8106
|
const result = reconcileScene(scopedAccepted ?? accepted);
|
|
6782
8107
|
labelPositionCache = null;
|
|
6783
8108
|
adjacency = null;
|
|
8109
|
+
sceneIncidence = null;
|
|
6784
8110
|
structuralChange = result.structuralChange;
|
|
6785
8111
|
positionChange = result.positionChange;
|
|
6786
8112
|
}
|
|
@@ -6853,6 +8179,8 @@ function createGraphInstance(opts) {
|
|
|
6853
8179
|
revisions.scope += 1;
|
|
6854
8180
|
revisions.render += 1;
|
|
6855
8181
|
if (eng !== null) {
|
|
8182
|
+
nodeAlphaComposer.reset();
|
|
8183
|
+
edgeAlphaComposer.reset();
|
|
6856
8184
|
const buffers = {};
|
|
6857
8185
|
if (nodesAffected) buffers.pointColor = composeNodeAlphaBuffer(basePointColorBuffer());
|
|
6858
8186
|
if (edgesAffected) buffers.linkColor = composeEdgeAlphaBuffer(baseLinkColorBuffer());
|
|
@@ -7047,9 +8375,10 @@ function createGraphInstance(opts) {
|
|
|
7047
8375
|
if (eng !== null && scene !== null && labelModel !== null && cfg !== void 0 && cfg.enabled !== false) {
|
|
7048
8376
|
const sceneRef = scene;
|
|
7049
8377
|
const vp = store.getState().viewport ?? eng.getViewport() ?? { zoom: 1 };
|
|
8378
|
+
const hardCap = degradeController.isEngaged("cap-dom-labels") ? LABEL_MAX_VISIBLE_DEFAULT : LABEL_MAX_VISIBLE_CAP;
|
|
7050
8379
|
const k = Math.max(
|
|
7051
8380
|
0,
|
|
7052
|
-
Math.min(Math.floor(cfg.maxVisible ?? LABEL_MAX_VISIBLE_DEFAULT),
|
|
8381
|
+
Math.min(Math.floor(cfg.maxVisible ?? LABEL_MAX_VISIBLE_DEFAULT), hardCap)
|
|
7053
8382
|
);
|
|
7054
8383
|
const clusterCandidates = clusterLabelCandidates(cfg, vp.zoom, k);
|
|
7055
8384
|
const nodeLabelsSuppressed = cfg.maxZoom !== void 0 && vp.zoom <= cfg.maxZoom;
|
|
@@ -7518,7 +8847,8 @@ function createGraphInstance(opts) {
|
|
|
7518
8847
|
defaultPointColor: theme.nodeDefault,
|
|
7519
8848
|
defaultLinkColor: theme.edgeDefault,
|
|
7520
8849
|
linkArrows: edgeArrows,
|
|
7521
|
-
renderLinks: showLinks
|
|
8850
|
+
renderLinks: showLinks,
|
|
8851
|
+
emphasisRingColor: theme.emphasisRing
|
|
7522
8852
|
};
|
|
7523
8853
|
if (simulation !== void 0) c.simulation = simulation;
|
|
7524
8854
|
if (clusterSpec !== null) {
|
|
@@ -7578,6 +8908,11 @@ function createGraphInstance(opts) {
|
|
|
7578
8908
|
}
|
|
7579
8909
|
function pushImageRefs() {
|
|
7580
8910
|
if (session === null || session.policy === null || session.policy.images !== "native") return;
|
|
8911
|
+
if (degradeController.isEngaged("defer-images")) {
|
|
8912
|
+
imageRefsDeferred = true;
|
|
8913
|
+
return;
|
|
8914
|
+
}
|
|
8915
|
+
imageRefsDeferred = false;
|
|
7581
8916
|
const model = sceneModel();
|
|
7582
8917
|
if (model === null) return;
|
|
7583
8918
|
if (nodeImage === void 0) {
|
|
@@ -7691,15 +9026,25 @@ function createGraphInstance(opts) {
|
|
|
7691
9026
|
}
|
|
7692
9027
|
eng.setPinnedIndices(indexSet.size > 0 ? [...indexSet] : null);
|
|
7693
9028
|
}
|
|
9029
|
+
function applyEmphasis(eng, index) {
|
|
9030
|
+
if (!emphasisRingOn) return;
|
|
9031
|
+
eng.setFocusedIndex(index);
|
|
9032
|
+
}
|
|
7694
9033
|
function reapplyInteractionState(eng) {
|
|
7695
9034
|
const { selection, pins, pinnedNodeIds, hover } = store.getState();
|
|
7696
9035
|
if (selection.nodeIds.length > 0 || selection.groupIds.length > 0) {
|
|
7697
9036
|
pushSelectionToEngine(eng, selection.nodeIds);
|
|
7698
9037
|
}
|
|
7699
9038
|
if (pins.size > 0 || pinnedNodeIds.size > 0) pushPinsToEngine(eng, pins);
|
|
9039
|
+
if (emphasizedNodeId !== null && scene !== null && !scene.indexById.has(emphasizedNodeId)) {
|
|
9040
|
+
emphasizedNodeId = null;
|
|
9041
|
+
}
|
|
7700
9042
|
if (hover.nodeId !== null && scene !== null) {
|
|
7701
9043
|
const idx = scene.indexById.get(hover.nodeId);
|
|
7702
|
-
if (idx !== void 0) eng
|
|
9044
|
+
if (idx !== void 0) applyEmphasis(eng, idx);
|
|
9045
|
+
} else if (emphasizedNodeId !== null && scene !== null) {
|
|
9046
|
+
const idx = scene.indexById.get(emphasizedNodeId);
|
|
9047
|
+
if (idx !== void 0) applyEmphasis(eng, idx);
|
|
7703
9048
|
}
|
|
7704
9049
|
}
|
|
7705
9050
|
function maybeFitView(eng) {
|
|
@@ -8441,6 +9786,7 @@ function createGraphInstance(opts) {
|
|
|
8441
9786
|
invalidateExpansionRecords();
|
|
8442
9787
|
edgeIndexById = new Map(p.merged.edges.map((e, k) => [e.id, k]));
|
|
8443
9788
|
adjacency = null;
|
|
9789
|
+
sceneIncidence = null;
|
|
8444
9790
|
acceptedAdjacency = null;
|
|
8445
9791
|
acceptedModelSeq += 1;
|
|
8446
9792
|
scopedAccepted = computeScopedAccepted();
|
|
@@ -8610,9 +9956,15 @@ function createGraphInstance(opts) {
|
|
|
8610
9956
|
if (pinsChanged || pinnedChanged || structuralChange && (nextPins.size > 0 || nextPinned.size > 0)) {
|
|
8611
9957
|
pushPinsToEngine(eng, nextPins);
|
|
8612
9958
|
}
|
|
9959
|
+
if (structuralChange && emphasizedNodeId !== null && !ingestScene.indexById.has(emphasizedNodeId)) {
|
|
9960
|
+
emphasizedNodeId = null;
|
|
9961
|
+
}
|
|
8613
9962
|
if (structuralChange && !hoverCleared && prev.hover.nodeId !== null) {
|
|
8614
9963
|
const idx = ingestScene.indexById.get(prev.hover.nodeId);
|
|
8615
|
-
if (idx !== void 0) eng
|
|
9964
|
+
if (idx !== void 0) applyEmphasis(eng, idx);
|
|
9965
|
+
} else if (structuralChange && emphasizedNodeId !== null) {
|
|
9966
|
+
const idx = ingestScene.indexById.get(emphasizedNodeId);
|
|
9967
|
+
if (idx !== void 0) applyEmphasis(eng, idx);
|
|
8616
9968
|
}
|
|
8617
9969
|
if (commitNeeded) maybeFitView(eng);
|
|
8618
9970
|
}
|
|
@@ -9373,6 +10725,7 @@ function createGraphInstance(opts) {
|
|
|
9373
10725
|
const result = reconcileScene(scopedAccepted ?? accepted);
|
|
9374
10726
|
labelPositionCache = null;
|
|
9375
10727
|
adjacency = null;
|
|
10728
|
+
sceneIncidence = null;
|
|
9376
10729
|
hardScopeGen += 1;
|
|
9377
10730
|
visibleGen += 1;
|
|
9378
10731
|
const filterDiagsChanged = rebuildMaskMemberships(prev.hiddenNodeIds);
|
|
@@ -9728,10 +11081,14 @@ function createGraphInstance(opts) {
|
|
|
9728
11081
|
checkRestoreAcknowledgement(update);
|
|
9729
11082
|
});
|
|
9730
11083
|
}
|
|
9731
|
-
function applyHostUpdateInner(update) {
|
|
11084
|
+
function applyHostUpdateInner(update, preAccepted) {
|
|
11085
|
+
const phaseT0 = performance.now();
|
|
11086
|
+
let phaseProjectStart = phaseT0;
|
|
9732
11087
|
const issuedModelSeq = acceptedModelSeq;
|
|
9733
11088
|
let searchIndexRejected = false;
|
|
9734
11089
|
let crossfilterRejected = false;
|
|
11090
|
+
let columnarRejected = false;
|
|
11091
|
+
let metricsDeferredToWorker = false;
|
|
9735
11092
|
const diagnosticsForced = pendingDiagnosticsRefresh;
|
|
9736
11093
|
pendingDiagnosticsRefresh = false;
|
|
9737
11094
|
if (update.filter !== void 0 && update.filter !== null) {
|
|
@@ -9772,11 +11129,42 @@ function createGraphInstance(opts) {
|
|
|
9772
11129
|
let positionChange = false;
|
|
9773
11130
|
let datasetKeyChanged = false;
|
|
9774
11131
|
let overlayIdsCleared = false;
|
|
9775
|
-
|
|
11132
|
+
let data;
|
|
11133
|
+
if (update.data !== void 0 && isColumnarSnapshot(update.data)) {
|
|
11134
|
+
const columnar = update.data;
|
|
11135
|
+
const isColumnarReplay = baseAccepted !== null && baseAccepted.datasetKey === columnar.datasetKey && baseAccepted.sourceRevision === columnar.sourceRevision;
|
|
11136
|
+
if (isColumnarReplay) {
|
|
11137
|
+
data = void 0;
|
|
11138
|
+
} else {
|
|
11139
|
+
const issues = validateColumnarStructure(columnar);
|
|
11140
|
+
if (issues.length > 0) {
|
|
11141
|
+
columnarDiags = [
|
|
11142
|
+
{
|
|
11143
|
+
code: "invalid-columnar-snapshot",
|
|
11144
|
+
severity: "error",
|
|
11145
|
+
count: issues.length,
|
|
11146
|
+
sampleIds: issues.slice(0, DIAGNOSTIC_SAMPLE_CAP).map((i) => i.where),
|
|
11147
|
+
message: `columnar snapshot rejected whole (\xA75.1): ${issues[0].where} \u2014 ${issues[0].detail}`
|
|
11148
|
+
}
|
|
11149
|
+
];
|
|
11150
|
+
columnarRejected = true;
|
|
11151
|
+
data = void 0;
|
|
11152
|
+
} else if (workerEligible()) {
|
|
11153
|
+
scheduleWorkerAcceptance(columnar, update.metrics);
|
|
11154
|
+
metricsDeferredToWorker = true;
|
|
11155
|
+
data = void 0;
|
|
11156
|
+
} else {
|
|
11157
|
+
data = materializeColumnarSnapshot(columnar);
|
|
11158
|
+
if (columnar.bufferOwnership === "transfer") detachColumnarBuffers(columnar);
|
|
11159
|
+
}
|
|
11160
|
+
}
|
|
11161
|
+
} else {
|
|
11162
|
+
data = update.data;
|
|
11163
|
+
}
|
|
9776
11164
|
if (data !== void 0) {
|
|
9777
11165
|
const isReplay = baseAccepted !== null && baseAccepted.datasetKey === data.datasetKey && baseAccepted.sourceRevision === data.sourceRevision;
|
|
9778
11166
|
if (!isReplay) {
|
|
9779
|
-
const nextAccepted = validateSnapshot(data);
|
|
11167
|
+
const nextAccepted = preAccepted ?? validateSnapshot(data);
|
|
9780
11168
|
abortAllSessions(null, "replaced: a declarative snapshot was applied (\xA77.5)");
|
|
9781
11169
|
overlayIdsCleared = clearOverlayState();
|
|
9782
11170
|
datasetKeyChanged = baseAccepted !== null && baseAccepted.datasetKey !== data.datasetKey;
|
|
@@ -9809,8 +11197,11 @@ function createGraphInstance(opts) {
|
|
|
9809
11197
|
baseSource = "declarative";
|
|
9810
11198
|
edgeIndexById = new Map(nextAccepted.edges.map((e, k) => [e.id, k]));
|
|
9811
11199
|
adjacency = null;
|
|
11200
|
+
sceneIncidence = null;
|
|
9812
11201
|
acceptedAdjacency = null;
|
|
9813
11202
|
dataDiags = nextAccepted.diagnostics;
|
|
11203
|
+
columnarDiags = [];
|
|
11204
|
+
pendingDeriveToken += 1;
|
|
9814
11205
|
dataChanged = true;
|
|
9815
11206
|
}
|
|
9816
11207
|
}
|
|
@@ -9953,6 +11344,7 @@ function createGraphInstance(opts) {
|
|
|
9953
11344
|
const result = reconcileScene(scopedAccepted ?? accepted);
|
|
9954
11345
|
labelPositionCache = null;
|
|
9955
11346
|
adjacency = null;
|
|
11347
|
+
sceneIncidence = null;
|
|
9956
11348
|
structuralChange = result.structuralChange;
|
|
9957
11349
|
positionChange = result.positionChange;
|
|
9958
11350
|
}
|
|
@@ -9966,6 +11358,7 @@ function createGraphInstance(opts) {
|
|
|
9966
11358
|
const result = reconcileScene(scopedAccepted ?? accepted);
|
|
9967
11359
|
labelPositionCache = null;
|
|
9968
11360
|
adjacency = null;
|
|
11361
|
+
sceneIncidence = null;
|
|
9969
11362
|
structuralChange = structuralChange || result.structuralChange;
|
|
9970
11363
|
positionChange = positionChange || result.positionChange;
|
|
9971
11364
|
}
|
|
@@ -10003,7 +11396,7 @@ function createGraphInstance(opts) {
|
|
|
10003
11396
|
if (clusterDiags !== clusterDiagsBefore) groupsDiagsChanged = true;
|
|
10004
11397
|
let metricsAdmitted = null;
|
|
10005
11398
|
let metricsProcessed = false;
|
|
10006
|
-
if (update.metrics !== void 0 && update.metrics.length > 0) {
|
|
11399
|
+
if (!metricsDeferredToWorker && update.metrics !== void 0 && update.metrics.length > 0) {
|
|
10007
11400
|
metricsProcessed = true;
|
|
10008
11401
|
if (accepted === null || !ensureMetricModel()) {
|
|
10009
11402
|
metricDiags = [
|
|
@@ -10068,6 +11461,7 @@ function createGraphInstance(opts) {
|
|
|
10068
11461
|
let configPatch;
|
|
10069
11462
|
let themeChanged = false;
|
|
10070
11463
|
let mutedAlphaChanged = false;
|
|
11464
|
+
let emphasisToggled = false;
|
|
10071
11465
|
let nodeBaseInvalidated = false;
|
|
10072
11466
|
let linkBaseInvalidated = false;
|
|
10073
11467
|
{
|
|
@@ -10102,6 +11496,10 @@ function createGraphInstance(opts) {
|
|
|
10102
11496
|
linkBaseInvalidated = true;
|
|
10103
11497
|
}
|
|
10104
11498
|
}
|
|
11499
|
+
if (nextTheme.emphasisRing !== theme.emphasisRing) {
|
|
11500
|
+
c.emphasisRingColor = nextTheme.emphasisRing;
|
|
11501
|
+
any = true;
|
|
11502
|
+
}
|
|
10105
11503
|
mutedAlphaChanged = nextTheme.mutedAlpha !== theme.mutedAlpha;
|
|
10106
11504
|
if (groupRewrite !== null && (nextTheme.accent !== theme.accent || nextTheme.nodeDefault !== theme.nodeDefault)) {
|
|
10107
11505
|
dirtyNodeColor = true;
|
|
@@ -10124,6 +11522,10 @@ function createGraphInstance(opts) {
|
|
|
10124
11522
|
c.renderLinks = showLinks;
|
|
10125
11523
|
any = true;
|
|
10126
11524
|
}
|
|
11525
|
+
if (update.emphasisRing !== void 0 && update.emphasisRing !== emphasisRingOn) {
|
|
11526
|
+
emphasisRingOn = update.emphasisRing;
|
|
11527
|
+
emphasisToggled = true;
|
|
11528
|
+
}
|
|
10127
11529
|
if (clusterConfigDirty) {
|
|
10128
11530
|
c.cluster = clusterConfigPayload();
|
|
10129
11531
|
any = true;
|
|
@@ -10325,6 +11727,8 @@ function createGraphInstance(opts) {
|
|
|
10325
11727
|
const edgesAffected = drain.edges.length > 0 || drain.edgesAlpha.length > 0;
|
|
10326
11728
|
maskAffected = nodesAffected || edgesAffected;
|
|
10327
11729
|
if (maskAffected) {
|
|
11730
|
+
nodeAlphaComposer.reset();
|
|
11731
|
+
edgeAlphaComposer.reset();
|
|
10328
11732
|
maskBuffers = {};
|
|
10329
11733
|
if (nodesAffected) maskBuffers.pointColor = composeNodeAlphaBuffer(basePointColorBuffer());
|
|
10330
11734
|
if (edgesAffected) maskBuffers.linkColor = composeEdgeAlphaBuffer(baseLinkColorBuffer());
|
|
@@ -10344,6 +11748,7 @@ function createGraphInstance(opts) {
|
|
|
10344
11748
|
maskBuffers.linkColor = composeEdgeAlphaBuffer(baseLinkColorBuffer());
|
|
10345
11749
|
}
|
|
10346
11750
|
}
|
|
11751
|
+
phaseProjectStart = performance.now();
|
|
10347
11752
|
let buffers = projectChannelBuffers({
|
|
10348
11753
|
nodeColor: dirtyNodeColor,
|
|
10349
11754
|
nodeSize: dirtyNodeSize,
|
|
@@ -10393,7 +11798,15 @@ function createGraphInstance(opts) {
|
|
|
10393
11798
|
simRestarted = true;
|
|
10394
11799
|
}
|
|
10395
11800
|
}
|
|
11801
|
+
const phaseUploadStart = performance.now();
|
|
10396
11802
|
commitToEngine(eng, commit);
|
|
11803
|
+
lastCommitMs = {
|
|
11804
|
+
kind: structure !== void 0 ? scopeChanged && !dataChanged ? "scope" : "model" : buffers !== void 0 ? "mask" : "config",
|
|
11805
|
+
validate: 0,
|
|
11806
|
+
derive: phaseProjectStart - phaseT0,
|
|
11807
|
+
project: phaseUploadStart - phaseProjectStart,
|
|
11808
|
+
upload: performance.now() - phaseUploadStart
|
|
11809
|
+
};
|
|
10397
11810
|
revisions.appliedRender = eng.appliedRevision();
|
|
10398
11811
|
const facade = session !== null ? session.edgePicking : null;
|
|
10399
11812
|
if (facade !== null) {
|
|
@@ -10433,7 +11846,7 @@ function createGraphInstance(opts) {
|
|
|
10433
11846
|
patch.timeline = { playingKey: null };
|
|
10434
11847
|
changed = true;
|
|
10435
11848
|
}
|
|
10436
|
-
if (dataChanged || datasetKeyChanged || buffers !== void 0 || labelRerank.diagsChanged || filterDiagsChanged || metricsProcessed || searchIndexRejected || crossfilterRejected || parallelRejected || groupsDiagsChanged || diagnosticsForced) {
|
|
11849
|
+
if (dataChanged || datasetKeyChanged || buffers !== void 0 || labelRerank.diagsChanged || filterDiagsChanged || metricsProcessed || searchIndexRejected || crossfilterRejected || columnarRejected || parallelRejected || groupsDiagsChanged || diagnosticsForced) {
|
|
10437
11850
|
patch.diagnostics = composeDiagnostics();
|
|
10438
11851
|
changed = true;
|
|
10439
11852
|
}
|
|
@@ -10502,9 +11915,26 @@ function createGraphInstance(opts) {
|
|
|
10502
11915
|
if (pinsChanged || pinnedChanged || structuralChange && (nextPins.size > 0 || nextPinned.size > 0)) {
|
|
10503
11916
|
pushPinsToEngine(eng, nextPins);
|
|
10504
11917
|
}
|
|
11918
|
+
if (structuralChange && emphasizedNodeId !== null && scene !== null && !scene.indexById.has(emphasizedNodeId)) {
|
|
11919
|
+
emphasizedNodeId = null;
|
|
11920
|
+
}
|
|
10505
11921
|
if (structuralChange && !hoverCleared && prev.hover.nodeId !== null && scene !== null) {
|
|
10506
11922
|
const idx = scene.indexById.get(prev.hover.nodeId);
|
|
10507
|
-
if (idx !== void 0) eng
|
|
11923
|
+
if (idx !== void 0) applyEmphasis(eng, idx);
|
|
11924
|
+
} else if (structuralChange && emphasizedNodeId !== null && scene !== null) {
|
|
11925
|
+
const idx = scene.indexById.get(emphasizedNodeId);
|
|
11926
|
+
if (idx !== void 0) applyEmphasis(eng, idx);
|
|
11927
|
+
}
|
|
11928
|
+
if (emphasisToggled) {
|
|
11929
|
+
if (!emphasisRingOn) {
|
|
11930
|
+
eng.setFocusedIndex(null);
|
|
11931
|
+
} else if (!hoverCleared && prev.hover.nodeId !== null && scene !== null) {
|
|
11932
|
+
const idx = scene.indexById.get(prev.hover.nodeId);
|
|
11933
|
+
if (idx !== void 0) applyEmphasis(eng, idx);
|
|
11934
|
+
} else if (emphasizedNodeId !== null && scene !== null) {
|
|
11935
|
+
const idx = scene.indexById.get(emphasizedNodeId);
|
|
11936
|
+
if (idx !== void 0) applyEmphasis(eng, idx);
|
|
11937
|
+
}
|
|
10508
11938
|
}
|
|
10509
11939
|
if (commitNeeded) maybeFitView(eng);
|
|
10510
11940
|
}
|
|
@@ -10554,7 +11984,8 @@ function createGraphInstance(opts) {
|
|
|
10554
11984
|
const nodeId = node === null ? null : node.id;
|
|
10555
11985
|
const hover = store.getState().hover;
|
|
10556
11986
|
if (hover.nodeId !== nodeId) publish({ hover: { nodeId, edgeId: hover.edgeId } });
|
|
10557
|
-
|
|
11987
|
+
emphasizedNodeId = null;
|
|
11988
|
+
applyEmphasis(s.engine, node === null ? null : index);
|
|
10558
11989
|
emit("nodeHover", { node });
|
|
10559
11990
|
},
|
|
10560
11991
|
// §13 native-route edge events (§7.4 mapping). Host onLink* events are
|
|
@@ -10575,6 +12006,9 @@ function createGraphInstance(opts) {
|
|
|
10575
12006
|
emit("edgeClick", { edge });
|
|
10576
12007
|
},
|
|
10577
12008
|
onLinkHover(linkIndex) {
|
|
12009
|
+
if (linkIndex !== null && degradeController.isEngaged("defer-link-picking") && store.getState().simulationRunning) {
|
|
12010
|
+
return;
|
|
12011
|
+
}
|
|
10578
12012
|
if (!active()) return;
|
|
10579
12013
|
if (linkIndex !== null && !maskEdgeVisibleAt(linkIndex)) linkIndex = null;
|
|
10580
12014
|
const edge = linkIndex === null ? null : edgeAtLinkIndex(linkIndex) ?? null;
|
|
@@ -10616,6 +12050,32 @@ function createGraphInstance(opts) {
|
|
|
10616
12050
|
*/
|
|
10617
12051
|
onFrame(timeMs) {
|
|
10618
12052
|
if (!active()) return;
|
|
12053
|
+
frameCadence += 1;
|
|
12054
|
+
pressureSampler.noteFrame(timeMs, !store.getState().simulationRunning);
|
|
12055
|
+
flushCrossfilterNotify();
|
|
12056
|
+
if (timeMs - lastPerfSampleAt >= PERF_SAMPLE_THROTTLE_MS) {
|
|
12057
|
+
lastPerfSampleAt = timeMs;
|
|
12058
|
+
const ewma = pressureSampler.snapshot().frameEwmaMs;
|
|
12059
|
+
if (Number.isFinite(ewma)) {
|
|
12060
|
+
const vis = store.getState().visible;
|
|
12061
|
+
const visible = { nodes: vis.nodes, edges: vis.edges };
|
|
12062
|
+
if (ewma > FRAME_PRESSURE_ENGAGE_MS) {
|
|
12063
|
+
for (const step of ["cap-dom-labels", "defer-link-picking"]) {
|
|
12064
|
+
const event = degradeController.engageForPressure(step, "frame-pressure", visible);
|
|
12065
|
+
if (event !== null) applyDegradeEvent(event);
|
|
12066
|
+
}
|
|
12067
|
+
} else if (ewma < FRAME_PRESSURE_CLEAR_MS) {
|
|
12068
|
+
for (const step of ["cap-dom-labels", "defer-link-picking"]) {
|
|
12069
|
+
const event = degradeController.clearPressure(step, visible);
|
|
12070
|
+
if (event !== null) applyDegradeEvent(event);
|
|
12071
|
+
}
|
|
12072
|
+
}
|
|
12073
|
+
}
|
|
12074
|
+
if ((listeners.get("perfSample")?.size ?? 0) > 0) {
|
|
12075
|
+
emit("perfSample", getPerfSnapshot());
|
|
12076
|
+
}
|
|
12077
|
+
pressureSampler.resetCounters();
|
|
12078
|
+
}
|
|
10619
12079
|
if (store.getState().simulationRunning && labelsEnabled()) {
|
|
10620
12080
|
if (lastHotRefreshMs === null || timeMs - lastHotRefreshMs >= SIM_HOT_REFRESH_MS || timeMs < lastHotRefreshMs) {
|
|
10621
12081
|
lastHotRefreshMs = timeMs;
|
|
@@ -10662,6 +12122,7 @@ function createGraphInstance(opts) {
|
|
|
10662
12122
|
notifyLabelSubs(positionSubs);
|
|
10663
12123
|
}
|
|
10664
12124
|
emit("simulationEnd", {});
|
|
12125
|
+
armQuiescenceAssertion();
|
|
10665
12126
|
},
|
|
10666
12127
|
onError(error) {
|
|
10667
12128
|
if (!active()) return;
|
|
@@ -10739,11 +12200,13 @@ function createGraphInstance(opts) {
|
|
|
10739
12200
|
}
|
|
10740
12201
|
function buildAndCommitFullReplay(s, restartAlpha) {
|
|
10741
12202
|
const eng = s.engine;
|
|
12203
|
+
const phaseT0 = performance.now();
|
|
10742
12204
|
const renderRevision = store.getState().revisions.render;
|
|
10743
12205
|
const replayModel = renderModel();
|
|
10744
12206
|
if (replayModel !== null) {
|
|
10745
12207
|
reconcileScene(replayModel);
|
|
10746
12208
|
const replayScene = scene;
|
|
12209
|
+
const phaseProjectStart = performance.now();
|
|
10747
12210
|
const commit = {
|
|
10748
12211
|
revision: renderRevision,
|
|
10749
12212
|
structure: {
|
|
@@ -10767,7 +12230,15 @@ function createGraphInstance(opts) {
|
|
|
10767
12230
|
commit.resources = { ...commit.resources ?? {}, pointImageIndex: syncIndex };
|
|
10768
12231
|
}
|
|
10769
12232
|
if (restartAlpha !== null) commit.restart = { alpha: restartAlpha };
|
|
12233
|
+
const phaseUploadStart = performance.now();
|
|
10770
12234
|
commitToEngine(eng, commit);
|
|
12235
|
+
lastCommitMs = {
|
|
12236
|
+
kind: "model",
|
|
12237
|
+
validate: 0,
|
|
12238
|
+
derive: phaseProjectStart - phaseT0,
|
|
12239
|
+
project: phaseUploadStart - phaseProjectStart,
|
|
12240
|
+
upload: performance.now() - phaseUploadStart
|
|
12241
|
+
};
|
|
10771
12242
|
if (s.edgePicking !== null) {
|
|
10772
12243
|
if (restartAlpha !== null) s.edgePicking.disarm();
|
|
10773
12244
|
else s.edgePicking.arm(replayScene.positions, replayScene.links);
|
|
@@ -10859,6 +12330,11 @@ function createGraphInstance(opts) {
|
|
|
10859
12330
|
session = null;
|
|
10860
12331
|
mountPromise = null;
|
|
10861
12332
|
cancelRerankTimer();
|
|
12333
|
+
flushCrossfilterNotify();
|
|
12334
|
+
if (quiescenceTimer !== null) {
|
|
12335
|
+
clearTimeout(quiescenceTimer);
|
|
12336
|
+
quiescenceTimer = null;
|
|
12337
|
+
}
|
|
10862
12338
|
const timelinePatch = resetTimelineForPatch();
|
|
10863
12339
|
s.edgePicking?.destroy();
|
|
10864
12340
|
s.edgePicking = null;
|
|
@@ -10871,10 +12347,48 @@ function createGraphInstance(opts) {
|
|
|
10871
12347
|
});
|
|
10872
12348
|
rerankAndNotify();
|
|
10873
12349
|
}
|
|
12350
|
+
function armQuiescenceAssertion() {
|
|
12351
|
+
if (quiescenceAsserted || quiescenceTimer !== null || destroyed) return;
|
|
12352
|
+
const env = globalThis.process?.env?.NODE_ENV;
|
|
12353
|
+
if (env !== "development") return;
|
|
12354
|
+
const g = globalThis;
|
|
12355
|
+
if (typeof g.requestAnimationFrame !== "function") return;
|
|
12356
|
+
const armedRender = store.getState().revisions.render;
|
|
12357
|
+
quiescenceTimer = setTimeout(() => {
|
|
12358
|
+
quiescenceTimer = null;
|
|
12359
|
+
const state = store.getState();
|
|
12360
|
+
if (destroyed || state.simulationRunning || state.revisions.render !== armedRender) {
|
|
12361
|
+
return;
|
|
12362
|
+
}
|
|
12363
|
+
quiescenceAsserted = true;
|
|
12364
|
+
const native = g.requestAnimationFrame;
|
|
12365
|
+
if (typeof native !== "function") return;
|
|
12366
|
+
let registrations = 0;
|
|
12367
|
+
const wrapper = (cb) => {
|
|
12368
|
+
registrations += 1;
|
|
12369
|
+
return native.call(globalThis, cb);
|
|
12370
|
+
};
|
|
12371
|
+
g.requestAnimationFrame = wrapper;
|
|
12372
|
+
setTimeout(() => {
|
|
12373
|
+
if (g.requestAnimationFrame === wrapper) g.requestAnimationFrame = native;
|
|
12374
|
+
if (registrations > 0 && !destroyed) {
|
|
12375
|
+
console.warn(
|
|
12376
|
+
`orbit: ${registrations} requestAnimationFrame registration(s) observed over 500ms while this instance is quiescent (\xA717/S13-T06 one-frame-loop). If no app-owned animation is running, a second frame loop is leaking.`
|
|
12377
|
+
);
|
|
12378
|
+
}
|
|
12379
|
+
}, 500);
|
|
12380
|
+
}, 2e3);
|
|
12381
|
+
}
|
|
10874
12382
|
function destroy() {
|
|
10875
12383
|
if (destroyed) return;
|
|
10876
12384
|
destroyed = true;
|
|
12385
|
+
workerLane?.terminate();
|
|
12386
|
+
workerLane = null;
|
|
10877
12387
|
stopTimelineTimer();
|
|
12388
|
+
if (quiescenceTimer !== null) {
|
|
12389
|
+
clearTimeout(quiescenceTimer);
|
|
12390
|
+
quiescenceTimer = null;
|
|
12391
|
+
}
|
|
10878
12392
|
timelinePlayingKey = null;
|
|
10879
12393
|
imagePipeline?.dispose();
|
|
10880
12394
|
imagePipeline = null;
|
|
@@ -11622,7 +13136,7 @@ function createGraphInstance(opts) {
|
|
|
11622
13136
|
if (eng === null || scene === null) return EMPTY_IDS;
|
|
11623
13137
|
const idx = scene.indexById.get(id);
|
|
11624
13138
|
if (idx === void 0) return EMPTY_IDS;
|
|
11625
|
-
eng
|
|
13139
|
+
applyEmphasis(eng, idx);
|
|
11626
13140
|
if (effectiveReducedMotion()) eng.zoomToIndex?.(idx, 0);
|
|
11627
13141
|
else eng.zoomToIndex?.(idx);
|
|
11628
13142
|
const neighbors = neighborIdsOf(id);
|
|
@@ -11637,6 +13151,67 @@ function createGraphInstance(opts) {
|
|
|
11637
13151
|
}
|
|
11638
13152
|
return neighbors;
|
|
11639
13153
|
}
|
|
13154
|
+
function emphasizeNode(id) {
|
|
13155
|
+
const eng = engineIfReady();
|
|
13156
|
+
if (eng === null) return;
|
|
13157
|
+
if (id === null) {
|
|
13158
|
+
emphasizedNodeId = null;
|
|
13159
|
+
applyEmphasis(eng, null);
|
|
13160
|
+
return;
|
|
13161
|
+
}
|
|
13162
|
+
if (scene === null) return;
|
|
13163
|
+
const idx = scene.indexById.get(id);
|
|
13164
|
+
if (idx !== void 0) {
|
|
13165
|
+
emphasizedNodeId = id;
|
|
13166
|
+
applyEmphasis(eng, idx);
|
|
13167
|
+
}
|
|
13168
|
+
}
|
|
13169
|
+
function getPerfSnapshot() {
|
|
13170
|
+
const st = store.getState();
|
|
13171
|
+
const p = pressureSampler.snapshot();
|
|
13172
|
+
const snap = {
|
|
13173
|
+
at: Date.now(),
|
|
13174
|
+
nodeCount: st.nodeCount,
|
|
13175
|
+
edgeCount: st.edgeCount,
|
|
13176
|
+
visibleNodeCount: st.visible.nodes,
|
|
13177
|
+
visibleEdgeCount: st.visible.edges,
|
|
13178
|
+
estimatedCpuBytes: estimateCpuBytes(),
|
|
13179
|
+
// The acceptance queue is synchronous (depth is 0 outside a job, and a
|
|
13180
|
+
// reader inside a job observes 1); async ingestion depth arrives with
|
|
13181
|
+
// the worker lane (PR-E).
|
|
13182
|
+
queueDepth: acceptanceQueue.active ? 1 : 0,
|
|
13183
|
+
modelRevision: st.revisions.model,
|
|
13184
|
+
scopeRevision: st.revisions.scope,
|
|
13185
|
+
renderRevision: st.revisions.render,
|
|
13186
|
+
appliedRenderRevision: st.revisions.appliedRender,
|
|
13187
|
+
activeDegradations: degradeController.activeSteps(),
|
|
13188
|
+
// 'worker' iff the lane actually boots — a configured-but-unavailable
|
|
13189
|
+
// lane reports the honest 'main' (plus its worker-unavailable diag).
|
|
13190
|
+
execution: workerLane !== null && workerLane.available() === true ? "worker" : "main",
|
|
13191
|
+
rangeUpdates: session !== null && session.policy !== null ? [...session.policy.rangedChannels] : [],
|
|
13192
|
+
pressure: {
|
|
13193
|
+
frameEwmaMs: p.frameEwmaMs,
|
|
13194
|
+
droppedFrames: p.droppedFrames,
|
|
13195
|
+
idleWakeups: p.idleWakeups
|
|
13196
|
+
}
|
|
13197
|
+
};
|
|
13198
|
+
if (lastCommitMs !== void 0) snap.lastCommitMs = { ...lastCommitMs };
|
|
13199
|
+
if (scene !== null) {
|
|
13200
|
+
snap.estimatedGpuBytes = scene.count * (2 + 4 + 1) * 4 + scene.linkCount * (4 + 1 + 2) * 4;
|
|
13201
|
+
}
|
|
13202
|
+
return snap;
|
|
13203
|
+
}
|
|
13204
|
+
function estimateCpuBytes() {
|
|
13205
|
+
let bytes = 0;
|
|
13206
|
+
if (scene !== null) bytes += scene.positions.byteLength + scene.links.byteLength;
|
|
13207
|
+
if (basePointColors !== null) bytes += basePointColors.byteLength;
|
|
13208
|
+
if (baseLinkColors !== null) bytes += baseLinkColors.byteLength;
|
|
13209
|
+
if (lastLinkWidths !== null) bytes += lastLinkWidths.byteLength;
|
|
13210
|
+
bytes += metricStore.estimatedBytes();
|
|
13211
|
+
if (crossfilterEngine !== null) bytes += crossfilterEngine.estimatedBytes();
|
|
13212
|
+
if (softMask !== null) bytes += softMask.estimatedBytes();
|
|
13213
|
+
return bytes;
|
|
13214
|
+
}
|
|
11640
13215
|
function cameraZoom(factor) {
|
|
11641
13216
|
const eng = engineIfReady();
|
|
11642
13217
|
if (eng === null) return;
|
|
@@ -11709,6 +13284,7 @@ function createGraphInstance(opts) {
|
|
|
11709
13284
|
else eng.setViewport(v);
|
|
11710
13285
|
},
|
|
11711
13286
|
focusNode,
|
|
13287
|
+
emphasizeNode,
|
|
11712
13288
|
requestNodeContextMenu,
|
|
11713
13289
|
getViewState,
|
|
11714
13290
|
setViewState,
|
|
@@ -11729,6 +13305,9 @@ function createGraphInstance(opts) {
|
|
|
11729
13305
|
pickEdgeAt,
|
|
11730
13306
|
sampleEdgeHover,
|
|
11731
13307
|
sampleEdgeClick,
|
|
13308
|
+
getFrameCadence: () => frameCadence,
|
|
13309
|
+
getPerfCounters: () => perfCounters,
|
|
13310
|
+
getPerfSnapshot,
|
|
11732
13311
|
hideNodes,
|
|
11733
13312
|
showNodes,
|
|
11734
13313
|
showAll,
|
|
@@ -11933,6 +13512,73 @@ var OverviewController = class {
|
|
|
11933
13512
|
}
|
|
11934
13513
|
};
|
|
11935
13514
|
|
|
11936
|
-
|
|
13515
|
+
// src/descriptors.ts
|
|
13516
|
+
function field(path) {
|
|
13517
|
+
return path;
|
|
13518
|
+
}
|
|
13519
|
+
var TRANSFORM_OPS = /* @__PURE__ */ new Set(["identity", "number", "lowercase", "date-to-epoch-ms", "coalesce"]);
|
|
13520
|
+
function validateFieldAccessor(value) {
|
|
13521
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
13522
|
+
return { kind: "not-an-object" };
|
|
13523
|
+
}
|
|
13524
|
+
const candidate = value;
|
|
13525
|
+
if (typeof candidate.field !== "string") return { kind: "field-not-a-string" };
|
|
13526
|
+
if (candidate.transform !== void 0) {
|
|
13527
|
+
const t = candidate.transform;
|
|
13528
|
+
if (t === null || typeof t !== "object" || Array.isArray(t)) {
|
|
13529
|
+
return { kind: "transform-not-an-object" };
|
|
13530
|
+
}
|
|
13531
|
+
const op = t.op;
|
|
13532
|
+
if (typeof op !== "string" || !TRANSFORM_OPS.has(op)) {
|
|
13533
|
+
return { kind: "unknown-transform-op", op: String(op) };
|
|
13534
|
+
}
|
|
13535
|
+
if (op === "coalesce" && !("value" in t)) {
|
|
13536
|
+
return { kind: "coalesce-missing-value" };
|
|
13537
|
+
}
|
|
13538
|
+
}
|
|
13539
|
+
return null;
|
|
13540
|
+
}
|
|
13541
|
+
function isFieldAccessor(value) {
|
|
13542
|
+
return validateFieldAccessor(value) === null;
|
|
13543
|
+
}
|
|
13544
|
+
function descriptorKey(accessor) {
|
|
13545
|
+
const t = accessor.transform;
|
|
13546
|
+
if (t === void 0) return `f:${accessor.field}|identity`;
|
|
13547
|
+
if (t.op === "coalesce") return `f:${accessor.field}|coalesce:${JSON.stringify(t.value ?? null)}`;
|
|
13548
|
+
return `f:${accessor.field}|${t.op}`;
|
|
13549
|
+
}
|
|
13550
|
+
function coerceNumber(value) {
|
|
13551
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
|
13552
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
13553
|
+
const parsed = Number(value);
|
|
13554
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
13555
|
+
}
|
|
13556
|
+
return null;
|
|
13557
|
+
}
|
|
13558
|
+
function coerceEpochMs(value) {
|
|
13559
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
|
13560
|
+
if (typeof value === "string") {
|
|
13561
|
+
const parsed = Date.parse(value);
|
|
13562
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
13563
|
+
}
|
|
13564
|
+
return null;
|
|
13565
|
+
}
|
|
13566
|
+
function evaluateFieldAccessor(accessor, id, attrs) {
|
|
13567
|
+
const raw = accessor.field === "id" ? id : attrs?.[accessor.field];
|
|
13568
|
+
const t = accessor.transform;
|
|
13569
|
+
if (t === void 0 || t.op === "identity") return raw;
|
|
13570
|
+
switch (t.op) {
|
|
13571
|
+
case "number":
|
|
13572
|
+
return coerceNumber(raw);
|
|
13573
|
+
case "lowercase":
|
|
13574
|
+
return typeof raw === "string" ? raw.toLowerCase() : null;
|
|
13575
|
+
case "date-to-epoch-ms":
|
|
13576
|
+
return coerceEpochMs(raw);
|
|
13577
|
+
case "coalesce":
|
|
13578
|
+
return raw === null || raw === void 0 ? t.value : raw;
|
|
13579
|
+
}
|
|
13580
|
+
}
|
|
13581
|
+
|
|
13582
|
+
export { ATLAS_MAX_CONCURRENT_DEFAULT, ATLAS_MAX_ENTRIES_DEFAULT, ATLAS_MAX_RETRIES_DEFAULT, AcceptanceQueue, BASE_PENDING_KEY, BRUSH_HISTORY_COALESCE_MS, CATEGORICAL_PALETTE, DEFAULT_BIN_COUNT, DIM_ALPHA_DEFAULT, DIVERGING_RANGE_DEFAULT, DomainStore, EDGE_PICK_TOLERANCE_PX, EdgePickingFacade, GRAPH_THEME_DARK, GRAPH_THEME_LIGHT, HISTORY_LIMIT_DEFAULT, HistoryKernel, INGEST_MAX_FLUSH_LATENCY_MS_DEFAULT, INGEST_MAX_PENDING_BYTES_DEFAULT, INGEST_OVERFLOW_FACTOR, ImageAtlasPipeline, LABEL_MAX_VISIBLE_CAP, LABEL_MAX_VISIBLE_DEFAULT, LinkPickIndex, META_EDGE_MAX_WIDTH, MetricStore, OVERVIEW_HOT_INTERVAL_MS, OVERVIEW_IDLE_INTERVAL_MS, OVERVIEW_SIZE_DEFAULT, OrbitOperationError, OverviewController, PHYSICAL_DEFAULT_LINK_WIDTH, PHYSICAL_DEFAULT_POINT_SIZE, PendingExpansions, Reconciler, SEARCH_CACHE_LIMIT, SEARCH_LIMIT_DEFAULT, SEARCH_SCAN_CHUNK, SEARCH_SCORE_EXACT_ID, SEARCH_SCORE_ID_PREFIX, SEARCH_SCORE_SUBSTRING, SEARCH_SCORE_TOKEN_START_BONUS, SEQUENTIAL_RANGE_DEFAULT, SUPER_NODE_MAX_SIZE, SVG_MAX_ELEMENTS_DEFAULT, SoftMask, SvgBudgetError, TIMELINE_STEP_DEFAULT, TIMELINE_TICK_MS_DEFAULT, TypedColumnCrossfilter, VIEW_STATE_VERSION, admitServiceResult, assertCapabilityMethodParity, baseFromAccepted, baseFromContribution, buildAcceptedAdjacency, buildAcceptedFromColumnar, buildAdjacency, canonicalFilterKey, canonicalJson2 as canonicalJson, canonicalScaleKey, cascadeEdges, categoricalIndex, categoricalRows, coerceNumeric, coerceNumericInto, collapseParallelEdges, columnarArrayBuffers, compileEdgeFilter, compileNodeFilter, computeNumericDomain, createGraphInstance, createLocalExpansionService, createLocalSearchService, createRequestContext, deriveGroupsByKey, descriptorKey, detachColumnarBuffers, divergingColor, escapeXml, estimateBatchBytes, evaluateFieldAccessor, evaluateFilterExpr, field, graphErrorToError, groupByDerivedId, groupSceneKey, interpolateColor, isColumnarSnapshot, isFatalGraphError, isFieldAccessor, materializeColumnarSnapshot, medianLinkWidthPx, mergeDiagnostics, mergeModel, metaEdgePublicId, metaEdgeSceneKey, metaEdgeWidthFor, neighborsOf, newContribution, newStagingTallies, nextRequestId, normalizeCommitForCapabilities, parseColor, pointSegmentDistanceSquared, projectColors, projectSizes, renderSvg, resolveEnginePolicy, resolveFilterField, resolveManualGroups, resolveScope, resolveTheme, resourceLimitFatal, rewriteGroups, sameDataRef, sameGroupBySpec, sameGroupSpecArrays, sceneGroupsOf, sceneLinkRefAt, scenePointRefAt, selectLabelCandidates, sequentialColor, sequentialSize, serviceCacheKey, sessionCommitDiagnostics, stageBatch, superNodeSizeFor, validateColumnarStructure, validateFieldAccessor, validateFilterExpr, validateGroupBySpec, validateGroupSpecs, validateSnapshot, validateViewState };
|
|
11937
13583
|
//# sourceMappingURL=index.js.map
|
|
11938
13584
|
//# sourceMappingURL=index.js.map
|