@rezi-ui/node 0.1.0-alpha.45 → 0.1.0-alpha.48
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/backend/nodeBackend.d.ts +0 -7
- package/dist/backend/nodeBackend.d.ts.map +1 -1
- package/dist/backend/nodeBackend.js +289 -29
- package/dist/backend/nodeBackend.js.map +1 -1
- package/dist/backend/nodeBackendInline.d.ts.map +1 -1
- package/dist/backend/nodeBackendInline.js +68 -15
- package/dist/backend/nodeBackendInline.js.map +1 -1
- package/dist/frameAudit.d.ts +51 -0
- package/dist/frameAudit.d.ts.map +1 -0
- package/dist/frameAudit.js +257 -0
- package/dist/frameAudit.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -7
- package/dist/index.js.map +1 -1
- package/dist/worker/engineWorker.js +446 -1
- package/dist/worker/engineWorker.js.map +1 -1
- package/package.json +3 -3
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { performance } from "node:perf_hooks";
|
|
9
9
|
import { parentPort, workerData } from "node:worker_threads";
|
|
10
|
+
import { FRAME_AUDIT_NATIVE_ENABLED, FRAME_AUDIT_NATIVE_RING_BYTES, ZR_DEBUG_CAT_DRAWLIST, ZR_DEBUG_CAT_FRAME, ZR_DEBUG_CAT_PERF, ZR_DEBUG_CODE_DRAWLIST_CMD, ZR_DEBUG_CODE_DRAWLIST_EXECUTE, ZR_DEBUG_CODE_DRAWLIST_VALIDATE, ZR_DEBUG_CODE_FRAME_BEGIN, ZR_DEBUG_CODE_FRAME_PRESENT, ZR_DEBUG_CODE_FRAME_RESIZE, ZR_DEBUG_CODE_FRAME_SUBMIT, ZR_DEBUG_CODE_PERF_DIFF_PATH, ZR_DEBUG_CODE_PERF_TIMING, createFrameAuditLogger, drawlistFingerprint, } from "../frameAudit.js";
|
|
10
11
|
import { EVENT_POOL_SIZE, FRAME_SAB_CONTROL_CONSUMED_SEQ_WORD, FRAME_SAB_CONTROL_HEADER_WORDS, FRAME_SAB_CONTROL_PUBLISHED_BYTES_WORD, FRAME_SAB_CONTROL_PUBLISHED_SEQ_WORD, FRAME_SAB_CONTROL_PUBLISHED_SLOT_WORD, FRAME_SAB_CONTROL_PUBLISHED_TOKEN_WORD, FRAME_SAB_CONTROL_WORDS_PER_SLOT, FRAME_SAB_SLOT_STATE_FREE, FRAME_SAB_SLOT_STATE_IN_USE, FRAME_SAB_SLOT_STATE_READY, FRAME_TRANSPORT_SAB_V1, FRAME_TRANSPORT_TRANSFER_V1, FRAME_TRANSPORT_VERSION, MAX_POLL_DRAIN_ITERS, } from "./protocol.js";
|
|
11
12
|
import { computeNextIdleDelay, computeTickTiming } from "./tickTiming.js";
|
|
12
13
|
/**
|
|
@@ -154,12 +155,42 @@ const DEBUG_HEADER_BYTES = 40;
|
|
|
154
155
|
const DEBUG_QUERY_MIN_HEADERS_CAP = DEBUG_HEADER_BYTES;
|
|
155
156
|
const DEBUG_QUERY_MAX_HEADERS_CAP = 1 << 20; // 1 MiB
|
|
156
157
|
const NO_RECYCLED_DRAWLISTS = Object.freeze([]);
|
|
158
|
+
const DEBUG_DRAWLIST_RECORD_BYTES = 48;
|
|
159
|
+
const DEBUG_FRAME_RECORD_BYTES = 56;
|
|
160
|
+
const DEBUG_PERF_RECORD_BYTES = 24;
|
|
161
|
+
// Must cover sizeof(zr_diff_telemetry_record_t) (includes native trailing pad).
|
|
162
|
+
const DEBUG_DIFF_PATH_RECORD_BYTES = 64;
|
|
163
|
+
const NATIVE_FRAME_AUDIT_CATEGORY_MASK = (1 << ZR_DEBUG_CAT_DRAWLIST) | (1 << ZR_DEBUG_CAT_FRAME) | (1 << ZR_DEBUG_CAT_PERF);
|
|
164
|
+
function nativeFrameCodeName(code) {
|
|
165
|
+
if (code === ZR_DEBUG_CODE_FRAME_BEGIN)
|
|
166
|
+
return "frame.begin";
|
|
167
|
+
if (code === ZR_DEBUG_CODE_FRAME_SUBMIT)
|
|
168
|
+
return "frame.submit";
|
|
169
|
+
if (code === ZR_DEBUG_CODE_FRAME_PRESENT)
|
|
170
|
+
return "frame.present";
|
|
171
|
+
if (code === ZR_DEBUG_CODE_FRAME_RESIZE)
|
|
172
|
+
return "frame.resize";
|
|
173
|
+
return "frame.unknown";
|
|
174
|
+
}
|
|
175
|
+
function nativePerfPhaseName(phase) {
|
|
176
|
+
if (phase === 0)
|
|
177
|
+
return "poll";
|
|
178
|
+
if (phase === 1)
|
|
179
|
+
return "submit";
|
|
180
|
+
if (phase === 2)
|
|
181
|
+
return "present";
|
|
182
|
+
return "unknown";
|
|
183
|
+
}
|
|
157
184
|
let engineId = null;
|
|
158
185
|
let running = false;
|
|
159
186
|
let haveSubmittedDrawlist = false;
|
|
160
187
|
let pendingFrame = null;
|
|
161
188
|
let lastConsumedSabPublishedSeq = 0;
|
|
162
189
|
let frameTransport = Object.freeze({ kind: FRAME_TRANSPORT_TRANSFER_V1 });
|
|
190
|
+
const frameAudit = createFrameAuditLogger("worker");
|
|
191
|
+
const frameAuditBySeq = new Map();
|
|
192
|
+
let nativeFrameAuditEnabled = false;
|
|
193
|
+
let nativeFrameAuditNextRecordId = 1n;
|
|
163
194
|
let eventPool = [];
|
|
164
195
|
let discardBuffer = null;
|
|
165
196
|
let droppedSinceLast = 0;
|
|
@@ -170,6 +201,284 @@ let idleDelayMs = 0;
|
|
|
170
201
|
let maxIdleDelayMs = 0;
|
|
171
202
|
let sabWakeArmed = false;
|
|
172
203
|
let sabWakeEpoch = 0;
|
|
204
|
+
function u64FromView(v, offset) {
|
|
205
|
+
const lo = BigInt(v.getUint32(offset, true));
|
|
206
|
+
const hi = BigInt(v.getUint32(offset + 4, true));
|
|
207
|
+
return (hi << 32n) | lo;
|
|
208
|
+
}
|
|
209
|
+
function setFrameAuditMeta(frameSeq, patch) {
|
|
210
|
+
if (!frameAudit.enabled)
|
|
211
|
+
return;
|
|
212
|
+
const prev = frameAuditBySeq.get(frameSeq);
|
|
213
|
+
const next = {
|
|
214
|
+
frameSeq,
|
|
215
|
+
enqueuedAtMs: prev?.enqueuedAtMs ?? Date.now(),
|
|
216
|
+
transport: prev?.transport ?? FRAME_TRANSPORT_TRANSFER_V1,
|
|
217
|
+
byteLen: prev?.byteLen ?? 0,
|
|
218
|
+
...(prev ?? {}),
|
|
219
|
+
...patch,
|
|
220
|
+
};
|
|
221
|
+
frameAuditBySeq.set(frameSeq, next);
|
|
222
|
+
}
|
|
223
|
+
function emitFrameAudit(stage, frameSeq, fields = {}) {
|
|
224
|
+
if (!frameAudit.enabled)
|
|
225
|
+
return;
|
|
226
|
+
const meta = frameAuditBySeq.get(frameSeq);
|
|
227
|
+
frameAudit.emit(stage, {
|
|
228
|
+
frameSeq,
|
|
229
|
+
ageMs: meta ? Math.max(0, Date.now() - meta.enqueuedAtMs) : null,
|
|
230
|
+
...(meta ?? {}),
|
|
231
|
+
...fields,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
function deleteFrameAudit(frameSeq) {
|
|
235
|
+
if (!frameAudit.enabled)
|
|
236
|
+
return;
|
|
237
|
+
frameAuditBySeq.delete(frameSeq);
|
|
238
|
+
}
|
|
239
|
+
function maybeEnableNativeFrameAudit() {
|
|
240
|
+
if (!frameAudit.enabled)
|
|
241
|
+
return;
|
|
242
|
+
if (!FRAME_AUDIT_NATIVE_ENABLED)
|
|
243
|
+
return;
|
|
244
|
+
if (engineId === null)
|
|
245
|
+
return;
|
|
246
|
+
let rc = -1;
|
|
247
|
+
try {
|
|
248
|
+
rc = native.engineDebugEnable(engineId, {
|
|
249
|
+
enabled: true,
|
|
250
|
+
ringCapacity: FRAME_AUDIT_NATIVE_RING_BYTES,
|
|
251
|
+
minSeverity: 0,
|
|
252
|
+
categoryMask: NATIVE_FRAME_AUDIT_CATEGORY_MASK,
|
|
253
|
+
captureRawEvents: false,
|
|
254
|
+
captureDrawlistBytes: true,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
catch (err) {
|
|
258
|
+
frameAudit.emit("native.debug.enable_error", { detail: safeDetail(err) });
|
|
259
|
+
nativeFrameAuditEnabled = false;
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
nativeFrameAuditEnabled = rc >= 0;
|
|
263
|
+
nativeFrameAuditNextRecordId = 1n;
|
|
264
|
+
frameAudit.emit("native.debug.enable", {
|
|
265
|
+
rc,
|
|
266
|
+
enabled: nativeFrameAuditEnabled,
|
|
267
|
+
ringCapacity: FRAME_AUDIT_NATIVE_RING_BYTES,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
function drainNativeFrameAudit(reason) {
|
|
271
|
+
if (!frameAudit.enabled || !nativeFrameAuditEnabled)
|
|
272
|
+
return;
|
|
273
|
+
if (engineId === null)
|
|
274
|
+
return;
|
|
275
|
+
const headersCap = DEBUG_HEADER_BYTES * 64;
|
|
276
|
+
const headersBuf = new Uint8Array(headersCap);
|
|
277
|
+
const tryReadDebugPayload = (recordId, code, expectedBytes, opts = {}) => {
|
|
278
|
+
if (engineId === null)
|
|
279
|
+
return null;
|
|
280
|
+
const payload = new Uint8Array(expectedBytes);
|
|
281
|
+
let wrote = 0;
|
|
282
|
+
try {
|
|
283
|
+
wrote = native.engineDebugGetPayload(engineId, recordId, payload);
|
|
284
|
+
}
|
|
285
|
+
catch (err) {
|
|
286
|
+
frameAudit.emit("native.debug.payload_error", {
|
|
287
|
+
reason,
|
|
288
|
+
recordId: recordId.toString(),
|
|
289
|
+
code,
|
|
290
|
+
expectedBytes,
|
|
291
|
+
detail: safeDetail(err),
|
|
292
|
+
});
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
if (wrote <= 0) {
|
|
296
|
+
frameAudit.emit("native.debug.payload_error", {
|
|
297
|
+
reason,
|
|
298
|
+
recordId: recordId.toString(),
|
|
299
|
+
code,
|
|
300
|
+
expectedBytes,
|
|
301
|
+
wrote,
|
|
302
|
+
detail: "payload read returned non-positive length",
|
|
303
|
+
});
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
if (wrote < expectedBytes) {
|
|
307
|
+
frameAudit.emit("native.debug.payload_error", {
|
|
308
|
+
reason,
|
|
309
|
+
recordId: recordId.toString(),
|
|
310
|
+
code,
|
|
311
|
+
expectedBytes,
|
|
312
|
+
wrote,
|
|
313
|
+
detail: "short payload read",
|
|
314
|
+
});
|
|
315
|
+
if (opts.allowPartialOnShortRead !== true) {
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return payload.subarray(0, Math.min(wrote, payload.byteLength));
|
|
320
|
+
};
|
|
321
|
+
for (let iter = 0; iter < 8; iter++) {
|
|
322
|
+
let result;
|
|
323
|
+
try {
|
|
324
|
+
result = native.engineDebugQuery(engineId, {
|
|
325
|
+
minRecordId: nativeFrameAuditNextRecordId,
|
|
326
|
+
categoryMask: NATIVE_FRAME_AUDIT_CATEGORY_MASK,
|
|
327
|
+
minSeverity: 0,
|
|
328
|
+
maxRecords: Math.floor(headersCap / DEBUG_HEADER_BYTES),
|
|
329
|
+
}, headersBuf);
|
|
330
|
+
}
|
|
331
|
+
catch (err) {
|
|
332
|
+
frameAudit.emit("native.debug.query_error", { reason, detail: safeDetail(err) });
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
const recordsReturned = Number.isInteger(result.recordsReturned) && result.recordsReturned > 0
|
|
336
|
+
? Math.min(result.recordsReturned, Math.floor(headersCap / DEBUG_HEADER_BYTES))
|
|
337
|
+
: 0;
|
|
338
|
+
if (recordsReturned <= 0)
|
|
339
|
+
return;
|
|
340
|
+
let advanced = false;
|
|
341
|
+
for (let i = 0; i < recordsReturned; i++) {
|
|
342
|
+
const off = i * DEBUG_HEADER_BYTES;
|
|
343
|
+
const dv = new DataView(headersBuf.buffer, headersBuf.byteOffset + off, DEBUG_HEADER_BYTES);
|
|
344
|
+
const recordId = u64FromView(dv, 0);
|
|
345
|
+
const timestampUs = u64FromView(dv, 8);
|
|
346
|
+
const frameId = u64FromView(dv, 16);
|
|
347
|
+
const category = dv.getUint32(24, true);
|
|
348
|
+
const severity = dv.getUint32(28, true);
|
|
349
|
+
const code = dv.getUint32(32, true);
|
|
350
|
+
const payloadSize = dv.getUint32(36, true);
|
|
351
|
+
if (recordId < nativeFrameAuditNextRecordId) {
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
if (recordId === 0n && frameId === 0n && category === 0 && code === 0 && payloadSize === 0) {
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
advanced = true;
|
|
358
|
+
nativeFrameAuditNextRecordId = recordId + 1n;
|
|
359
|
+
frameAudit.emit("native.debug.header", {
|
|
360
|
+
reason,
|
|
361
|
+
recordId: recordId.toString(),
|
|
362
|
+
frameId: frameId.toString(),
|
|
363
|
+
timestampUs: timestampUs.toString(),
|
|
364
|
+
category,
|
|
365
|
+
severity,
|
|
366
|
+
code,
|
|
367
|
+
payloadSize,
|
|
368
|
+
});
|
|
369
|
+
if (code === ZR_DEBUG_CODE_DRAWLIST_CMD && payloadSize > 0) {
|
|
370
|
+
const cap = Math.min(Math.max(payloadSize, 1), 4096);
|
|
371
|
+
const view = tryReadDebugPayload(recordId, code, cap, {
|
|
372
|
+
allowPartialOnShortRead: true,
|
|
373
|
+
});
|
|
374
|
+
if (view === null)
|
|
375
|
+
continue;
|
|
376
|
+
const fp = drawlistFingerprint(view);
|
|
377
|
+
frameAudit.emit("native.drawlist.payload", {
|
|
378
|
+
reason,
|
|
379
|
+
recordId: recordId.toString(),
|
|
380
|
+
frameId: frameId.toString(),
|
|
381
|
+
payloadSize: view.byteLength,
|
|
382
|
+
hash32: fp.hash32,
|
|
383
|
+
prefixHash32: fp.prefixHash32,
|
|
384
|
+
head16: fp.head16,
|
|
385
|
+
tail16: fp.tail16,
|
|
386
|
+
});
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
if ((code === ZR_DEBUG_CODE_DRAWLIST_VALIDATE || code === ZR_DEBUG_CODE_DRAWLIST_EXECUTE) &&
|
|
390
|
+
payloadSize >= DEBUG_DRAWLIST_RECORD_BYTES) {
|
|
391
|
+
const payload = tryReadDebugPayload(recordId, code, DEBUG_DRAWLIST_RECORD_BYTES);
|
|
392
|
+
if (payload === null)
|
|
393
|
+
continue;
|
|
394
|
+
const dvPayload = new DataView(payload.buffer, payload.byteOffset, DEBUG_DRAWLIST_RECORD_BYTES);
|
|
395
|
+
frameAudit.emit("native.drawlist.summary", {
|
|
396
|
+
reason,
|
|
397
|
+
recordId: recordId.toString(),
|
|
398
|
+
frameId: u64FromView(dvPayload, 0).toString(),
|
|
399
|
+
totalBytes: dvPayload.getUint32(8, true),
|
|
400
|
+
cmdCount: dvPayload.getUint32(12, true),
|
|
401
|
+
version: dvPayload.getUint32(16, true),
|
|
402
|
+
validationResult: dvPayload.getInt32(20, true),
|
|
403
|
+
executionResult: dvPayload.getInt32(24, true),
|
|
404
|
+
clipStackMaxDepth: dvPayload.getUint32(28, true),
|
|
405
|
+
textRuns: dvPayload.getUint32(32, true),
|
|
406
|
+
fillRects: dvPayload.getUint32(36, true),
|
|
407
|
+
});
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
if ((code === ZR_DEBUG_CODE_FRAME_BEGIN ||
|
|
411
|
+
code === ZR_DEBUG_CODE_FRAME_SUBMIT ||
|
|
412
|
+
code === ZR_DEBUG_CODE_FRAME_PRESENT ||
|
|
413
|
+
code === ZR_DEBUG_CODE_FRAME_RESIZE) &&
|
|
414
|
+
payloadSize >= DEBUG_FRAME_RECORD_BYTES) {
|
|
415
|
+
const payload = tryReadDebugPayload(recordId, code, DEBUG_FRAME_RECORD_BYTES);
|
|
416
|
+
if (payload === null)
|
|
417
|
+
continue;
|
|
418
|
+
const dvPayload = new DataView(payload.buffer, payload.byteOffset, DEBUG_FRAME_RECORD_BYTES);
|
|
419
|
+
frameAudit.emit("native.frame.summary", {
|
|
420
|
+
reason,
|
|
421
|
+
recordId: recordId.toString(),
|
|
422
|
+
frameId: u64FromView(dvPayload, 0).toString(),
|
|
423
|
+
code,
|
|
424
|
+
codeName: nativeFrameCodeName(code),
|
|
425
|
+
cols: dvPayload.getUint32(8, true),
|
|
426
|
+
rows: dvPayload.getUint32(12, true),
|
|
427
|
+
drawlistBytes: dvPayload.getUint32(16, true),
|
|
428
|
+
drawlistCmds: dvPayload.getUint32(20, true),
|
|
429
|
+
diffBytesEmitted: dvPayload.getUint32(24, true),
|
|
430
|
+
dirtyLines: dvPayload.getUint32(28, true),
|
|
431
|
+
dirtyCells: dvPayload.getUint32(32, true),
|
|
432
|
+
damageRects: dvPayload.getUint32(36, true),
|
|
433
|
+
usDrawlist: dvPayload.getUint32(40, true),
|
|
434
|
+
usDiff: dvPayload.getUint32(44, true),
|
|
435
|
+
usWrite: dvPayload.getUint32(48, true),
|
|
436
|
+
});
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
if (code === ZR_DEBUG_CODE_PERF_TIMING && payloadSize >= DEBUG_PERF_RECORD_BYTES) {
|
|
440
|
+
const payload = tryReadDebugPayload(recordId, code, DEBUG_PERF_RECORD_BYTES);
|
|
441
|
+
if (payload === null)
|
|
442
|
+
continue;
|
|
443
|
+
const dvPayload = new DataView(payload.buffer, payload.byteOffset, DEBUG_PERF_RECORD_BYTES);
|
|
444
|
+
const phase = dvPayload.getUint32(8, true);
|
|
445
|
+
frameAudit.emit("native.perf.timing", {
|
|
446
|
+
reason,
|
|
447
|
+
recordId: recordId.toString(),
|
|
448
|
+
frameId: u64FromView(dvPayload, 0).toString(),
|
|
449
|
+
phase,
|
|
450
|
+
phaseName: nativePerfPhaseName(phase),
|
|
451
|
+
usElapsed: dvPayload.getUint32(12, true),
|
|
452
|
+
bytesProcessed: dvPayload.getUint32(16, true),
|
|
453
|
+
});
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
if (code === ZR_DEBUG_CODE_PERF_DIFF_PATH && payloadSize >= DEBUG_DIFF_PATH_RECORD_BYTES) {
|
|
457
|
+
const payload = tryReadDebugPayload(recordId, code, DEBUG_DIFF_PATH_RECORD_BYTES);
|
|
458
|
+
if (payload === null)
|
|
459
|
+
continue;
|
|
460
|
+
const dvPayload = new DataView(payload.buffer, payload.byteOffset, DEBUG_DIFF_PATH_RECORD_BYTES);
|
|
461
|
+
frameAudit.emit("native.perf.diffPath", {
|
|
462
|
+
reason,
|
|
463
|
+
recordId: recordId.toString(),
|
|
464
|
+
frameId: u64FromView(dvPayload, 0).toString(),
|
|
465
|
+
sweepFramesTotal: u64FromView(dvPayload, 8).toString(),
|
|
466
|
+
damageFramesTotal: u64FromView(dvPayload, 16).toString(),
|
|
467
|
+
scrollAttemptsTotal: u64FromView(dvPayload, 24).toString(),
|
|
468
|
+
scrollHitsTotal: u64FromView(dvPayload, 32).toString(),
|
|
469
|
+
collisionGuardHitsTotal: u64FromView(dvPayload, 40).toString(),
|
|
470
|
+
pathSweepUsed: dvPayload.getUint8(48),
|
|
471
|
+
pathDamageUsed: dvPayload.getUint8(49),
|
|
472
|
+
scrollOptAttempted: dvPayload.getUint8(50),
|
|
473
|
+
scrollOptHit: dvPayload.getUint8(51),
|
|
474
|
+
collisionGuardHitsLast: dvPayload.getUint32(52, true),
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
if (!advanced)
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
173
482
|
function writeResizeBatchV1(buf, cols, rows) {
|
|
174
483
|
// Batch header (24) + RESIZE record (32) = 56 bytes.
|
|
175
484
|
const totalSize = 56;
|
|
@@ -314,6 +623,9 @@ function startTickLoop(fpsCap) {
|
|
|
314
623
|
armSabFrameWake();
|
|
315
624
|
}
|
|
316
625
|
function fatal(where, code, detail) {
|
|
626
|
+
if (frameAudit.enabled) {
|
|
627
|
+
frameAudit.emit("fatal", { where, code, detail });
|
|
628
|
+
}
|
|
317
629
|
postToMain({ type: "fatal", where, code, detail });
|
|
318
630
|
}
|
|
319
631
|
function shutdownComplete() {
|
|
@@ -336,6 +648,8 @@ function releasePendingFrame(frame, expectedSabState) {
|
|
|
336
648
|
function postFrameStatus(frameSeq, completedResult) {
|
|
337
649
|
if (!Number.isInteger(frameSeq) || frameSeq <= 0)
|
|
338
650
|
return;
|
|
651
|
+
emitFrameAudit("frame.completed", frameSeq, { completedResult });
|
|
652
|
+
deleteFrameAudit(frameSeq);
|
|
339
653
|
postToMain({
|
|
340
654
|
type: "frameStatus",
|
|
341
655
|
acceptedSeq: frameSeq,
|
|
@@ -347,6 +661,7 @@ function postFrameStatus(frameSeq, completedResult) {
|
|
|
347
661
|
function postFrameAccepted(frameSeq) {
|
|
348
662
|
if (!Number.isInteger(frameSeq) || frameSeq <= 0)
|
|
349
663
|
return;
|
|
664
|
+
emitFrameAudit("frame.accepted", frameSeq);
|
|
350
665
|
postToMain({
|
|
351
666
|
type: "frameStatus",
|
|
352
667
|
acceptedSeq: frameSeq,
|
|
@@ -405,9 +720,22 @@ function syncPendingSabFrameFromMailbox() {
|
|
|
405
720
|
if (latest === null)
|
|
406
721
|
return;
|
|
407
722
|
if (pendingFrame !== null) {
|
|
723
|
+
emitFrameAudit("frame.overwritten", pendingFrame.frameSeq, { reason: "mailbox-latest-wins" });
|
|
724
|
+
deleteFrameAudit(pendingFrame.frameSeq);
|
|
408
725
|
releasePendingFrame(pendingFrame, FRAME_SAB_SLOT_STATE_READY);
|
|
409
726
|
}
|
|
410
727
|
pendingFrame = latest;
|
|
728
|
+
setFrameAuditMeta(latest.frameSeq, {
|
|
729
|
+
transport: latest.transport,
|
|
730
|
+
byteLen: latest.byteLen,
|
|
731
|
+
slotIndex: latest.slotIndex,
|
|
732
|
+
slotToken: latest.slotToken,
|
|
733
|
+
});
|
|
734
|
+
emitFrameAudit("frame.mailbox.latest", latest.frameSeq, {
|
|
735
|
+
slotIndex: latest.slotIndex,
|
|
736
|
+
slotToken: latest.slotToken,
|
|
737
|
+
byteLen: latest.byteLen,
|
|
738
|
+
});
|
|
411
739
|
}
|
|
412
740
|
function destroyEngineBestEffort() {
|
|
413
741
|
const id = engineId;
|
|
@@ -425,9 +753,17 @@ function shutdownNow() {
|
|
|
425
753
|
running = false;
|
|
426
754
|
stopTickLoop();
|
|
427
755
|
if (pendingFrame !== null) {
|
|
756
|
+
emitFrameAudit("frame.dropped", pendingFrame.frameSeq, { reason: "shutdown" });
|
|
757
|
+
deleteFrameAudit(pendingFrame.frameSeq);
|
|
428
758
|
releasePendingFrame(pendingFrame, FRAME_SAB_SLOT_STATE_READY);
|
|
429
759
|
pendingFrame = null;
|
|
430
760
|
}
|
|
761
|
+
if (frameAudit.enabled) {
|
|
762
|
+
for (const [seq] of frameAuditBySeq.entries()) {
|
|
763
|
+
emitFrameAudit("frame.dropped", seq, { reason: "shutdown_pending" });
|
|
764
|
+
}
|
|
765
|
+
frameAuditBySeq.clear();
|
|
766
|
+
}
|
|
431
767
|
destroyEngineBestEffort();
|
|
432
768
|
shutdownComplete();
|
|
433
769
|
// Let worker thread exit naturally once handles are cleared.
|
|
@@ -454,12 +790,32 @@ function tick() {
|
|
|
454
790
|
if (pendingFrame !== null) {
|
|
455
791
|
const f = pendingFrame;
|
|
456
792
|
pendingFrame = null;
|
|
793
|
+
emitFrameAudit("frame.submit.begin", f.frameSeq, {
|
|
794
|
+
transport: f.transport,
|
|
795
|
+
byteLen: f.byteLen,
|
|
796
|
+
...(f.transport === FRAME_TRANSPORT_SAB_V1
|
|
797
|
+
? { slotIndex: f.slotIndex, slotToken: f.slotToken }
|
|
798
|
+
: {}),
|
|
799
|
+
});
|
|
457
800
|
let res = -1;
|
|
458
801
|
let sabInUse = false;
|
|
459
802
|
let staleSabFrame = false;
|
|
460
803
|
try {
|
|
461
804
|
if (f.transport === FRAME_TRANSPORT_TRANSFER_V1) {
|
|
462
|
-
|
|
805
|
+
const view = new Uint8Array(f.buf, 0, f.byteLen);
|
|
806
|
+
if (frameAudit.enabled) {
|
|
807
|
+
const fp = drawlistFingerprint(view);
|
|
808
|
+
setFrameAuditMeta(f.frameSeq, {
|
|
809
|
+
transport: f.transport,
|
|
810
|
+
byteLen: f.byteLen,
|
|
811
|
+
hash32: fp.hash32,
|
|
812
|
+
prefixHash32: fp.prefixHash32,
|
|
813
|
+
cmdCount: fp.cmdCount,
|
|
814
|
+
totalSize: fp.totalSize,
|
|
815
|
+
});
|
|
816
|
+
emitFrameAudit("frame.submit.payload", f.frameSeq, fp);
|
|
817
|
+
}
|
|
818
|
+
res = native.engineSubmitDrawlist(engineId, view);
|
|
463
819
|
}
|
|
464
820
|
else {
|
|
465
821
|
if (frameTransport.kind !== FRAME_TRANSPORT_SAB_V1) {
|
|
@@ -487,6 +843,20 @@ function tick() {
|
|
|
487
843
|
sabInUse = true;
|
|
488
844
|
const offset = f.slotIndex * frameTransport.slotBytes;
|
|
489
845
|
const view = frameTransport.data.subarray(offset, offset + f.byteLen);
|
|
846
|
+
if (frameAudit.enabled) {
|
|
847
|
+
const fp = drawlistFingerprint(view);
|
|
848
|
+
setFrameAuditMeta(f.frameSeq, {
|
|
849
|
+
transport: f.transport,
|
|
850
|
+
byteLen: f.byteLen,
|
|
851
|
+
slotIndex: f.slotIndex,
|
|
852
|
+
slotToken: f.slotToken,
|
|
853
|
+
hash32: fp.hash32,
|
|
854
|
+
prefixHash32: fp.prefixHash32,
|
|
855
|
+
cmdCount: fp.cmdCount,
|
|
856
|
+
totalSize: fp.totalSize,
|
|
857
|
+
});
|
|
858
|
+
emitFrameAudit("frame.submit.payload", f.frameSeq, fp);
|
|
859
|
+
}
|
|
490
860
|
res = native.engineSubmitDrawlist(engineId, view);
|
|
491
861
|
}
|
|
492
862
|
}
|
|
@@ -494,7 +864,9 @@ function tick() {
|
|
|
494
864
|
}
|
|
495
865
|
catch (err) {
|
|
496
866
|
releasePendingFrame(f, sabInUse ? FRAME_SAB_SLOT_STATE_IN_USE : FRAME_SAB_SLOT_STATE_READY);
|
|
867
|
+
emitFrameAudit("frame.submit.throw", f.frameSeq, { detail: safeDetail(err) });
|
|
497
868
|
postFrameStatus(f.frameSeq, -1);
|
|
869
|
+
drainNativeFrameAudit("submit-throw");
|
|
498
870
|
fatal("engineSubmitDrawlist", -1, `engine_submit_drawlist threw: ${safeDetail(err)}`);
|
|
499
871
|
running = false;
|
|
500
872
|
return;
|
|
@@ -503,6 +875,8 @@ function tick() {
|
|
|
503
875
|
// This frame was superseded in the shared mailbox before submit.
|
|
504
876
|
// Keep latest-wins behavior without surfacing a fatal protocol error.
|
|
505
877
|
didFrameWork = true;
|
|
878
|
+
emitFrameAudit("frame.submit.stale", f.frameSeq, { reason: "slot-token-mismatch" });
|
|
879
|
+
deleteFrameAudit(f.frameSeq);
|
|
506
880
|
syncPendingSabFrameFromMailbox();
|
|
507
881
|
// Continue with present/event processing on this tick.
|
|
508
882
|
}
|
|
@@ -511,6 +885,8 @@ function tick() {
|
|
|
511
885
|
haveSubmittedDrawlist = haveSubmittedDrawlist || didSubmitDrawlistThisTick;
|
|
512
886
|
didFrameWork = true;
|
|
513
887
|
releasePendingFrame(f, FRAME_SAB_SLOT_STATE_IN_USE);
|
|
888
|
+
emitFrameAudit("frame.submit.result", f.frameSeq, { submitResult: res });
|
|
889
|
+
drainNativeFrameAudit("post-submit");
|
|
514
890
|
if (res < 0) {
|
|
515
891
|
postFrameStatus(f.frameSeq, res);
|
|
516
892
|
fatal("engineSubmitDrawlist", res, "engine_submit_drawlist failed");
|
|
@@ -533,22 +909,31 @@ function tick() {
|
|
|
533
909
|
pres = native.enginePresent(engineId);
|
|
534
910
|
}
|
|
535
911
|
catch (err) {
|
|
912
|
+
if (submittedFrameSeq !== null)
|
|
913
|
+
emitFrameAudit("frame.present.throw", submittedFrameSeq, { detail: safeDetail(err) });
|
|
536
914
|
if (submittedFrameSeq !== null)
|
|
537
915
|
postFrameStatus(submittedFrameSeq, -1);
|
|
916
|
+
drainNativeFrameAudit("present-throw");
|
|
538
917
|
fatal("enginePresent", -1, `engine_present threw: ${safeDetail(err)}`);
|
|
539
918
|
running = false;
|
|
540
919
|
return;
|
|
541
920
|
}
|
|
542
921
|
if (pres < 0) {
|
|
922
|
+
if (submittedFrameSeq !== null)
|
|
923
|
+
emitFrameAudit("frame.present.result", submittedFrameSeq, { presentResult: pres });
|
|
543
924
|
if (submittedFrameSeq !== null)
|
|
544
925
|
postFrameStatus(submittedFrameSeq, pres);
|
|
926
|
+
drainNativeFrameAudit("present-failed");
|
|
545
927
|
fatal("enginePresent", pres, "engine_present failed");
|
|
546
928
|
running = false;
|
|
547
929
|
return;
|
|
548
930
|
}
|
|
931
|
+
if (submittedFrameSeq !== null)
|
|
932
|
+
emitFrameAudit("frame.present.result", submittedFrameSeq, { presentResult: pres });
|
|
549
933
|
}
|
|
550
934
|
if (submittedFrameSeq !== null) {
|
|
551
935
|
postFrameStatus(submittedFrameSeq, 0);
|
|
936
|
+
drainNativeFrameAudit("frame-complete");
|
|
552
937
|
}
|
|
553
938
|
// 3) drain events (bounded)
|
|
554
939
|
const discard = discardBuffer;
|
|
@@ -668,6 +1053,9 @@ function onMessage(msg) {
|
|
|
668
1053
|
running = true;
|
|
669
1054
|
pendingFrame = null;
|
|
670
1055
|
lastConsumedSabPublishedSeq = 0;
|
|
1056
|
+
frameAuditBySeq.clear();
|
|
1057
|
+
nativeFrameAuditEnabled = false;
|
|
1058
|
+
nativeFrameAuditNextRecordId = 1n;
|
|
671
1059
|
if (frameTransport.kind === FRAME_TRANSPORT_SAB_V1) {
|
|
672
1060
|
Atomics.store(frameTransport.controlHeader, FRAME_SAB_CONTROL_PUBLISHED_SEQ_WORD, 0);
|
|
673
1061
|
Atomics.store(frameTransport.controlHeader, FRAME_SAB_CONTROL_PUBLISHED_SLOT_WORD, 0);
|
|
@@ -697,6 +1085,15 @@ function onMessage(msg) {
|
|
|
697
1085
|
if (shim === null || shim.length === 0) {
|
|
698
1086
|
maybeInjectInitialResize(maxEventBytes);
|
|
699
1087
|
}
|
|
1088
|
+
if (frameAudit.enabled) {
|
|
1089
|
+
frameAudit.emit("engine.ready", {
|
|
1090
|
+
engineId: id,
|
|
1091
|
+
frameTransport: frameTransport.kind,
|
|
1092
|
+
maxEventBytes,
|
|
1093
|
+
fpsCap: parsePositiveInt(msg.config.fpsCap) ?? 60,
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
maybeEnableNativeFrameAudit();
|
|
700
1097
|
postToMain({ type: "ready", engineId: id });
|
|
701
1098
|
const fpsCap = parsePositiveInt(msg.config.fpsCap) ?? 60;
|
|
702
1099
|
startTickLoop(fpsCap);
|
|
@@ -707,6 +1104,10 @@ function onMessage(msg) {
|
|
|
707
1104
|
return;
|
|
708
1105
|
// latest-wins overwrite for transfer-path fallback.
|
|
709
1106
|
if (pendingFrame !== null) {
|
|
1107
|
+
emitFrameAudit("frame.overwritten", pendingFrame.frameSeq, {
|
|
1108
|
+
reason: "message-latest-wins",
|
|
1109
|
+
});
|
|
1110
|
+
deleteFrameAudit(pendingFrame.frameSeq);
|
|
710
1111
|
releasePendingFrame(pendingFrame, FRAME_SAB_SLOT_STATE_READY);
|
|
711
1112
|
}
|
|
712
1113
|
const frameTransportTag = msg.transport ?? FRAME_TRANSPORT_TRANSFER_V1;
|
|
@@ -742,6 +1143,18 @@ function onMessage(msg) {
|
|
|
742
1143
|
slotToken: msg.slotToken,
|
|
743
1144
|
byteLen: msg.byteLen,
|
|
744
1145
|
};
|
|
1146
|
+
setFrameAuditMeta(msg.frameSeq, {
|
|
1147
|
+
transport: FRAME_TRANSPORT_SAB_V1,
|
|
1148
|
+
byteLen: msg.byteLen,
|
|
1149
|
+
slotIndex: msg.slotIndex,
|
|
1150
|
+
slotToken: msg.slotToken,
|
|
1151
|
+
});
|
|
1152
|
+
emitFrameAudit("frame.received", msg.frameSeq, {
|
|
1153
|
+
transport: FRAME_TRANSPORT_SAB_V1,
|
|
1154
|
+
byteLen: msg.byteLen,
|
|
1155
|
+
slotIndex: msg.slotIndex,
|
|
1156
|
+
slotToken: msg.slotToken,
|
|
1157
|
+
});
|
|
745
1158
|
}
|
|
746
1159
|
else {
|
|
747
1160
|
if (!(msg.drawlist instanceof ArrayBuffer)) {
|
|
@@ -762,6 +1175,21 @@ function onMessage(msg) {
|
|
|
762
1175
|
buf: msg.drawlist,
|
|
763
1176
|
byteLen: msg.byteLen,
|
|
764
1177
|
};
|
|
1178
|
+
if (frameAudit.enabled) {
|
|
1179
|
+
const fp = drawlistFingerprint(new Uint8Array(msg.drawlist, 0, msg.byteLen));
|
|
1180
|
+
setFrameAuditMeta(msg.frameSeq, {
|
|
1181
|
+
transport: FRAME_TRANSPORT_TRANSFER_V1,
|
|
1182
|
+
byteLen: msg.byteLen,
|
|
1183
|
+
hash32: fp.hash32,
|
|
1184
|
+
prefixHash32: fp.prefixHash32,
|
|
1185
|
+
cmdCount: fp.cmdCount,
|
|
1186
|
+
totalSize: fp.totalSize,
|
|
1187
|
+
});
|
|
1188
|
+
emitFrameAudit("frame.received", msg.frameSeq, {
|
|
1189
|
+
transport: FRAME_TRANSPORT_TRANSFER_V1,
|
|
1190
|
+
...fp,
|
|
1191
|
+
});
|
|
1192
|
+
}
|
|
765
1193
|
}
|
|
766
1194
|
idleDelayMs = tickIntervalMs;
|
|
767
1195
|
scheduleTickNow();
|
|
@@ -870,6 +1298,15 @@ function onMessage(msg) {
|
|
|
870
1298
|
fatal("engineDebugEnable", -1, `engine_debug_enable threw: ${safeDetail(err)}`);
|
|
871
1299
|
return;
|
|
872
1300
|
}
|
|
1301
|
+
if (frameAudit.enabled) {
|
|
1302
|
+
frameAudit.emit("native.debug.enable.user", {
|
|
1303
|
+
rc,
|
|
1304
|
+
captureDrawlistBytes: msg.config.captureDrawlistBytes ?? false,
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1307
|
+
nativeFrameAuditEnabled = rc >= 0 && frameAudit.enabled;
|
|
1308
|
+
if (nativeFrameAuditEnabled)
|
|
1309
|
+
nativeFrameAuditNextRecordId = 1n;
|
|
873
1310
|
postToMain({ type: "debug:enableResult", result: rc });
|
|
874
1311
|
return;
|
|
875
1312
|
}
|
|
@@ -884,6 +1321,10 @@ function onMessage(msg) {
|
|
|
884
1321
|
fatal("engineDebugDisable", -1, `engine_debug_disable threw: ${safeDetail(err)}`);
|
|
885
1322
|
return;
|
|
886
1323
|
}
|
|
1324
|
+
nativeFrameAuditEnabled = false;
|
|
1325
|
+
if (frameAudit.enabled) {
|
|
1326
|
+
frameAudit.emit("native.debug.disable.user", { rc });
|
|
1327
|
+
}
|
|
887
1328
|
postToMain({ type: "debug:disableResult", result: rc });
|
|
888
1329
|
return;
|
|
889
1330
|
}
|
|
@@ -1001,6 +1442,10 @@ function onMessage(msg) {
|
|
|
1001
1442
|
fatal("engineDebugReset", -1, `engine_debug_reset threw: ${safeDetail(err)}`);
|
|
1002
1443
|
return;
|
|
1003
1444
|
}
|
|
1445
|
+
if (frameAudit.enabled && rc >= 0) {
|
|
1446
|
+
nativeFrameAuditNextRecordId = 1n;
|
|
1447
|
+
frameAudit.emit("native.debug.reset", { rc });
|
|
1448
|
+
}
|
|
1004
1449
|
postToMain({ type: "debug:resetResult", result: rc });
|
|
1005
1450
|
return;
|
|
1006
1451
|
}
|