@pascal-app/viewer 1.0.0-beta.5 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +67 -0
- package/dist/capture/asset-url.d.ts +2 -0
- package/dist/capture/asset-url.d.ts.map +1 -0
- package/dist/capture/asset-url.js +13 -0
- package/dist/capture/capture-runtime.d.ts +43 -0
- package/dist/capture/capture-runtime.d.ts.map +1 -0
- package/dist/capture/capture-runtime.js +198 -0
- package/dist/capture/frame.d.ts +4 -0
- package/dist/capture/frame.d.ts.map +1 -0
- package/dist/capture/frame.js +21 -0
- package/dist/capture/index.d.ts +12 -0
- package/dist/capture/index.d.ts.map +1 -0
- package/dist/capture/index.js +11 -0
- package/dist/capture/layer-visibility.d.ts +5 -0
- package/dist/capture/layer-visibility.d.ts.map +1 -0
- package/dist/capture/layer-visibility.js +11 -0
- package/dist/capture/layers/clay-matcap.d.ts +3 -0
- package/dist/capture/layers/clay-matcap.d.ts.map +1 -0
- package/dist/capture/layers/clay-matcap.js +26 -0
- package/dist/capture/layers/device-motion-layer.d.ts +7 -0
- package/dist/capture/layers/device-motion-layer.d.ts.map +1 -0
- package/dist/capture/layers/device-motion-layer.js +76 -0
- package/dist/capture/layers/point-cloud-layer.d.ts +15 -0
- package/dist/capture/layers/point-cloud-layer.d.ts.map +1 -0
- package/dist/capture/layers/point-cloud-layer.js +106 -0
- package/dist/capture/layers/room-model-layer.d.ts +9 -0
- package/dist/capture/layers/room-model-layer.d.ts.map +1 -0
- package/dist/capture/layers/room-model-layer.js +71 -0
- package/dist/capture/layers/surface-mesh-data.d.ts +9 -0
- package/dist/capture/layers/surface-mesh-data.d.ts.map +1 -0
- package/dist/capture/layers/surface-mesh-data.js +74 -0
- package/dist/capture/layers/surface-mesh-layer.d.ts +7 -0
- package/dist/capture/layers/surface-mesh-layer.d.ts.map +1 -0
- package/dist/capture/layers/surface-mesh-layer.js +16 -0
- package/dist/capture/preview.d.ts +3 -0
- package/dist/capture/preview.d.ts.map +1 -0
- package/dist/capture/preview.js +2 -0
- package/dist/capture/source-state.d.ts +22 -0
- package/dist/capture/source-state.d.ts.map +1 -0
- package/dist/capture/source-state.js +182 -0
- package/dist/capture/stream-rendering.d.ts +14 -0
- package/dist/capture/stream-rendering.d.ts.map +1 -0
- package/dist/capture/stream-rendering.js +70 -0
- package/dist/capture/trajectory.d.ts +20 -0
- package/dist/capture/trajectory.d.ts.map +1 -0
- package/dist/capture/trajectory.js +77 -0
- package/dist/capture/use-json-artifact.d.ts +2 -0
- package/dist/capture/use-json-artifact.d.ts.map +1 -0
- package/dist/capture/use-json-artifact.js +42 -0
- package/dist/components/viewer/batched-mesh-spike.d.ts +14 -0
- package/dist/components/viewer/batched-mesh-spike.d.ts.map +1 -0
- package/dist/components/viewer/batched-mesh-spike.js +64 -0
- package/dist/components/viewer/frame-limiter.d.ts.map +1 -1
- package/dist/components/viewer/frame-limiter.js +4 -3
- package/dist/components/viewer/glb-scene.d.ts.map +1 -1
- package/dist/components/viewer/glb-scene.js +3 -1
- package/dist/components/viewer/index.d.ts.map +1 -1
- package/dist/components/viewer/index.js +70 -62
- package/dist/components/viewer/lights.d.ts.map +1 -1
- package/dist/components/viewer/lights.js +123 -89
- package/dist/components/viewer/perf-monitor.d.ts +6 -1
- package/dist/components/viewer/perf-monitor.d.ts.map +1 -1
- package/dist/components/viewer/perf-monitor.js +307 -116
- package/dist/components/viewer/perf-panel.d.ts +3 -0
- package/dist/components/viewer/perf-panel.d.ts.map +1 -0
- package/dist/components/viewer/perf-panel.js +178 -0
- package/dist/components/viewer/pointer-raycast-layers.d.ts +15 -0
- package/dist/components/viewer/pointer-raycast-layers.d.ts.map +1 -0
- package/dist/components/viewer/pointer-raycast-layers.js +28 -0
- package/dist/components/viewer/post-processing.d.ts.map +1 -1
- package/dist/components/viewer/post-processing.js +130 -83
- package/dist/components/viewer/registered-systems.d.ts +14 -5
- package/dist/components/viewer/registered-systems.d.ts.map +1 -1
- package/dist/components/viewer/registered-systems.js +22 -10
- package/dist/components/viewer/scene-atmosphere.d.ts +30 -0
- package/dist/components/viewer/scene-atmosphere.d.ts.map +1 -0
- package/dist/components/viewer/scene-atmosphere.js +89 -0
- package/dist/components/viewer/scene-environment.d.ts.map +1 -1
- package/dist/components/viewer/scene-environment.js +19 -5
- package/dist/components/viewer/scene-ground-replacement.d.ts +5 -0
- package/dist/components/viewer/scene-ground-replacement.d.ts.map +1 -0
- package/dist/components/viewer/scene-ground-replacement.js +35 -0
- package/dist/components/viewer/selection-manager.d.ts.map +1 -1
- package/dist/components/viewer/selection-manager.js +8 -16
- package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
- package/dist/components/viewer/viewer-camera.js +4 -1
- package/dist/components/viewer/viewer-presentations.d.ts +67 -0
- package/dist/components/viewer/viewer-presentations.d.ts.map +1 -0
- package/dist/components/viewer/viewer-presentations.js +120 -0
- package/dist/hooks/use-library-materials-version.d.ts +7 -0
- package/dist/hooks/use-library-materials-version.d.ts.map +1 -0
- package/dist/hooks/use-library-materials-version.js +10 -0
- package/dist/hooks/use-node-events.d.ts.map +1 -1
- package/dist/hooks/use-node-events.js +1 -0
- package/dist/index.d.ts +16 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -7
- package/dist/lib/csg-utils.d.ts +1 -0
- package/dist/lib/csg-utils.d.ts.map +1 -1
- package/dist/lib/csg-utils.js +6 -1
- package/dist/lib/gpu-perf.d.ts +0 -2
- package/dist/lib/gpu-perf.d.ts.map +1 -1
- package/dist/lib/gpu-perf.js +7 -21
- package/dist/lib/isolation.d.ts +14 -30
- package/dist/lib/isolation.d.ts.map +1 -1
- package/dist/lib/isolation.js +62 -66
- package/dist/lib/layer-pass.d.ts +28 -0
- package/dist/lib/layer-pass.d.ts.map +1 -0
- package/dist/lib/layer-pass.js +254 -0
- package/dist/lib/materials.d.ts +12 -0
- package/dist/lib/materials.d.ts.map +1 -1
- package/dist/lib/materials.js +52 -17
- package/dist/lib/merged-outline-node.d.ts +17 -8
- package/dist/lib/merged-outline-node.d.ts.map +1 -1
- package/dist/lib/merged-outline-node.js +194 -52
- package/dist/lib/perf-actions.d.ts +55 -0
- package/dist/lib/perf-actions.d.ts.map +1 -0
- package/dist/lib/perf-actions.js +211 -0
- package/dist/lib/perf-observers.d.ts +2 -0
- package/dist/lib/perf-observers.d.ts.map +1 -0
- package/dist/lib/perf-observers.js +17 -0
- package/dist/lib/perf-panel-store.d.ts +71 -0
- package/dist/lib/perf-panel-store.d.ts.map +1 -0
- package/dist/lib/perf-panel-store.js +29 -0
- package/dist/lib/perf-tracks.d.ts +43 -0
- package/dist/lib/perf-tracks.d.ts.map +1 -0
- package/dist/lib/perf-tracks.js +113 -0
- package/dist/lib/pointer-events.d.ts +7 -0
- package/dist/lib/pointer-events.d.ts.map +1 -0
- package/dist/lib/pointer-events.js +680 -0
- package/dist/lib/post-processing-resources.d.ts +12 -0
- package/dist/lib/post-processing-resources.d.ts.map +1 -0
- package/dist/lib/post-processing-resources.js +19 -0
- package/dist/lib/renderer-capability.d.ts.map +1 -1
- package/dist/lib/renderer-capability.js +4 -2
- package/dist/lib/scene-visibility.d.ts +2 -1
- package/dist/lib/scene-visibility.d.ts.map +1 -1
- package/dist/lib/scene-visibility.js +25 -2
- package/dist/lib/snapshot-pipeline.d.ts +3 -1
- package/dist/lib/snapshot-pipeline.d.ts.map +1 -1
- package/dist/lib/snapshot-pipeline.js +32 -16
- package/dist/store/use-viewer.d.ts +5 -3
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/store/use-viewer.js +2 -0
- package/dist/systems/ceiling/ceiling-system.js +1 -1
- package/dist/systems/door/door-animation-system.d.ts.map +1 -1
- package/dist/systems/door/door-animation-system.js +6 -9
- package/dist/systems/door/door-system.d.ts.map +1 -1
- package/dist/systems/door/door-system.js +13 -2
- package/dist/systems/floor-elevation/floor-elevation-system.d.ts.map +1 -1
- package/dist/systems/floor-elevation/floor-elevation-system.js +7 -3
- package/dist/systems/geometry/geometry-system.d.ts.map +1 -1
- package/dist/systems/geometry/geometry-system.js +7 -2
- package/dist/systems/level/level-system.d.ts.map +1 -1
- package/dist/systems/level/level-system.js +1 -1
- package/dist/systems/level/level-utils.d.ts +9 -0
- package/dist/systems/level/level-utils.d.ts.map +1 -1
- package/dist/systems/level/level-utils.js +14 -0
- package/dist/systems/perf-action-settle/perf-action-settle-system.d.ts +8 -0
- package/dist/systems/perf-action-settle/perf-action-settle-system.d.ts.map +1 -0
- package/dist/systems/perf-action-settle/perf-action-settle-system.js +32 -0
- package/dist/systems/roof/roof-system.d.ts.map +1 -1
- package/dist/systems/roof/roof-system.js +1196 -58
- package/dist/systems/scan/scan-system.js +6 -5
- package/dist/systems/slab/slab-system.d.ts.map +1 -1
- package/dist/systems/slab/slab-system.js +1 -2
- package/dist/systems/stair/stair-system.d.ts.map +1 -1
- package/dist/systems/stair/stair-system.js +9 -8
- package/dist/systems/surface-hole-geometry.d.ts +1 -1
- package/dist/systems/surface-hole-geometry.d.ts.map +1 -1
- package/dist/systems/surface-hole-geometry.js +1 -1
- package/dist/systems/wall/level-miter-cache.d.ts.map +1 -1
- package/dist/systems/wall/wall-build-lifecycle.d.ts +9 -0
- package/dist/systems/wall/wall-build-lifecycle.d.ts.map +1 -0
- package/dist/systems/wall/wall-build-lifecycle.js +74 -0
- package/dist/systems/wall/wall-cutout-cache.d.ts +57 -0
- package/dist/systems/wall/wall-cutout-cache.d.ts.map +1 -0
- package/dist/systems/wall/wall-cutout-cache.js +304 -0
- package/dist/systems/wall/wall-cutout.d.ts +12 -1
- package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
- package/dist/systems/wall/wall-cutout.js +24 -161
- package/dist/systems/wall/wall-material-variant.d.ts +31 -0
- package/dist/systems/wall/wall-material-variant.d.ts.map +1 -0
- package/dist/systems/wall/wall-material-variant.js +10 -0
- package/dist/systems/wall/wall-materials.d.ts +6 -0
- package/dist/systems/wall/wall-materials.d.ts.map +1 -1
- package/dist/systems/wall/wall-materials.js +29 -13
- package/dist/systems/wall/wall-placeholder-sweep.d.ts +50 -0
- package/dist/systems/wall/wall-placeholder-sweep.d.ts.map +1 -0
- package/dist/systems/wall/wall-placeholder-sweep.js +51 -0
- package/dist/systems/wall/wall-rebuild-notifications.d.ts +5 -0
- package/dist/systems/wall/wall-rebuild-notifications.d.ts.map +1 -0
- package/dist/systems/wall/wall-rebuild-notifications.js +23 -0
- package/dist/systems/wall/wall-system.d.ts +11 -4
- package/dist/systems/wall/wall-system.d.ts.map +1 -1
- package/dist/systems/wall/wall-system.js +405 -153
- package/dist/systems/window/window-animation-system.d.ts.map +1 -1
- package/dist/systems/window/window-animation-system.js +9 -8
- package/dist/systems/window/window-system.d.ts +1 -0
- package/dist/systems/window/window-system.d.ts.map +1 -1
- package/dist/systems/window/window-system.js +16 -2
- package/dist/systems/zone/zone-system.d.ts.map +1 -1
- package/dist/systems/zone/zone-system.js +28 -2
- package/package.json +17 -4
- package/dist/lib/polygon-union.d.ts +0 -4
- package/dist/lib/polygon-union.d.ts.map +0 -1
- package/dist/lib/polygon-union.js +0 -289
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export type PerfActionReceipt = {
|
|
2
|
+
name: string;
|
|
3
|
+
/** Free-form context, e.g. the node id or kind. */
|
|
4
|
+
detail: string;
|
|
5
|
+
/** begin → commit (the human gesture; 0 for instant actions like undo). */
|
|
6
|
+
dragMs: number;
|
|
7
|
+
/** commit → fully settled (rebuilds + one GPU sample after quiet). */
|
|
8
|
+
settleMs: number;
|
|
9
|
+
/** begin → settled. */
|
|
10
|
+
totalMs: number;
|
|
11
|
+
/** Frames observed between commit and settled. */
|
|
12
|
+
settleFrames: number;
|
|
13
|
+
/** Per-track attribution over begin → settled, sorted by totalMs desc. */
|
|
14
|
+
tracks: Array<{
|
|
15
|
+
name: string;
|
|
16
|
+
totalMs: number;
|
|
17
|
+
count: number;
|
|
18
|
+
}>;
|
|
19
|
+
outcome: 'settled' | 'interrupted' | 'timeout';
|
|
20
|
+
endedAt: number;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Start attributing samples to a named action. Interrupts any active one.
|
|
24
|
+
* Returns an id the caller can compare against `getActivePerfActionId()` to
|
|
25
|
+
* commit only the action it actually began.
|
|
26
|
+
*/
|
|
27
|
+
export declare function beginPerfAction(name: string, detail?: string): number | null;
|
|
28
|
+
/** The gesture ended (pointer up / operation dispatched); settling begins. */
|
|
29
|
+
export declare function commitPerfAction(): void;
|
|
30
|
+
/** The gesture was aborted (Escape mid-drag); discard without a settle wait. */
|
|
31
|
+
export declare function cancelPerfAction(): void;
|
|
32
|
+
/** Convenience for instant actions (undo, level switch): begin + commit. */
|
|
33
|
+
export declare function markPerfAction(name: string, detail?: string): void;
|
|
34
|
+
/**
|
|
35
|
+
* Whether an action is currently being attributed. Lets a generic call site
|
|
36
|
+
* (the interaction scope) yield to a more specific one that began first.
|
|
37
|
+
*/
|
|
38
|
+
export declare function hasActivePerfAction(): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Like `hasActivePerfAction`, but false once the active action has committed.
|
|
41
|
+
* A generic bracket yields to an UNCOMMITTED action (a gesture in flight) but
|
|
42
|
+
* must be free to start a new receipt while the previous one is merely
|
|
43
|
+
* settling — beginPerfAction then finalizes the settling one as interrupted.
|
|
44
|
+
*/
|
|
45
|
+
export declare function hasUncommittedPerfAction(): boolean;
|
|
46
|
+
/** Id of the action currently attributing samples, if any. */
|
|
47
|
+
export declare function getActivePerfActionId(): number | null;
|
|
48
|
+
/**
|
|
49
|
+
* Called once per frame by the viewer settle system with the current dirty
|
|
50
|
+
* count and the wall system's deferred-neighbour backlog. Also the ledger's
|
|
51
|
+
* only heartbeat, so it is where a stuck action gets released.
|
|
52
|
+
*/
|
|
53
|
+
export declare function notifyPerfActionFrame(dirtyCount: number, pendingRebuilds: number): void;
|
|
54
|
+
export declare function usePerfActionReceipts(): PerfActionReceipt[];
|
|
55
|
+
//# sourceMappingURL=perf-actions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"perf-actions.d.ts","sourceRoot":"","sources":["../../src/lib/perf-actions.ts"],"names":[],"mappings":"AAkBA,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,mDAAmD;IACnD,MAAM,EAAE,MAAM,CAAA;IACd,2EAA2E;IAC3E,MAAM,EAAE,MAAM,CAAA;IACd,sEAAsE;IACtE,QAAQ,EAAE,MAAM,CAAA;IAChB,uBAAuB;IACvB,OAAO,EAAE,MAAM,CAAA;IACf,kDAAkD;IAClD,YAAY,EAAE,MAAM,CAAA;IACpB,0EAA0E;IAC1E,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC/D,OAAO,EAAE,SAAS,GAAG,aAAa,GAAG,SAAS,CAAA;IAC9C,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AA8FD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,SAAK,GAAG,MAAM,GAAG,IAAI,CAmCxE;AAED,8EAA8E;AAC9E,wBAAgB,gBAAgB,IAAI,IAAI,CAGvC;AAED,gFAAgF;AAChF,wBAAgB,gBAAgB,IAAI,IAAI,CAGvC;AAED,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,SAAK,GAAG,IAAI,CAG9D;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,OAAO,CAE7C;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,IAAI,OAAO,CAElD;AAED,8DAA8D;AAC9D,wBAAgB,qBAAqB,IAAI,MAAM,GAAG,IAAI,CAErD;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,IAAI,CAgBvF;AAgBD,wBAAgB,qBAAqB,IAAI,iBAAiB,EAAE,CAE3D"}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
// Action-cost ledger for `?perf`.
|
|
2
|
+
//
|
|
3
|
+
// An "action" is one user edit gesture: a wall-endpoint drag, a door move, an
|
|
4
|
+
// undo, a level switch. The editor package brackets the gesture with
|
|
5
|
+
// `beginPerfAction` / `commitPerfAction`; every perf-tracks sample recorded in
|
|
6
|
+
// between (wall-csg, geometry, react-render, gpu-render, …) is attributed to
|
|
7
|
+
// it. The action is "settled" only when the scene has finished digesting the
|
|
8
|
+
// edit: dirty queue empty, deferred wall neighbour rebuilds flushed, and one
|
|
9
|
+
// more GPU sample resolved after that — i.e. the user actually sees the final
|
|
10
|
+
// result. The settle system in the viewer feeds that state per frame via
|
|
11
|
+
// `notifyPerfActionFrame`.
|
|
12
|
+
//
|
|
13
|
+
// Everything is a no-op without `?perf`.
|
|
14
|
+
import { useSyncExternalStore } from 'react';
|
|
15
|
+
import { PERF_OVERLAY_ENABLED } from './gpu-perf';
|
|
16
|
+
import { subscribePerfSamples } from './perf-tracks';
|
|
17
|
+
const SETTLE_TIMEOUT_MS = 5000;
|
|
18
|
+
// A gesture that begins and never commits (a hover preview, a drag whose
|
|
19
|
+
// pointerup never reached the caller) is never settle-checked, so without an
|
|
20
|
+
// absolute cap it would hold its sample subscription — and keep growing its
|
|
21
|
+
// buckets — for the rest of the session.
|
|
22
|
+
const UNCOMMITTED_TIMEOUT_MS = 60_000;
|
|
23
|
+
const MAX_RECEIPTS = 5;
|
|
24
|
+
let active = null;
|
|
25
|
+
let actionSeq = 0;
|
|
26
|
+
// Whether this device has ever produced a real timestamp-query sample. Without
|
|
27
|
+
// `timestamp-query` support no 'gpu-render' sample can ever arrive, so settle
|
|
28
|
+
// falls back to the queue fence — see the sample listener below.
|
|
29
|
+
let gpuTimestampsSeen = false;
|
|
30
|
+
let receipts = [];
|
|
31
|
+
const listeners = new Set();
|
|
32
|
+
function emitReceipts() {
|
|
33
|
+
for (const listener of listeners)
|
|
34
|
+
listener();
|
|
35
|
+
}
|
|
36
|
+
function finalize(outcome) {
|
|
37
|
+
const action = active;
|
|
38
|
+
if (!action)
|
|
39
|
+
return;
|
|
40
|
+
active = null;
|
|
41
|
+
action.unsubscribe();
|
|
42
|
+
const now = performance.now();
|
|
43
|
+
const committedAt = action.committedAt ?? now;
|
|
44
|
+
const receipt = {
|
|
45
|
+
name: action.name,
|
|
46
|
+
detail: action.detail,
|
|
47
|
+
dragMs: committedAt - action.startedAt,
|
|
48
|
+
settleMs: now - committedAt,
|
|
49
|
+
totalMs: now - action.startedAt,
|
|
50
|
+
settleFrames: action.settleFrames,
|
|
51
|
+
tracks: [...action.buckets.entries()]
|
|
52
|
+
.map(([name, b]) => ({ name, totalMs: b.totalMs, count: b.count }))
|
|
53
|
+
.sort((a, b) => b.totalMs - a.totalMs),
|
|
54
|
+
outcome,
|
|
55
|
+
endedAt: now,
|
|
56
|
+
};
|
|
57
|
+
receipts = [receipt, ...receipts].slice(0, MAX_RECEIPTS);
|
|
58
|
+
emitReceipts();
|
|
59
|
+
// One timeline entry per action so recordings show the full span with its
|
|
60
|
+
// breakdown attached.
|
|
61
|
+
try {
|
|
62
|
+
performance.measure(`${action.name}${action.detail ? ` ${action.detail}` : ''}`, {
|
|
63
|
+
start: action.startedAt,
|
|
64
|
+
end: now,
|
|
65
|
+
detail: {
|
|
66
|
+
devtools: {
|
|
67
|
+
dataType: 'track-entry',
|
|
68
|
+
track: 'Actions',
|
|
69
|
+
trackGroup: 'Pascal',
|
|
70
|
+
color: outcome === 'settled' ? 'secondary' : 'error',
|
|
71
|
+
properties: [
|
|
72
|
+
['outcome', outcome],
|
|
73
|
+
['drag ms', receipt.dragMs.toFixed(1)],
|
|
74
|
+
['settle ms', receipt.settleMs.toFixed(1)],
|
|
75
|
+
...receipt.tracks
|
|
76
|
+
.slice(0, 6)
|
|
77
|
+
.map((t) => [t.name, `${t.totalMs.toFixed(1)}ms (${t.count}×)`]),
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
catch { }
|
|
84
|
+
// eslint-disable-next-line no-console
|
|
85
|
+
console.log(`[perf] ${action.name}${action.detail ? ` (${action.detail})` : ''}: ` +
|
|
86
|
+
`${receipt.totalMs.toFixed(0)}ms total — drag ${receipt.dragMs.toFixed(0)}, ` +
|
|
87
|
+
`settle ${receipt.settleMs.toFixed(0)} over ${receipt.settleFrames} frames [${outcome}] — ` +
|
|
88
|
+
receipt.tracks
|
|
89
|
+
.slice(0, 6)
|
|
90
|
+
.map((t) => `${t.name} ${t.totalMs.toFixed(1)}ms`)
|
|
91
|
+
.join(', '));
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Start attributing samples to a named action. Interrupts any active one.
|
|
95
|
+
* Returns an id the caller can compare against `getActivePerfActionId()` to
|
|
96
|
+
* commit only the action it actually began.
|
|
97
|
+
*/
|
|
98
|
+
export function beginPerfAction(name, detail = '') {
|
|
99
|
+
if (!PERF_OVERLAY_ENABLED)
|
|
100
|
+
return null;
|
|
101
|
+
if (active)
|
|
102
|
+
finalize('interrupted');
|
|
103
|
+
const id = ++actionSeq;
|
|
104
|
+
const buckets = new Map();
|
|
105
|
+
active = {
|
|
106
|
+
id,
|
|
107
|
+
name,
|
|
108
|
+
detail,
|
|
109
|
+
startedAt: performance.now(),
|
|
110
|
+
committedAt: null,
|
|
111
|
+
settleFrames: 0,
|
|
112
|
+
awaitingGpu: false,
|
|
113
|
+
buckets,
|
|
114
|
+
unsubscribe: subscribePerfSamples((track, ms) => {
|
|
115
|
+
const bucket = buckets.get(track);
|
|
116
|
+
if (bucket) {
|
|
117
|
+
bucket.totalMs += ms;
|
|
118
|
+
bucket.count += 1;
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
buckets.set(track, { totalMs: ms, count: 1 });
|
|
122
|
+
}
|
|
123
|
+
if (track === 'gpu-render')
|
|
124
|
+
gpuTimestampsSeen = true;
|
|
125
|
+
// A GPU sample landing after the quiet point is the settle signal. On
|
|
126
|
+
// devices without timestamp-query no 'gpu-render' sample ever arrives —
|
|
127
|
+
// the queue fence is the closest "the user saw it" stand-in there.
|
|
128
|
+
if (active?.awaitingGpu &&
|
|
129
|
+
(track === 'gpu-render' || (!gpuTimestampsSeen && track === 'gpu-queue'))) {
|
|
130
|
+
finalize('settled');
|
|
131
|
+
}
|
|
132
|
+
}),
|
|
133
|
+
};
|
|
134
|
+
return id;
|
|
135
|
+
}
|
|
136
|
+
/** The gesture ended (pointer up / operation dispatched); settling begins. */
|
|
137
|
+
export function commitPerfAction() {
|
|
138
|
+
if (!active || active.committedAt !== null)
|
|
139
|
+
return;
|
|
140
|
+
active.committedAt = performance.now();
|
|
141
|
+
}
|
|
142
|
+
/** The gesture was aborted (Escape mid-drag); discard without a settle wait. */
|
|
143
|
+
export function cancelPerfAction() {
|
|
144
|
+
if (!active)
|
|
145
|
+
return;
|
|
146
|
+
finalize('interrupted');
|
|
147
|
+
}
|
|
148
|
+
/** Convenience for instant actions (undo, level switch): begin + commit. */
|
|
149
|
+
export function markPerfAction(name, detail = '') {
|
|
150
|
+
beginPerfAction(name, detail);
|
|
151
|
+
commitPerfAction();
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Whether an action is currently being attributed. Lets a generic call site
|
|
155
|
+
* (the interaction scope) yield to a more specific one that began first.
|
|
156
|
+
*/
|
|
157
|
+
export function hasActivePerfAction() {
|
|
158
|
+
return active !== null;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Like `hasActivePerfAction`, but false once the active action has committed.
|
|
162
|
+
* A generic bracket yields to an UNCOMMITTED action (a gesture in flight) but
|
|
163
|
+
* must be free to start a new receipt while the previous one is merely
|
|
164
|
+
* settling — beginPerfAction then finalizes the settling one as interrupted.
|
|
165
|
+
*/
|
|
166
|
+
export function hasUncommittedPerfAction() {
|
|
167
|
+
return active !== null && active.committedAt === null;
|
|
168
|
+
}
|
|
169
|
+
/** Id of the action currently attributing samples, if any. */
|
|
170
|
+
export function getActivePerfActionId() {
|
|
171
|
+
return active?.id ?? null;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Called once per frame by the viewer settle system with the current dirty
|
|
175
|
+
* count and the wall system's deferred-neighbour backlog. Also the ledger's
|
|
176
|
+
* only heartbeat, so it is where a stuck action gets released.
|
|
177
|
+
*/
|
|
178
|
+
export function notifyPerfActionFrame(dirtyCount, pendingRebuilds) {
|
|
179
|
+
const action = active;
|
|
180
|
+
if (!action)
|
|
181
|
+
return;
|
|
182
|
+
const now = performance.now();
|
|
183
|
+
if (action.committedAt === null) {
|
|
184
|
+
if (now - action.startedAt > UNCOMMITTED_TIMEOUT_MS)
|
|
185
|
+
finalize('interrupted');
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
action.settleFrames += 1;
|
|
189
|
+
if (now - action.committedAt > SETTLE_TIMEOUT_MS) {
|
|
190
|
+
finalize('timeout');
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (!action.awaitingGpu && dirtyCount === 0 && pendingRebuilds === 0) {
|
|
194
|
+
action.awaitingGpu = true;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// Module-level so the panel's twice-a-second re-render doesn't tear the
|
|
198
|
+
// subscription down and rebuild it; `receipts` is replaced, never mutated, so
|
|
199
|
+
// the snapshot is stable between finalizes.
|
|
200
|
+
function subscribeReceipts(listener) {
|
|
201
|
+
listeners.add(listener);
|
|
202
|
+
return () => {
|
|
203
|
+
listeners.delete(listener);
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
function getReceipts() {
|
|
207
|
+
return receipts;
|
|
208
|
+
}
|
|
209
|
+
export function usePerfActionReceipts() {
|
|
210
|
+
return useSyncExternalStore(subscribeReceipts, getReceipts, getReceipts);
|
|
211
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"perf-observers.d.ts","sourceRoot":"","sources":["../../src/lib/perf-observers.ts"],"names":[],"mappings":"AAKA,wBAAgB,iBAAiB,IAAI,IAAI,CAYxC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { PERF_OVERLAY_ENABLED } from './gpu-perf';
|
|
2
|
+
import { recordPerfSample } from './perf-tracks';
|
|
3
|
+
let initialized = false;
|
|
4
|
+
export function initPerfObservers() {
|
|
5
|
+
if (!PERF_OVERLAY_ENABLED || initialized)
|
|
6
|
+
return;
|
|
7
|
+
initialized = true;
|
|
8
|
+
try {
|
|
9
|
+
const observer = new PerformanceObserver((list) => {
|
|
10
|
+
for (const entry of list.getEntries()) {
|
|
11
|
+
recordPerfSample('long-task', entry.duration);
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
observer.observe({ type: 'longtask', buffered: true });
|
|
15
|
+
}
|
|
16
|
+
catch { }
|
|
17
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
export type PerfTrackLine = {
|
|
2
|
+
name: string;
|
|
3
|
+
totalMs: number;
|
|
4
|
+
count: number;
|
|
5
|
+
maxMs: number;
|
|
6
|
+
};
|
|
7
|
+
export type PerfStats = {
|
|
8
|
+
fps: number;
|
|
9
|
+
frameMs: number;
|
|
10
|
+
frameMaxMs: number;
|
|
11
|
+
encodeMs: number;
|
|
12
|
+
encodeMaxMs: number;
|
|
13
|
+
gpuMs: number;
|
|
14
|
+
gpuMaxMs: number;
|
|
15
|
+
gpuTracked: boolean;
|
|
16
|
+
queueMs: number;
|
|
17
|
+
queueMaxMs: number;
|
|
18
|
+
drawCalls: number;
|
|
19
|
+
triangles: number;
|
|
20
|
+
batch: PerfBatchStats;
|
|
21
|
+
dirty: number;
|
|
22
|
+
dirtyDetail: string;
|
|
23
|
+
geometries: number;
|
|
24
|
+
textures: number;
|
|
25
|
+
gpuBytes: number;
|
|
26
|
+
heapBytes: number;
|
|
27
|
+
meshes: number;
|
|
28
|
+
lines: number;
|
|
29
|
+
sprites: number;
|
|
30
|
+
lights: number;
|
|
31
|
+
tracks: PerfTrackLine[];
|
|
32
|
+
};
|
|
33
|
+
export declare function publishPerfStats(stats: PerfStats): void;
|
|
34
|
+
export declare function usePerfStats(): PerfStats | null;
|
|
35
|
+
/**
|
|
36
|
+
* Batch membership, published by the batch systems themselves (the panel's
|
|
37
|
+
* collector cannot read their stores across packages). Stable truth — items
|
|
38
|
+
* and instances currently drawn through batch containers — unlike the
|
|
39
|
+
* per-pass `_multiDrawCount`, which snapshots whichever camera (main, shadow,
|
|
40
|
+
* outline) culled the batch last and flips between passes. On WebGPU each
|
|
41
|
+
* batched instance still counts once in `drawCalls` (the backend loops
|
|
42
|
+
* drawIndexed per visible instance), so without this row a batched scene
|
|
43
|
+
* looks no cheaper than an unbatched one — the saving is encode cost per
|
|
44
|
+
* call, not call count.
|
|
45
|
+
*/
|
|
46
|
+
export type PerfBatchStats = {
|
|
47
|
+
items: number;
|
|
48
|
+
instances: number;
|
|
49
|
+
containers: number;
|
|
50
|
+
releases?: number;
|
|
51
|
+
joins?: number;
|
|
52
|
+
geometryReplacements?: number;
|
|
53
|
+
overflowRebuilds?: number;
|
|
54
|
+
wallDrain?: {
|
|
55
|
+
initialBuildActive: boolean;
|
|
56
|
+
wallsConsumedThisFrame: number;
|
|
57
|
+
budgetExits: number;
|
|
58
|
+
heavyExits: number;
|
|
59
|
+
drainedExits: number;
|
|
60
|
+
capExits: number;
|
|
61
|
+
pendingNeighbours: number;
|
|
62
|
+
firstBuilds: number;
|
|
63
|
+
reinvalidationBuilds: number;
|
|
64
|
+
neighbourEnqueues: number;
|
|
65
|
+
};
|
|
66
|
+
geometryBytesCopied?: number;
|
|
67
|
+
};
|
|
68
|
+
export declare function publishPerfBatchStats(stats: Partial<PerfBatchStats>): void;
|
|
69
|
+
export declare function publishPerfWallDrainStats(stats: NonNullable<PerfBatchStats['wallDrain']>): void;
|
|
70
|
+
export declare function readPerfBatchStats(): PerfBatchStats;
|
|
71
|
+
//# sourceMappingURL=perf-panel-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"perf-panel-store.d.ts","sourceRoot":"","sources":["../../src/lib/perf-panel-store.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,aAAa,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAE3F,MAAM,MAAM,SAAS,GAAG;IACtB,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,OAAO,CAAA;IACnB,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,cAAc,CAAA;IACrB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,aAAa,EAAE,CAAA;CACxB,CAAA;AAKD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAGvD;AAOD,wBAAgB,YAAY,IAAI,SAAS,GAAG,IAAI,CAM/C;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAC7B,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,SAAS,CAAC,EAAE;QACV,kBAAkB,EAAE,OAAO,CAAA;QAC3B,sBAAsB,EAAE,MAAM,CAAA;QAC9B,WAAW,EAAE,MAAM,CAAA;QACnB,UAAU,EAAE,MAAM,CAAA;QAClB,YAAY,EAAE,MAAM,CAAA;QACpB,QAAQ,EAAE,MAAM,CAAA;QAChB,iBAAiB,EAAE,MAAM,CAAA;QACzB,WAAW,EAAE,MAAM,CAAA;QACnB,oBAAoB,EAAE,MAAM,CAAA;QAC5B,iBAAiB,EAAE,MAAM,CAAA;KAC1B,CAAA;IACD,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC7B,CAAA;AAID,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,IAAI,CAE1E;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,WAAW,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC,GAAG,IAAI,CAE/F;AAED,wBAAgB,kBAAkB,IAAI,cAAc,CAEnD"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Bridge between the in-canvas collector (perf-monitor.tsx, R3F reconciler)
|
|
2
|
+
// and the DOM panel (perf-panel.tsx). react-dom portals can't cross the R3F
|
|
3
|
+
// renderer boundary, so the collector publishes here and the panel — mounted
|
|
4
|
+
// outside <Canvas> — subscribes via useSyncExternalStore.
|
|
5
|
+
import { useSyncExternalStore } from 'react';
|
|
6
|
+
let current = null;
|
|
7
|
+
const listeners = new Set();
|
|
8
|
+
export function publishPerfStats(stats) {
|
|
9
|
+
current = stats;
|
|
10
|
+
for (const listener of listeners)
|
|
11
|
+
listener();
|
|
12
|
+
}
|
|
13
|
+
function subscribe(listener) {
|
|
14
|
+
listeners.add(listener);
|
|
15
|
+
return () => listeners.delete(listener);
|
|
16
|
+
}
|
|
17
|
+
export function usePerfStats() {
|
|
18
|
+
return useSyncExternalStore(subscribe, () => current, () => null);
|
|
19
|
+
}
|
|
20
|
+
let batchStats = { items: 0, instances: 0, containers: 0 };
|
|
21
|
+
export function publishPerfBatchStats(stats) {
|
|
22
|
+
batchStats = { ...batchStats, ...stats };
|
|
23
|
+
}
|
|
24
|
+
export function publishPerfWallDrainStats(stats) {
|
|
25
|
+
batchStats.wallDrain = stats;
|
|
26
|
+
}
|
|
27
|
+
export function readPerfBatchStats() {
|
|
28
|
+
return batchStats;
|
|
29
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export type PerfCounterBucket = {
|
|
2
|
+
totalMs: number;
|
|
3
|
+
maxMs: number;
|
|
4
|
+
count: number;
|
|
5
|
+
};
|
|
6
|
+
/** DevTools palette names accepted by the extensibility API. */
|
|
7
|
+
export type PerfTrackColor = 'primary' | 'primary-light' | 'primary-dark' | 'secondary' | 'secondary-light' | 'secondary-dark' | 'tertiary' | 'tertiary-light' | 'tertiary-dark' | 'error';
|
|
8
|
+
type PerfSampleListener = (track: string, ms: number) => void;
|
|
9
|
+
export declare function subscribePerfSamples(listener: PerfSampleListener): () => void;
|
|
10
|
+
/**
|
|
11
|
+
* Time a synchronous block and file it under `track`. The label defaults to
|
|
12
|
+
* the track name; pass `name` for per-entry granularity (e.g. a node id) —
|
|
13
|
+
* it only affects the DevTools lane, not the aggregate bucket.
|
|
14
|
+
*/
|
|
15
|
+
export declare function timeSpan<T>(track: string, fn: () => T, opts?: {
|
|
16
|
+
name?: string;
|
|
17
|
+
color?: PerfTrackColor;
|
|
18
|
+
properties?: Array<[string, string]>;
|
|
19
|
+
}): T;
|
|
20
|
+
/**
|
|
21
|
+
* Span for non-callback shapes (spans crossing await points or frames).
|
|
22
|
+
* `beginSpan` returns null when perf is off — callers pass the handle back to
|
|
23
|
+
* `endSpan`, which no-ops on null.
|
|
24
|
+
*/
|
|
25
|
+
export type PerfSpanHandle = {
|
|
26
|
+
track: string;
|
|
27
|
+
name: string;
|
|
28
|
+
start: number;
|
|
29
|
+
color: PerfTrackColor;
|
|
30
|
+
};
|
|
31
|
+
export declare function beginSpan(track: string, opts?: {
|
|
32
|
+
name?: string;
|
|
33
|
+
color?: PerfTrackColor;
|
|
34
|
+
}): PerfSpanHandle | null;
|
|
35
|
+
export declare function endSpan(handle: PerfSpanHandle | null, properties?: Array<[string, string]>): void;
|
|
36
|
+
/** Record a duration measured externally (no measure emitted). */
|
|
37
|
+
export declare function recordPerfSample(track: string, ms: number): void;
|
|
38
|
+
/** Hand the current window's buckets to the overlay and start a new window. */
|
|
39
|
+
export declare function drainPerfCounters(): Map<string, PerfCounterBucket>;
|
|
40
|
+
/** Drop accumulated timeline entries so long `?perf` sessions don't leak. */
|
|
41
|
+
export declare function clearPerfMeasures(): void;
|
|
42
|
+
export {};
|
|
43
|
+
//# sourceMappingURL=perf-tracks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"perf-tracks.d.ts","sourceRoot":"","sources":["../../src/lib/perf-tracks.ts"],"names":[],"mappings":"AAkBA,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED,gEAAgE;AAChE,MAAM,MAAM,cAAc,GACtB,SAAS,GACT,eAAe,GACf,cAAc,GACd,WAAW,GACX,iBAAiB,GACjB,gBAAgB,GAChB,UAAU,GACV,gBAAgB,GAChB,eAAe,GACf,OAAO,CAAA;AAOX,KAAK,kBAAkB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;AAG7D,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,kBAAkB,GAAG,MAAM,IAAI,CAG7E;AAyCD;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EACxB,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,MAAM,CAAC,EACX,IAAI,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,cAAc,CAAC;IAAC,UAAU,CAAC,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;CAAE,GACrF,CAAC,CAUH;AAED;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,cAAc,CAAA;CAAE,CAAA;AAElG,wBAAgB,SAAS,CACvB,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,cAAc,CAAA;CAAE,GAC/C,cAAc,GAAG,IAAI,CAQvB;AAED,wBAAgB,OAAO,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,EAAE,UAAU,CAAC,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,IAAI,CAKjG;AAED,kEAAkE;AAClE,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAGhE;AAED,+EAA+E;AAC/E,wBAAgB,iBAAiB,IAAI,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAIlE;AAED,6EAA6E;AAC7E,wBAAgB,iBAAiB,IAAI,IAAI,CAOxC"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Shared sink for `?perf` instrumentation.
|
|
2
|
+
//
|
|
3
|
+
// Two outputs from one call site:
|
|
4
|
+
// 1. Chrome DevTools Performance panel custom tracks — every span becomes a
|
|
5
|
+
// `performance.measure` with a `detail.devtools` payload, so Pascal systems
|
|
6
|
+
// show up as named lanes in the flame chart while recording.
|
|
7
|
+
// 2. Per-window aggregates — `drainPerfCounters()` hands the overlay one
|
|
8
|
+
// bucket per track (total/max/count since the last drain), so the panel can
|
|
9
|
+
// print "wall-csg 9.8ms" without touching the timeline.
|
|
10
|
+
//
|
|
11
|
+
// Everything is gated on PERF_OVERLAY_ENABLED: when `?perf` is absent the
|
|
12
|
+
// helpers reduce to calling `fn()` directly / returning early, so hot paths pay
|
|
13
|
+
// only a boolean check. Measures otherwise accumulate in the browser's
|
|
14
|
+
// timeline buffer indefinitely — the overlay is responsible for calling
|
|
15
|
+
// `clearPerfMeasures()` on its drain tick.
|
|
16
|
+
import { PERF_OVERLAY_ENABLED } from './gpu-perf';
|
|
17
|
+
const counters = new Map();
|
|
18
|
+
const sampleListeners = new Set();
|
|
19
|
+
export function subscribePerfSamples(listener) {
|
|
20
|
+
sampleListeners.add(listener);
|
|
21
|
+
return () => sampleListeners.delete(listener);
|
|
22
|
+
}
|
|
23
|
+
function record(track, ms) {
|
|
24
|
+
const bucket = counters.get(track);
|
|
25
|
+
if (bucket) {
|
|
26
|
+
bucket.totalMs += ms;
|
|
27
|
+
bucket.count += 1;
|
|
28
|
+
if (ms > bucket.maxMs)
|
|
29
|
+
bucket.maxMs = ms;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
counters.set(track, { totalMs: ms, maxMs: ms, count: 1 });
|
|
33
|
+
}
|
|
34
|
+
for (const listener of sampleListeners)
|
|
35
|
+
listener(track, ms);
|
|
36
|
+
}
|
|
37
|
+
function emitMeasure(track, name, start, end, color, properties) {
|
|
38
|
+
try {
|
|
39
|
+
performance.measure(name, {
|
|
40
|
+
start,
|
|
41
|
+
end,
|
|
42
|
+
detail: {
|
|
43
|
+
devtools: {
|
|
44
|
+
dataType: 'track-entry',
|
|
45
|
+
track,
|
|
46
|
+
trackGroup: 'Pascal',
|
|
47
|
+
color,
|
|
48
|
+
...(properties ? { properties } : {}),
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Older browsers reject the options bag — aggregates still work.
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Time a synchronous block and file it under `track`. The label defaults to
|
|
59
|
+
* the track name; pass `name` for per-entry granularity (e.g. a node id) —
|
|
60
|
+
* it only affects the DevTools lane, not the aggregate bucket.
|
|
61
|
+
*/
|
|
62
|
+
export function timeSpan(track, fn, opts) {
|
|
63
|
+
if (!PERF_OVERLAY_ENABLED)
|
|
64
|
+
return fn();
|
|
65
|
+
const start = performance.now();
|
|
66
|
+
try {
|
|
67
|
+
return fn();
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
const end = performance.now();
|
|
71
|
+
record(track, end - start);
|
|
72
|
+
emitMeasure(track, opts?.name ?? track, start, end, opts?.color ?? 'primary', opts?.properties);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
export function beginSpan(track, opts) {
|
|
76
|
+
if (!PERF_OVERLAY_ENABLED)
|
|
77
|
+
return null;
|
|
78
|
+
return {
|
|
79
|
+
track,
|
|
80
|
+
name: opts?.name ?? track,
|
|
81
|
+
start: performance.now(),
|
|
82
|
+
color: opts?.color ?? 'primary',
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
export function endSpan(handle, properties) {
|
|
86
|
+
if (!handle)
|
|
87
|
+
return;
|
|
88
|
+
const end = performance.now();
|
|
89
|
+
record(handle.track, end - handle.start);
|
|
90
|
+
emitMeasure(handle.track, handle.name, handle.start, end, handle.color, properties);
|
|
91
|
+
}
|
|
92
|
+
/** Record a duration measured externally (no measure emitted). */
|
|
93
|
+
export function recordPerfSample(track, ms) {
|
|
94
|
+
if (!PERF_OVERLAY_ENABLED)
|
|
95
|
+
return;
|
|
96
|
+
record(track, ms);
|
|
97
|
+
}
|
|
98
|
+
/** Hand the current window's buckets to the overlay and start a new window. */
|
|
99
|
+
export function drainPerfCounters() {
|
|
100
|
+
const out = new Map(counters);
|
|
101
|
+
counters.clear();
|
|
102
|
+
return out;
|
|
103
|
+
}
|
|
104
|
+
/** Drop accumulated timeline entries so long `?perf` sessions don't leak. */
|
|
105
|
+
export function clearPerfMeasures() {
|
|
106
|
+
try {
|
|
107
|
+
performance.clearMeasures();
|
|
108
|
+
performance.clearMarks();
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// ignore
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { events as createWebEvents, type EventManager, type RootStore } from '@react-three/fiber';
|
|
2
|
+
import * as THREE from 'three';
|
|
3
|
+
/** Opt in only when the raycast appends hits without reading prior hits or mutating scene/query state. */
|
|
4
|
+
export declare function markPureRaycast<T extends THREE.Object3D['raycast']>(raycast: T): T;
|
|
5
|
+
export declare function choosePointerEvents(search?: string): typeof createWebEvents;
|
|
6
|
+
export declare function createPascalPointerEvents(store: RootStore): EventManager<HTMLElement>;
|
|
7
|
+
//# sourceMappingURL=pointer-events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pointer-events.d.ts","sourceRoot":"","sources":["../../src/lib/pointer-events.ts"],"names":[],"mappings":"AA6BA,OAAO,EACL,MAAM,IAAI,eAAe,EAGzB,KAAK,YAAY,EAMjB,KAAK,SAAS,EAEf,MAAM,oBAAoB,CAAA;AAC3B,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAqC9B,0GAA0G;AAC1G,wBAAgB,eAAe,CAAC,CAAC,SAAS,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG,CAAC,CAGlF;AAYD,wBAAgB,mBAAmB,CACjC,MAAM,SAA8D,GACnE,OAAO,eAAe,CAIxB;AAiLD,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,SAAS,GAAG,YAAY,CAAC,WAAW,CAAC,CAmBrF"}
|