@forgeax/engine-rhi-debug 0.0.0-dev.8d955ade1c79
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 +202 -0
- package/README.md +178 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/__tests__/bootstrap-integration.test.d.ts +2 -0
- package/dist/__tests__/bootstrap-integration.test.d.ts.map +1 -0
- package/dist/__tests__/consumer-inventory.unit.test.d.ts +2 -0
- package/dist/__tests__/consumer-inventory.unit.test.d.ts.map +1 -0
- package/dist/__tests__/copy-src-usage-validity.dawn.test.d.ts +15 -0
- package/dist/__tests__/copy-src-usage-validity.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/coverage-invariant.test-d.d.ts +2 -0
- package/dist/__tests__/coverage-invariant.test-d.d.ts.map +1 -0
- package/dist/__tests__/coverage-invariant.unit.test.d.ts +2 -0
- package/dist/__tests__/coverage-invariant.unit.test.d.ts.map +1 -0
- package/dist/__tests__/e2e.browser.test.d.ts +2 -0
- package/dist/__tests__/e2e.browser.test.d.ts.map +1 -0
- package/dist/__tests__/error-code-narrowing.test-d.d.ts +2 -0
- package/dist/__tests__/error-code-narrowing.test-d.d.ts.map +1 -0
- package/dist/__tests__/errors-v7.test-d.d.ts +2 -0
- package/dist/__tests__/errors-v7.test-d.d.ts.map +1 -0
- package/dist/__tests__/errors-v7.unit.test.d.ts +2 -0
- package/dist/__tests__/errors-v7.unit.test.d.ts.map +1 -0
- package/dist/__tests__/errors.unit.test.d.ts +2 -0
- package/dist/__tests__/errors.unit.test.d.ts.map +1 -0
- package/dist/__tests__/event-semantics-coverage.unit.test.d.ts +2 -0
- package/dist/__tests__/event-semantics-coverage.unit.test.d.ts.map +1 -0
- package/dist/__tests__/format-info.unit.test.d.ts +2 -0
- package/dist/__tests__/format-info.unit.test.d.ts.map +1 -0
- package/dist/__tests__/frame-model-parity.test-d.d.ts +2 -0
- package/dist/__tests__/frame-model-parity.test-d.d.ts.map +1 -0
- package/dist/__tests__/frame-model-parity.unit.test.d.ts +2 -0
- package/dist/__tests__/frame-model-parity.unit.test.d.ts.map +1 -0
- package/dist/__tests__/guard-gates.test.d.ts +2 -0
- package/dist/__tests__/guard-gates.test.d.ts.map +1 -0
- package/dist/__tests__/public-surface.integration.test.d.ts +2 -0
- package/dist/__tests__/public-surface.integration.test.d.ts.map +1 -0
- package/dist/__tests__/readback-fault-cleanup.unit.test.d.ts +2 -0
- package/dist/__tests__/readback-fault-cleanup.unit.test.d.ts.map +1 -0
- package/dist/__tests__/readback-format-matrix.dawn.test.d.ts +2 -0
- package/dist/__tests__/readback-format-matrix.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/readback-format-matrix.unit.test.d.ts +2 -0
- package/dist/__tests__/readback-format-matrix.unit.test.d.ts.map +1 -0
- package/dist/__tests__/readback-matrix-fixture.d.ts +32 -0
- package/dist/__tests__/readback-matrix-fixture.d.ts.map +1 -0
- package/dist/__tests__/recorder-bootstrap.unit.test.d.ts +2 -0
- package/dist/__tests__/recorder-bootstrap.unit.test.d.ts.map +1 -0
- package/dist/__tests__/recorder-budget.unit.test.d.ts +2 -0
- package/dist/__tests__/recorder-budget.unit.test.d.ts.map +1 -0
- package/dist/__tests__/recorder-fail-fast.unit.test.d.ts +2 -0
- package/dist/__tests__/recorder-fail-fast.unit.test.d.ts.map +1 -0
- package/dist/__tests__/recorder-lifecycle.unit.test.d.ts +2 -0
- package/dist/__tests__/recorder-lifecycle.unit.test.d.ts.map +1 -0
- package/dist/__tests__/recorder-push-event.unit.test.d.ts +2 -0
- package/dist/__tests__/recorder-push-event.unit.test.d.ts.map +1 -0
- package/dist/__tests__/recorder-session.integration.test.d.ts +2 -0
- package/dist/__tests__/recorder-session.integration.test.d.ts.map +1 -0
- package/dist/__tests__/recorder-session.unit.test.d.ts +2 -0
- package/dist/__tests__/recorder-session.unit.test.d.ts.map +1 -0
- package/dist/__tests__/recorder-steady-frame.dawn.test.d.ts +2 -0
- package/dist/__tests__/recorder-steady-frame.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/replay-fail-fast.unit.test.d.ts +2 -0
- package/dist/__tests__/replay-fail-fast.unit.test.d.ts.map +1 -0
- package/dist/__tests__/replay-session.dawn.test.d.ts +2 -0
- package/dist/__tests__/replay-session.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/replay-session.test-d.d.ts +2 -0
- package/dist/__tests__/replay-session.test-d.d.ts.map +1 -0
- package/dist/__tests__/replay-session.unit.test.d.ts +2 -0
- package/dist/__tests__/replay-session.unit.test.d.ts.map +1 -0
- package/dist/__tests__/resolve-texture-descriptor.unit.test.d.ts +2 -0
- package/dist/__tests__/resolve-texture-descriptor.unit.test.d.ts.map +1 -0
- package/dist/__tests__/resource-lifecycle.unit.test.d.ts +2 -0
- package/dist/__tests__/resource-lifecycle.unit.test.d.ts.map +1 -0
- package/dist/__tests__/rhi-debug-fresh-replay.dawn.test.d.ts +2 -0
- package/dist/__tests__/rhi-debug-fresh-replay.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/tape-index.unit.test.d.ts +2 -0
- package/dist/__tests__/tape-index.unit.test.d.ts.map +1 -0
- package/dist/__tests__/tape-v7-invalid.unit.test.d.ts +2 -0
- package/dist/__tests__/tape-v7-invalid.unit.test.d.ts.map +1 -0
- package/dist/__tests__/tape-v7-roundtrip.unit.test.d.ts +2 -0
- package/dist/__tests__/tape-v7-roundtrip.unit.test.d.ts.map +1 -0
- package/dist/__tests__/tape-v7-version.unit.test.d.ts +2 -0
- package/dist/__tests__/tape-v7-version.unit.test.d.ts.map +1 -0
- package/dist/__tests__/tree-shake.unit.test.d.ts +2 -0
- package/dist/__tests__/tree-shake.unit.test.d.ts.map +1 -0
- package/dist/__tests__/types.test.d.ts +2 -0
- package/dist/__tests__/types.test.d.ts.map +1 -0
- package/dist/browser.d.ts +45 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.mjs +83 -0
- package/dist/browser.mjs.map +1 -0
- package/dist/errors.d.ts +85 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/frame-model.d.ts +158 -0
- package/dist/frame-model.d.ts.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +10415 -0
- package/dist/index.mjs.map +1 -0
- package/dist/protocol/codec.d.ts +6 -0
- package/dist/protocol/codec.d.ts.map +1 -0
- package/dist/protocol/event-semantics.d.ts +15 -0
- package/dist/protocol/event-semantics.d.ts.map +1 -0
- package/dist/protocol/tape-index.d.ts +30 -0
- package/dist/protocol/tape-index.d.ts.map +1 -0
- package/dist/protocol/types.d.ts +44 -0
- package/dist/protocol/types.d.ts.map +1 -0
- package/dist/protocol/validation.d.ts +7 -0
- package/dist/protocol/validation.d.ts.map +1 -0
- package/dist/readback.d.ts +156 -0
- package/dist/readback.d.ts.map +1 -0
- package/dist/recorder/assemble.d.ts +11 -0
- package/dist/recorder/assemble.d.ts.map +1 -0
- package/dist/recorder/closure.d.ts +39 -0
- package/dist/recorder/closure.d.ts.map +1 -0
- package/dist/recorder/core.d.ts +296 -0
- package/dist/recorder/core.d.ts.map +1 -0
- package/dist/recorder/device.d.ts +4 -0
- package/dist/recorder/device.d.ts.map +1 -0
- package/dist/recorder/encoder.d.ts +5 -0
- package/dist/recorder/encoder.d.ts.map +1 -0
- package/dist/recorder/lifecycle.d.ts +20 -0
- package/dist/recorder/lifecycle.d.ts.map +1 -0
- package/dist/recorder/pass.d.ts +6 -0
- package/dist/recorder/pass.d.ts.map +1 -0
- package/dist/recorder/proxy.d.ts +19 -0
- package/dist/recorder/proxy.d.ts.map +1 -0
- package/dist/recorder/queue.d.ts +4 -0
- package/dist/recorder/queue.d.ts.map +1 -0
- package/dist/recorder/resource-registry.d.ts +15 -0
- package/dist/recorder/resource-registry.d.ts.map +1 -0
- package/dist/recorder/session.d.ts +25 -0
- package/dist/recorder/session.d.ts.map +1 -0
- package/dist/recorder/shader.d.ts +3 -0
- package/dist/recorder/shader.d.ts.map +1 -0
- package/dist/recorder/snapshot.d.ts +10 -0
- package/dist/recorder/snapshot.d.ts.map +1 -0
- package/dist/recorder/wrap.d.ts +4 -0
- package/dist/recorder/wrap.d.ts.map +1 -0
- package/dist/recorder.d.ts +7 -0
- package/dist/recorder.d.ts.map +1 -0
- package/dist/replay/device-request.d.ts +8 -0
- package/dist/replay/device-request.d.ts.map +1 -0
- package/dist/replay/execute-support.d.ts +33 -0
- package/dist/replay/execute-support.d.ts.map +1 -0
- package/dist/replay/execute.d.ts +18 -0
- package/dist/replay/execute.d.ts.map +1 -0
- package/dist/replay/readback.d.ts +32 -0
- package/dist/replay/readback.d.ts.map +1 -0
- package/dist/replay/resources.d.ts +57 -0
- package/dist/replay/resources.d.ts.map +1 -0
- package/dist/replay/session.d.ts +49 -0
- package/dist/replay/session.d.ts.map +1 -0
- package/dist/replay/texture-format.d.ts +21 -0
- package/dist/replay/texture-format.d.ts.map +1 -0
- package/dist/texel-decode.d.ts +35 -0
- package/dist/texel-decode.d.ts.map +1 -0
- package/dist/texel-layout.d.ts +76 -0
- package/dist/texel-layout.d.ts.map +1 -0
- package/dist/types.d.ts +722 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +72 -0
- package/src/__tests__/bootstrap-integration.test.ts +268 -0
- package/src/__tests__/consumer-inventory.unit.test.ts +154 -0
- package/src/__tests__/copy-src-usage-validity.dawn.test.ts +117 -0
- package/src/__tests__/coverage-invariant.test-d.ts +115 -0
- package/src/__tests__/coverage-invariant.unit.test.ts +45 -0
- package/src/__tests__/e2e.browser.test.ts +239 -0
- package/src/__tests__/error-code-narrowing.test-d.ts +32 -0
- package/src/__tests__/errors-v7.test-d.ts +19 -0
- package/src/__tests__/errors-v7.unit.test.ts +15 -0
- package/src/__tests__/errors.unit.test.ts +83 -0
- package/src/__tests__/event-semantics-coverage.unit.test.ts +22 -0
- package/src/__tests__/format-info.unit.test.ts +85 -0
- package/src/__tests__/frame-model-parity.test-d.ts +20 -0
- package/src/__tests__/frame-model-parity.unit.test.ts +285 -0
- package/src/__tests__/guard-gates.test.ts +317 -0
- package/src/__tests__/public-surface.integration.test.ts +38 -0
- package/src/__tests__/readback-fault-cleanup.unit.test.ts +66 -0
- package/src/__tests__/readback-format-matrix.dawn.test.ts +378 -0
- package/src/__tests__/readback-format-matrix.unit.test.ts +120 -0
- package/src/__tests__/readback-matrix-fixture.ts +20 -0
- package/src/__tests__/recorder-bootstrap.unit.test.ts +302 -0
- package/src/__tests__/recorder-budget.unit.test.ts +42 -0
- package/src/__tests__/recorder-fail-fast.unit.test.ts +434 -0
- package/src/__tests__/recorder-lifecycle.unit.test.ts +42 -0
- package/src/__tests__/recorder-push-event.unit.test.ts +573 -0
- package/src/__tests__/recorder-session.integration.test.ts +32 -0
- package/src/__tests__/recorder-session.unit.test.ts +63 -0
- package/src/__tests__/recorder-steady-frame.dawn.test.ts +52 -0
- package/src/__tests__/replay-fail-fast.unit.test.ts +155 -0
- package/src/__tests__/replay-session.dawn.test.ts +220 -0
- package/src/__tests__/replay-session.test-d.ts +27 -0
- package/src/__tests__/replay-session.unit.test.ts +171 -0
- package/src/__tests__/resolve-texture-descriptor.unit.test.ts +144 -0
- package/src/__tests__/resource-lifecycle.unit.test.ts +114 -0
- package/src/__tests__/rhi-debug-fresh-replay.dawn.test.ts +178 -0
- package/src/__tests__/tape-index.unit.test.ts +56 -0
- package/src/__tests__/tape-v7-invalid.unit.test.ts +45 -0
- package/src/__tests__/tape-v7-roundtrip.unit.test.ts +66 -0
- package/src/__tests__/tape-v7-version.unit.test.ts +13 -0
- package/src/__tests__/tree-shake.unit.test.ts +151 -0
- package/src/__tests__/types.test.ts +257 -0
- package/src/browser.ts +146 -0
- package/src/errors.ts +173 -0
- package/src/frame-model.ts +753 -0
- package/src/index.ts +92 -0
- package/src/pako.d.ts +8 -0
- package/src/protocol/codec.ts +220 -0
- package/src/protocol/event-semantics.ts +180 -0
- package/src/protocol/tape-index.ts +120 -0
- package/src/protocol/types.ts +60 -0
- package/src/protocol/validation.ts +173 -0
- package/src/readback.ts +804 -0
- package/src/recorder/assemble.ts +192 -0
- package/src/recorder/closure.ts +408 -0
- package/src/recorder/core.ts +587 -0
- package/src/recorder/device.ts +474 -0
- package/src/recorder/encoder.ts +251 -0
- package/src/recorder/lifecycle.ts +847 -0
- package/src/recorder/pass.ts +241 -0
- package/src/recorder/proxy.ts +59 -0
- package/src/recorder/queue.ts +109 -0
- package/src/recorder/resource-registry.ts +53 -0
- package/src/recorder/session.ts +152 -0
- package/src/recorder/shader.ts +47 -0
- package/src/recorder/snapshot.ts +38 -0
- package/src/recorder/wrap.ts +157 -0
- package/src/recorder.ts +15 -0
- package/src/replay/device-request.ts +38 -0
- package/src/replay/execute-support.ts +243 -0
- package/src/replay/execute.ts +870 -0
- package/src/replay/readback.ts +671 -0
- package/src/replay/resources.ts +154 -0
- package/src/replay/session.ts +509 -0
- package/src/replay/texture-format.ts +99 -0
- package/src/texel-decode.ts +289 -0
- package/src/texel-layout.ts +324 -0
- package/src/types.ts +888 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { err, ok, type Result } from '@forgeax/engine-types';
|
|
2
|
+
import { sha256 } from '@noble/hashes/sha2.js';
|
|
3
|
+
import { bytesToHex } from '@noble/hashes/utils.js';
|
|
4
|
+
import { createRhiDebugError, type RhiDebugError } from '../errors';
|
|
5
|
+
import { encodeTape } from '../protocol/codec';
|
|
6
|
+
import type { BootstrapResource, Tape as V7Tape } from '../protocol/types';
|
|
7
|
+
import type { DebugRhiInstance } from '../recorder';
|
|
8
|
+
import type { HandleId, Tape as LegacyTape, RhiCallEvent } from '../types';
|
|
9
|
+
|
|
10
|
+
export interface EncodedTape {
|
|
11
|
+
readonly bytes: Uint8Array;
|
|
12
|
+
readonly digest: string;
|
|
13
|
+
readonly tape: V7Tape;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function assembleTape(recorder: DebugRhiInstance): Result<EncodedTape, RhiDebugError> {
|
|
17
|
+
const legacy = recorder.getTape();
|
|
18
|
+
if (legacy === undefined) {
|
|
19
|
+
return err(
|
|
20
|
+
createRhiDebugError('capture-snapshot-failed', {
|
|
21
|
+
stage: 'snapshot',
|
|
22
|
+
cause: 'the recorder finalized without a frame event',
|
|
23
|
+
}),
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
if ('code' in legacy) {
|
|
27
|
+
return err(legacy);
|
|
28
|
+
}
|
|
29
|
+
const tape = toV7Tape(legacy, new Set(recorder.bootstrapEvents()));
|
|
30
|
+
const encoded = encodeTape(tape);
|
|
31
|
+
if (!encoded.ok) return err(encoded.error);
|
|
32
|
+
return ok({
|
|
33
|
+
bytes: encoded.value,
|
|
34
|
+
digest: `sha256:${bytesToHex(sha256(encoded.value))}`,
|
|
35
|
+
tape,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function toV7Tape(legacy: LegacyTape, bootstrapEvents: ReadonlySet<RhiCallEvent>): V7Tape {
|
|
40
|
+
const firstFrame = legacy.events.findIndex((event) => event.kind === 'frameMark');
|
|
41
|
+
const boundary = firstFrame < 0 ? legacy.events.length : firstFrame;
|
|
42
|
+
const bootstrap: BootstrapResource[] = [];
|
|
43
|
+
const bootstrapIds = new Set<HandleId>();
|
|
44
|
+
const initialData = new Map<HandleId, string[]>();
|
|
45
|
+
for (const event of legacy.events.slice(0, boundary)) {
|
|
46
|
+
if (event.kind === 'initialData') {
|
|
47
|
+
const hashes = initialData.get(event.handleId) ?? [];
|
|
48
|
+
hashes.push(event.dataHash);
|
|
49
|
+
initialData.set(event.handleId, hashes);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (!bootstrapEvents.has(event)) continue;
|
|
53
|
+
const handleId = createdHandleId(event);
|
|
54
|
+
const kind = resourceKind(event);
|
|
55
|
+
if (handleId === undefined || kind === undefined || bootstrapIds.has(handleId)) continue;
|
|
56
|
+
bootstrapIds.add(handleId);
|
|
57
|
+
bootstrap.push({
|
|
58
|
+
handleId,
|
|
59
|
+
kind,
|
|
60
|
+
create: toJsonRecord(event),
|
|
61
|
+
initialData: [],
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
const allBlobs = Array.from(legacy.blobPool, ([hash, data]) => ({
|
|
65
|
+
hash,
|
|
66
|
+
bytes: new Uint8Array(data),
|
|
67
|
+
compression: 'none' as const,
|
|
68
|
+
}));
|
|
69
|
+
const referenced = collectReferencedHandleIds(legacy.events, bootstrapEvents);
|
|
70
|
+
const closure = collectBootstrapClosure(referenced, bootstrap);
|
|
71
|
+
const keptBootstrap = bootstrap
|
|
72
|
+
.filter((resource) => closure.has(resource.handleId))
|
|
73
|
+
.map((resource) => {
|
|
74
|
+
const hashes = initialData.get(resource.handleId) ?? [];
|
|
75
|
+
const slices = hashes.flatMap((hash) => {
|
|
76
|
+
const blob = legacy.blobPool.get(hash);
|
|
77
|
+
return blob === undefined ? [] : [{ hash, byteOffset: 0, byteLength: blob.byteLength }];
|
|
78
|
+
});
|
|
79
|
+
return { ...resource, initialData: slices };
|
|
80
|
+
});
|
|
81
|
+
const events = legacy.events
|
|
82
|
+
.filter((event) => event.kind !== 'initialData' && !bootstrapEvents.has(event))
|
|
83
|
+
.map(toJsonSafe);
|
|
84
|
+
const keptHashes = new Set(
|
|
85
|
+
keptBootstrap.flatMap((resource) => resource.initialData.map((slice) => slice.hash)),
|
|
86
|
+
);
|
|
87
|
+
for (const event of events) {
|
|
88
|
+
if ('dataHash' in event && typeof event.dataHash === 'string') keptHashes.add(event.dataHash);
|
|
89
|
+
}
|
|
90
|
+
const blobs = allBlobs.filter((blob) => keptHashes.has(blob.hash));
|
|
91
|
+
return {
|
|
92
|
+
header: {
|
|
93
|
+
formatVersion: 7,
|
|
94
|
+
rhiCaps: { ...legacy.rhiCapsRecorded },
|
|
95
|
+
eventCount: events.length,
|
|
96
|
+
blobCount: blobs.length,
|
|
97
|
+
},
|
|
98
|
+
bootstrap: keptBootstrap,
|
|
99
|
+
events,
|
|
100
|
+
blobs,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function collectReferencedHandleIds(
|
|
105
|
+
events: readonly RhiCallEvent[],
|
|
106
|
+
bootstrapEvents: ReadonlySet<RhiCallEvent>,
|
|
107
|
+
): Set<HandleId> {
|
|
108
|
+
const ids = new Set<HandleId>();
|
|
109
|
+
for (const event of events) {
|
|
110
|
+
if (event.kind === 'initialData' || bootstrapEvents.has(event)) continue;
|
|
111
|
+
collectHandleStrings(event, ids);
|
|
112
|
+
}
|
|
113
|
+
return ids;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function collectHandleStrings(value: unknown, ids: Set<HandleId>): void {
|
|
117
|
+
if (typeof value === 'string' && /^[a-zA-Z][a-zA-Z-]*:\S+$/.test(value)) {
|
|
118
|
+
ids.add(value);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (Array.isArray(value)) {
|
|
122
|
+
for (const entry of value) collectHandleStrings(entry, ids);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (value !== null && typeof value === 'object') {
|
|
126
|
+
for (const entry of Object.values(value)) collectHandleStrings(entry, ids);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function collectBootstrapClosure(
|
|
131
|
+
referenced: ReadonlySet<HandleId>,
|
|
132
|
+
resources: readonly BootstrapResource[],
|
|
133
|
+
): Set<HandleId> {
|
|
134
|
+
const byId = new Map(resources.map((resource) => [resource.handleId, resource]));
|
|
135
|
+
const closure = new Set<HandleId>();
|
|
136
|
+
const pending = [...referenced];
|
|
137
|
+
while (pending.length > 0) {
|
|
138
|
+
const handleId = pending.pop();
|
|
139
|
+
if (handleId === undefined || closure.has(handleId)) continue;
|
|
140
|
+
const resource = byId.get(handleId);
|
|
141
|
+
if (resource === undefined) continue;
|
|
142
|
+
closure.add(handleId);
|
|
143
|
+
const dependencies = new Set<HandleId>();
|
|
144
|
+
collectHandleStrings(resource.create, dependencies);
|
|
145
|
+
pending.push(...dependencies);
|
|
146
|
+
}
|
|
147
|
+
return closure;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function toJsonSafe(event: RhiCallEvent): RhiCallEvent {
|
|
151
|
+
return JSON.parse(JSON.stringify(event)) as RhiCallEvent;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function toJsonRecord(event: RhiCallEvent): Record<string, unknown> {
|
|
155
|
+
return JSON.parse(JSON.stringify(event));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function createdHandleId(event: RhiCallEvent): HandleId | undefined {
|
|
159
|
+
if (event.kind === 'createTextureView') return event.resultHandleId;
|
|
160
|
+
if (event.kind === 'createCommandEncoder') return event.cmdHandleId;
|
|
161
|
+
if (event.kind === 'beginRenderPass' || event.kind === 'beginComputePass') {
|
|
162
|
+
return event.passHandleId;
|
|
163
|
+
}
|
|
164
|
+
if (event.kind.startsWith('create') && 'handleId' in event) return event.handleId;
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function resourceKind(event: RhiCallEvent): BootstrapResource['kind'] | undefined {
|
|
169
|
+
switch (event.kind) {
|
|
170
|
+
case 'createBuffer':
|
|
171
|
+
return 'buffer';
|
|
172
|
+
case 'createTexture':
|
|
173
|
+
return 'texture';
|
|
174
|
+
case 'createTextureView':
|
|
175
|
+
return 'texture-view';
|
|
176
|
+
case 'createSampler':
|
|
177
|
+
return 'sampler';
|
|
178
|
+
case 'createShaderModule':
|
|
179
|
+
return 'shader-module';
|
|
180
|
+
case 'createRenderPipeline':
|
|
181
|
+
case 'createComputePipeline':
|
|
182
|
+
return 'pipeline';
|
|
183
|
+
case 'createBindGroup':
|
|
184
|
+
case 'createBindGroupLayout':
|
|
185
|
+
case 'createPipelineLayout':
|
|
186
|
+
return 'binding';
|
|
187
|
+
case 'createCommandEncoder':
|
|
188
|
+
return 'encoder';
|
|
189
|
+
default:
|
|
190
|
+
return undefined;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
// @forgeax/engine-rhi-debug/src/recorder/closure -- transitive handle closure owner.
|
|
2
|
+
|
|
3
|
+
import type { HandleId, RhiCallEvent } from '../types';
|
|
4
|
+
|
|
5
|
+
export function _collectFrameReferencedHandleIds(events: readonly RhiCallEvent[]): Set<HandleId> {
|
|
6
|
+
const refs = new Set<HandleId>();
|
|
7
|
+
for (const e of events) {
|
|
8
|
+
switch (e.kind) {
|
|
9
|
+
case 'writeBuffer':
|
|
10
|
+
case 'clearBuffer':
|
|
11
|
+
// initialData seeds a pre-arm resource's bytes; its handleId must be
|
|
12
|
+
// prefix-pulled so the resource's create* event lands in the bootstrap
|
|
13
|
+
// closure (otherwise the tape references a handle with no create event ->
|
|
14
|
+
// tape-invalid on deserialize).
|
|
15
|
+
case 'initialData': {
|
|
16
|
+
const we = e as { handleId: HandleId };
|
|
17
|
+
refs.add(we.handleId);
|
|
18
|
+
break;
|
|
19
|
+
}
|
|
20
|
+
case 'setVertexBuffer': {
|
|
21
|
+
const we = e as { passHandleId: HandleId; bufferHandleId: HandleId };
|
|
22
|
+
refs.add(we.passHandleId);
|
|
23
|
+
refs.add(we.bufferHandleId);
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
case 'setIndexBuffer': {
|
|
27
|
+
const we = e as { passHandleId: HandleId; bufferHandleId: HandleId };
|
|
28
|
+
refs.add(we.passHandleId);
|
|
29
|
+
refs.add(we.bufferHandleId);
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
case 'setPipeline': {
|
|
33
|
+
const we = e as { passHandleId: HandleId; pipelineHandleId: HandleId };
|
|
34
|
+
refs.add(we.passHandleId);
|
|
35
|
+
refs.add(we.pipelineHandleId);
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
case 'setComputePipeline': {
|
|
39
|
+
const we = e as { passHandleId: HandleId; pipelineHandleId: HandleId };
|
|
40
|
+
refs.add(we.passHandleId);
|
|
41
|
+
refs.add(we.pipelineHandleId);
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
case 'setBindGroup': {
|
|
45
|
+
const we = e as { passHandleId: HandleId; bindGroupHandleId: HandleId };
|
|
46
|
+
refs.add(we.passHandleId);
|
|
47
|
+
refs.add(we.bindGroupHandleId);
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
case 'draw': {
|
|
51
|
+
const we = e as { passHandleId: HandleId };
|
|
52
|
+
refs.add(we.passHandleId);
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
case 'drawIndexed': {
|
|
56
|
+
const we = e as { passHandleId: HandleId };
|
|
57
|
+
refs.add(we.passHandleId);
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
case 'setViewport': {
|
|
61
|
+
const we = e as { passHandleId: HandleId };
|
|
62
|
+
refs.add(we.passHandleId);
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
case 'setScissorRect': {
|
|
66
|
+
const we = e as { passHandleId: HandleId };
|
|
67
|
+
refs.add(we.passHandleId);
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
case 'setBlendConstant': {
|
|
71
|
+
const we = e as { passHandleId: HandleId };
|
|
72
|
+
refs.add(we.passHandleId);
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
case 'setStencilReference': {
|
|
76
|
+
const we = e as { passHandleId: HandleId };
|
|
77
|
+
refs.add(we.passHandleId);
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
case 'drawIndirect': {
|
|
81
|
+
const we = e as { passHandleId: HandleId; indirectBufferHandleId: HandleId };
|
|
82
|
+
refs.add(we.passHandleId);
|
|
83
|
+
refs.add(we.indirectBufferHandleId);
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
case 'drawIndexedIndirect': {
|
|
87
|
+
const we = e as { passHandleId: HandleId; indirectBufferHandleId: HandleId };
|
|
88
|
+
refs.add(we.passHandleId);
|
|
89
|
+
refs.add(we.indirectBufferHandleId);
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
case 'passPushDebugGroup': {
|
|
93
|
+
const we = e as { passHandleId: HandleId };
|
|
94
|
+
refs.add(we.passHandleId);
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
case 'passPopDebugGroup': {
|
|
98
|
+
const we = e as { passHandleId: HandleId };
|
|
99
|
+
refs.add(we.passHandleId);
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
case 'passInsertDebugMarker': {
|
|
103
|
+
const we = e as { passHandleId: HandleId };
|
|
104
|
+
refs.add(we.passHandleId);
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
case 'endRenderPass': {
|
|
108
|
+
const we = e as { passHandleId: HandleId };
|
|
109
|
+
refs.add(we.passHandleId);
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
case 'dispatchWorkgroups': {
|
|
113
|
+
const we = e as { passHandleId: HandleId };
|
|
114
|
+
refs.add(we.passHandleId);
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
case 'dispatchWorkgroupsIndirect': {
|
|
118
|
+
const we = e as { passHandleId: HandleId; indirectBufferHandleId: HandleId };
|
|
119
|
+
refs.add(we.passHandleId);
|
|
120
|
+
refs.add(we.indirectBufferHandleId);
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
case 'endComputePass': {
|
|
124
|
+
const we = e as { passHandleId: HandleId };
|
|
125
|
+
refs.add(we.passHandleId);
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
case 'submit': {
|
|
129
|
+
const we = e as { cmdHandleIds: readonly HandleId[] };
|
|
130
|
+
for (const id of we.cmdHandleIds) refs.add(id);
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
case 'beginRenderPass': {
|
|
134
|
+
const we = e as {
|
|
135
|
+
cmdHandleId: HandleId;
|
|
136
|
+
colorAttachmentViewHandleIds: readonly (HandleId | undefined)[];
|
|
137
|
+
depthStencilViewHandleId?: HandleId;
|
|
138
|
+
};
|
|
139
|
+
refs.add(we.cmdHandleId);
|
|
140
|
+
for (const vhId of we.colorAttachmentViewHandleIds) {
|
|
141
|
+
if (vhId !== undefined) refs.add(vhId);
|
|
142
|
+
}
|
|
143
|
+
if (we.depthStencilViewHandleId !== undefined) refs.add(we.depthStencilViewHandleId);
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
case 'beginComputePass': {
|
|
147
|
+
const we = e as { cmdHandleId: HandleId };
|
|
148
|
+
refs.add(we.cmdHandleId);
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
case 'finish': {
|
|
152
|
+
const we = e as { cmdHandleId: HandleId };
|
|
153
|
+
refs.add(we.cmdHandleId);
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
case 'pushDebugGroup':
|
|
157
|
+
case 'popDebugGroup':
|
|
158
|
+
case 'insertDebugMarker': {
|
|
159
|
+
const we = e as { cmdHandleId: HandleId };
|
|
160
|
+
refs.add(we.cmdHandleId);
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
case 'writeTexture': {
|
|
164
|
+
const we = e as { destination: { textureHandleId: HandleId } };
|
|
165
|
+
refs.add(we.destination.textureHandleId);
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
case 'copyExternalImageToTexture': {
|
|
169
|
+
const we = e as { destination: { textureHandleId: HandleId } };
|
|
170
|
+
refs.add(we.destination.textureHandleId);
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
case 'copyBufferToBuffer': {
|
|
174
|
+
const we = e as { sourceHandleId: HandleId; destinationHandleId: HandleId };
|
|
175
|
+
refs.add(we.sourceHandleId);
|
|
176
|
+
refs.add(we.destinationHandleId);
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
case 'copyBufferToTexture': {
|
|
180
|
+
const we = e as {
|
|
181
|
+
source: { bufferHandleId: HandleId };
|
|
182
|
+
destination: { textureHandleId: HandleId };
|
|
183
|
+
};
|
|
184
|
+
refs.add(we.source.bufferHandleId);
|
|
185
|
+
refs.add(we.destination.textureHandleId);
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
case 'copyTextureToBuffer': {
|
|
189
|
+
const we = e as {
|
|
190
|
+
source: { textureHandleId: HandleId };
|
|
191
|
+
destination: { bufferHandleId: HandleId };
|
|
192
|
+
};
|
|
193
|
+
refs.add(we.source.textureHandleId);
|
|
194
|
+
refs.add(we.destination.bufferHandleId);
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
case 'copyTextureToTexture': {
|
|
198
|
+
const we = e as {
|
|
199
|
+
source: { textureHandleId: HandleId };
|
|
200
|
+
destination: { textureHandleId: HandleId };
|
|
201
|
+
};
|
|
202
|
+
refs.add(we.source.textureHandleId);
|
|
203
|
+
refs.add(we.destination.textureHandleId);
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
case 'createBindGroup':
|
|
207
|
+
case 'createPipelineLayout':
|
|
208
|
+
case 'createRenderPipeline':
|
|
209
|
+
case 'createComputePipeline':
|
|
210
|
+
case 'createTextureView': {
|
|
211
|
+
// An in-frame-created resource may reference a PRE-ARM resource via its
|
|
212
|
+
// backward edges (e.g. a composite/FXAA bind group built mid-frame that
|
|
213
|
+
// samples a scratch TextureView created at setup; or an in-frame
|
|
214
|
+
// createTextureView of a pre-arm texture). Those pre-arm handles are
|
|
215
|
+
// reachable ONLY through this create* event's backward refs — no usage
|
|
216
|
+
// event names them directly — so without collecting them here they never
|
|
217
|
+
// become prefix seeds and the tape deserializes as non-self-contained
|
|
218
|
+
// (tape-invalid). Collect the edges; getTape's prefixSeedIds
|
|
219
|
+
// filter then drops entries that are themselves in-frame declared, leaving
|
|
220
|
+
// only the genuinely pre-arm dependencies to seed the bootstrap closure.
|
|
221
|
+
for (const ref of _getCreateEventReferencedHandleIds(e)) refs.add(ref);
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
case 'destroyBuffer':
|
|
225
|
+
case 'destroyTexture':
|
|
226
|
+
refs.add(e.handleId);
|
|
227
|
+
break;
|
|
228
|
+
case 'frameMark':
|
|
229
|
+
case 'createBuffer':
|
|
230
|
+
case 'createTexture':
|
|
231
|
+
case 'createSampler':
|
|
232
|
+
case 'createBindGroupLayout':
|
|
233
|
+
case 'createShaderModule':
|
|
234
|
+
case 'createCommandEncoder':
|
|
235
|
+
// Leaf declaration events — no backward references to collect.
|
|
236
|
+
break;
|
|
237
|
+
default: {
|
|
238
|
+
// Exhaustiveness guard: if a new RhiCallEvent member is added to the
|
|
239
|
+
// union without a corresponding handle-collection case, tsc fails here.
|
|
240
|
+
// This prevents silent omission of handle references (tape-invalid).
|
|
241
|
+
const _exhaustive: never = e;
|
|
242
|
+
void _exhaustive;
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return refs;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* @internal
|
|
252
|
+
* Return handleIds referenced by a create* event for transitive closure traversal.
|
|
253
|
+
*
|
|
254
|
+
* The edge set follows D-3 (plan-strategy 2):
|
|
255
|
+
* - createBindGroup → layoutHandleId + resourceHandleIds
|
|
256
|
+
* - createPipelineLayout → bglHandleIds
|
|
257
|
+
* - createRenderPipeline → layoutHandleId (if != 'layout:auto') + vertex/fragmentShaderModuleHandleId (R-1)
|
|
258
|
+
* - createComputePipeline → layoutHandleId (if != 'layout:auto') + computeShaderModuleHandleId (R-1)
|
|
259
|
+
* - createTextureView → sourceHandleId
|
|
260
|
+
* Leaf resources (buffer / texture / sampler / BGL / shaderModule) return empty.
|
|
261
|
+
*/
|
|
262
|
+
export function _getCreateEventReferencedHandleIds(event: RhiCallEvent): HandleId[] {
|
|
263
|
+
switch (event.kind) {
|
|
264
|
+
case 'createBindGroup': {
|
|
265
|
+
const e = event as { layoutHandleId: HandleId; resourceHandleIds: readonly HandleId[] };
|
|
266
|
+
return [e.layoutHandleId, ...e.resourceHandleIds];
|
|
267
|
+
}
|
|
268
|
+
case 'createPipelineLayout': {
|
|
269
|
+
const e = event as { bglHandleIds: readonly HandleId[] };
|
|
270
|
+
return [...e.bglHandleIds];
|
|
271
|
+
}
|
|
272
|
+
case 'createRenderPipeline': {
|
|
273
|
+
const e = event as {
|
|
274
|
+
layoutHandleId: HandleId;
|
|
275
|
+
vertexShaderModuleHandleId?: HandleId;
|
|
276
|
+
fragmentShaderModuleHandleId?: HandleId;
|
|
277
|
+
};
|
|
278
|
+
const refs: HandleId[] = [];
|
|
279
|
+
if (e.layoutHandleId !== 'layout:auto') refs.push(e.layoutHandleId);
|
|
280
|
+
if (e.vertexShaderModuleHandleId !== undefined) refs.push(e.vertexShaderModuleHandleId);
|
|
281
|
+
if (e.fragmentShaderModuleHandleId !== undefined) refs.push(e.fragmentShaderModuleHandleId);
|
|
282
|
+
return refs;
|
|
283
|
+
}
|
|
284
|
+
case 'createComputePipeline': {
|
|
285
|
+
const e = event as { layoutHandleId: HandleId; computeShaderModuleHandleId?: HandleId };
|
|
286
|
+
const refs: HandleId[] = [];
|
|
287
|
+
if (e.layoutHandleId !== 'layout:auto') refs.push(e.layoutHandleId);
|
|
288
|
+
if (e.computeShaderModuleHandleId !== undefined) refs.push(e.computeShaderModuleHandleId);
|
|
289
|
+
return refs;
|
|
290
|
+
}
|
|
291
|
+
case 'createTextureView': {
|
|
292
|
+
const e = event as { sourceHandleId: HandleId };
|
|
293
|
+
return [e.sourceHandleId];
|
|
294
|
+
}
|
|
295
|
+
case 'createBuffer':
|
|
296
|
+
case 'createTexture':
|
|
297
|
+
case 'createSampler':
|
|
298
|
+
case 'createBindGroupLayout':
|
|
299
|
+
case 'createShaderModule':
|
|
300
|
+
case 'createCommandEncoder':
|
|
301
|
+
return [];
|
|
302
|
+
default:
|
|
303
|
+
return [];
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* @internal
|
|
309
|
+
* Compute the transitive closure of handleIds from bootstrapCreates.
|
|
310
|
+
*
|
|
311
|
+
* Starting from the given seed set, recursively walks all referenced handleIds
|
|
312
|
+
* via _getCreateEventReferencedHandleIds. Returns the set of all handleIds
|
|
313
|
+
* whose create events must be included in the tape prefix for self-containment.
|
|
314
|
+
*
|
|
315
|
+
* If a referenced handleId is not found in bootstrapCreates, returns
|
|
316
|
+
* `null` for that id — the caller should produce a structured tape error.
|
|
317
|
+
*/
|
|
318
|
+
export function _computeClosure(
|
|
319
|
+
seedHandleIds: Set<HandleId>,
|
|
320
|
+
bootstrapCreates: Map<HandleId, RhiCallEvent>,
|
|
321
|
+
inFrameHandleIds: Set<HandleId>,
|
|
322
|
+
): { closure: Set<HandleId>; missing: HandleId | null } {
|
|
323
|
+
const closure = new Set(seedHandleIds);
|
|
324
|
+
const queue = [...seedHandleIds];
|
|
325
|
+
|
|
326
|
+
while (queue.length > 0) {
|
|
327
|
+
const current: HandleId | undefined = queue.shift();
|
|
328
|
+
if (current === undefined) break;
|
|
329
|
+
const createEvent = bootstrapCreates.get(current);
|
|
330
|
+
if (createEvent === undefined) {
|
|
331
|
+
// The handle is not in bootstrapCreates. If it is declared in
|
|
332
|
+
// s.events (e.g. swapchain textures from getCurrentTexture), treat it
|
|
333
|
+
// as a leaf — no further expansion needed.
|
|
334
|
+
if (inFrameHandleIds.has(current)) continue;
|
|
335
|
+
return { closure, missing: current };
|
|
336
|
+
}
|
|
337
|
+
const edges = _getCreateEventReferencedHandleIds(createEvent);
|
|
338
|
+
for (const target of edges) {
|
|
339
|
+
if (!closure.has(target)) {
|
|
340
|
+
closure.add(target);
|
|
341
|
+
queue.push(target);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return { closure, missing: null };
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* @internal
|
|
350
|
+
* Topologically sort the closure set so that dependencies appear before dependents.
|
|
351
|
+
*
|
|
352
|
+
* Builds a dep-graph: if event A references handleId of event B, then B must
|
|
353
|
+
* appear before A. Uses Kahn's algorithm.
|
|
354
|
+
*/
|
|
355
|
+
export function _topoSortClosure(
|
|
356
|
+
closure: Set<HandleId>,
|
|
357
|
+
bootstrapCreates: Map<HandleId, RhiCallEvent>,
|
|
358
|
+
): RhiCallEvent[] {
|
|
359
|
+
const inDegree = new Map<HandleId, number>();
|
|
360
|
+
const dependents = new Map<HandleId, HandleId[]>();
|
|
361
|
+
|
|
362
|
+
for (const hId of closure) {
|
|
363
|
+
inDegree.set(hId, 0);
|
|
364
|
+
dependents.set(hId, []);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
for (const hId of closure) {
|
|
368
|
+
const event = bootstrapCreates.get(hId);
|
|
369
|
+
if (event === undefined) continue;
|
|
370
|
+
const edges = _getCreateEventReferencedHandleIds(event);
|
|
371
|
+
for (const target of edges) {
|
|
372
|
+
if (closure.has(target)) {
|
|
373
|
+
// hId depends on target
|
|
374
|
+
const current = dependents.get(target);
|
|
375
|
+
if (current !== undefined) current.push(hId);
|
|
376
|
+
inDegree.set(hId, (inDegree.get(hId) ?? 0) + 1);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const queue: HandleId[] = [];
|
|
382
|
+
for (const [hId, deg] of inDegree) {
|
|
383
|
+
if (deg === 0) queue.push(hId);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const sorted: RhiCallEvent[] = [];
|
|
387
|
+
while (queue.length > 0) {
|
|
388
|
+
const current: HandleId | undefined = queue.shift();
|
|
389
|
+
if (current === undefined) break;
|
|
390
|
+
const event = bootstrapCreates.get(current);
|
|
391
|
+
if (event !== undefined) sorted.push(event);
|
|
392
|
+
|
|
393
|
+
const deps = dependents.get(current);
|
|
394
|
+
if (deps !== undefined) {
|
|
395
|
+
for (const dep of deps) {
|
|
396
|
+
const newDeg = (inDegree.get(dep) ?? 1) - 1;
|
|
397
|
+
inDegree.set(dep, newDeg);
|
|
398
|
+
if (newDeg === 0) queue.push(dep);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return sorted;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// ============================================================================
|
|
407
|
+
// DebugRhiInstance — public interface
|
|
408
|
+
// ============================================================================
|