@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
|
@@ -25,13 +25,6 @@ export type NodeBackendConfig = Readonly<{
|
|
|
25
25
|
* remain aligned by construction.
|
|
26
26
|
*/
|
|
27
27
|
maxEventBytes?: number;
|
|
28
|
-
/**
|
|
29
|
-
* Explicit drawlist version request.
|
|
30
|
-
*
|
|
31
|
-
* Defaults to `5` (enables v3 style extensions + v4 canvas + v5 image commands).
|
|
32
|
-
* Supported versions are `2`-`5`.
|
|
33
|
-
*/
|
|
34
|
-
drawlistVersion?: 2 | 3 | 4 | 5;
|
|
35
28
|
/**
|
|
36
29
|
* Frame transport mode:
|
|
37
30
|
* - "auto": prefer SAB mailbox transport when available, fallback to transfer.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nodeBackend.d.ts","sourceRoot":"","sources":["../../src/backend/nodeBackend.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAGV,YAAY,EAKZ,cAAc,EAGf,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"nodeBackend.d.ts","sourceRoot":"","sources":["../../src/backend/nodeBackend.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAGV,YAAY,EAKZ,cAAc,EAGf,MAAM,eAAe,CAAC;AAkDvB,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IACvC;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAC7C;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,KAAK,CAAC;IAC7C,2CAA2C;IAC3C,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,mDAAmD;IACnD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,YAAY,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACjD;;;;;;;;OAQG;IACH,gBAAgB,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC;CAC/C,CAAC,CAAC;AAEH,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAC7C,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC,CAAC;AAEH,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAC7C,MAAM,EAAE,QAAQ,CACd,MAAM,CACJ,MAAM,EACN;QACE,KAAK,EAAE,MAAM,CAAC;QACd,GAAG,EAAE,MAAM,CAAC;QACZ,GAAG,EAAE,MAAM,CAAC;QACZ,GAAG,EAAE,MAAM,CAAC;QACZ,GAAG,EAAE,MAAM,CAAC;QACZ,GAAG,EAAE,MAAM,CAAC;QACZ,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;KAC5B,CACF,CACF,CAAC;CACH,CAAC,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IACrC,YAAY,EAAE,MAAM,OAAO,CAAC,uBAAuB,CAAC,CAAC;CACtD,CAAC,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG,cAAc,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,eAAe,CAAA;CAAE,CAAC,CAAC;AA+UpG,wBAAgB,yBAAyB,CAAC,IAAI,GAAE,uBAA4B,GAAG,WAAW,CAwvCzF"}
|
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
* @see docs/backend/native.md
|
|
8
8
|
*/
|
|
9
9
|
import { Worker } from "node:worker_threads";
|
|
10
|
-
import { BACKEND_DRAWLIST_VERSION_MARKER, BACKEND_FPS_CAP_MARKER, BACKEND_MAX_EVENT_BYTES_MARKER, BACKEND_RAW_WRITE_MARKER, DEFAULT_TERMINAL_CAPS, FRAME_ACCEPTED_ACK_MARKER, } from "@rezi-ui/core";
|
|
11
|
-
import {
|
|
10
|
+
import { BACKEND_BEGIN_FRAME_MARKER, BACKEND_DRAWLIST_VERSION_MARKER, BACKEND_FPS_CAP_MARKER, BACKEND_MAX_EVENT_BYTES_MARKER, BACKEND_RAW_WRITE_MARKER, DEFAULT_TERMINAL_CAPS, FRAME_ACCEPTED_ACK_MARKER, } from "@rezi-ui/core";
|
|
11
|
+
import { ZR_DRAWLIST_VERSION_V1, ZR_ENGINE_ABI_MAJOR, ZR_ENGINE_ABI_MINOR, ZR_ENGINE_ABI_PATCH, ZR_EVENT_BATCH_VERSION_V1, ZrUiError, setTextMeasureEmojiPolicy, severityToNum, } from "@rezi-ui/core";
|
|
12
|
+
import { createFrameAuditLogger, drawlistFingerprint, maybeDumpDrawlistBytes, } from "../frameAudit.js";
|
|
12
13
|
import { 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_READY, FRAME_SAB_SLOT_STATE_WRITING, FRAME_TRANSPORT_SAB_V1, FRAME_TRANSPORT_TRANSFER_V1, FRAME_TRANSPORT_VERSION, } from "../worker/protocol.js";
|
|
13
14
|
import { applyEmojiWidthPolicy, resolveBackendEmojiWidthPolicy } from "./emojiWidthPolicy.js";
|
|
14
15
|
import { createNodeBackendInlineInternal } from "./nodeBackendInline.js";
|
|
@@ -80,19 +81,6 @@ function parsePositiveInt(n) {
|
|
|
80
81
|
return null;
|
|
81
82
|
return n;
|
|
82
83
|
}
|
|
83
|
-
function parseDrawlistVersion(v) {
|
|
84
|
-
if (v === undefined)
|
|
85
|
-
return null;
|
|
86
|
-
if (v === 2 || v === 3 || v === 4 || v === 5)
|
|
87
|
-
return v;
|
|
88
|
-
throw new ZrUiError("ZRUI_INVALID_PROPS", `createNodeBackend config mismatch: drawlistVersion must be one of 2, 3, 4, 5 (got ${String(v)}).`);
|
|
89
|
-
}
|
|
90
|
-
function resolveRequestedDrawlistVersion(config) {
|
|
91
|
-
const explicitDrawlistVersion = parseDrawlistVersion(config.drawlistVersion);
|
|
92
|
-
if (explicitDrawlistVersion !== null)
|
|
93
|
-
return explicitDrawlistVersion;
|
|
94
|
-
return ZR_DRAWLIST_VERSION_V5;
|
|
95
|
-
}
|
|
96
84
|
function parseBoundedPositiveIntOrThrow(name, value, fallback, max) {
|
|
97
85
|
if (value === undefined)
|
|
98
86
|
return fallback;
|
|
@@ -205,6 +193,38 @@ function acquireSabSlot(t) {
|
|
|
205
193
|
}
|
|
206
194
|
return -1;
|
|
207
195
|
}
|
|
196
|
+
function acquireSabSlotTracked(t) {
|
|
197
|
+
const start = t.nextSlot.value % t.slotCount;
|
|
198
|
+
for (let i = 0; i < t.slotCount; i++) {
|
|
199
|
+
const slot = (start + i) % t.slotCount;
|
|
200
|
+
const prev = Atomics.compareExchange(t.states, slot, FRAME_SAB_SLOT_STATE_FREE, FRAME_SAB_SLOT_STATE_WRITING);
|
|
201
|
+
if (prev === FRAME_SAB_SLOT_STATE_FREE) {
|
|
202
|
+
t.nextSlot.value = (slot + 1) % t.slotCount;
|
|
203
|
+
return { slotIndex: slot, reclaimedReady: false };
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
for (let i = 0; i < t.slotCount; i++) {
|
|
207
|
+
const slot = (start + i) % t.slotCount;
|
|
208
|
+
const prev = Atomics.compareExchange(t.states, slot, FRAME_SAB_SLOT_STATE_READY, FRAME_SAB_SLOT_STATE_WRITING);
|
|
209
|
+
if (prev === FRAME_SAB_SLOT_STATE_READY) {
|
|
210
|
+
t.nextSlot.value = (slot + 1) % t.slotCount;
|
|
211
|
+
return { slotIndex: slot, reclaimedReady: true };
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return { slotIndex: -1, reclaimedReady: false };
|
|
215
|
+
}
|
|
216
|
+
function acquireSabFreeSlot(t) {
|
|
217
|
+
const start = t.nextSlot.value % t.slotCount;
|
|
218
|
+
for (let i = 0; i < t.slotCount; i++) {
|
|
219
|
+
const slot = (start + i) % t.slotCount;
|
|
220
|
+
const prev = Atomics.compareExchange(t.states, slot, FRAME_SAB_SLOT_STATE_FREE, FRAME_SAB_SLOT_STATE_WRITING);
|
|
221
|
+
if (prev === FRAME_SAB_SLOT_STATE_FREE) {
|
|
222
|
+
t.nextSlot.value = (slot + 1) % t.slotCount;
|
|
223
|
+
return slot;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return -1;
|
|
227
|
+
}
|
|
208
228
|
function publishSabFrame(t, frameSeq, slotIndex, slotToken, byteLen) {
|
|
209
229
|
Atomics.store(t.controlHeader, FRAME_SAB_CONTROL_PUBLISHED_SLOT_WORD, slotIndex);
|
|
210
230
|
Atomics.store(t.controlHeader, FRAME_SAB_CONTROL_PUBLISHED_BYTES_WORD, byteLen);
|
|
@@ -212,6 +232,7 @@ function publishSabFrame(t, frameSeq, slotIndex, slotToken, byteLen) {
|
|
|
212
232
|
Atomics.store(t.controlHeader, FRAME_SAB_CONTROL_PUBLISHED_SEQ_WORD, frameSeq);
|
|
213
233
|
}
|
|
214
234
|
export function createNodeBackendInternal(opts = {}) {
|
|
235
|
+
const frameAudit = createFrameAuditLogger("backend");
|
|
215
236
|
const cfg = opts.config ?? {};
|
|
216
237
|
const fpsCap = parseBoundedPositiveIntOrThrow("fpsCap", cfg.fpsCap, DEFAULT_FPS_CAP, MAX_SAFE_FPS_CAP);
|
|
217
238
|
const requestedExecutionMode = cfg.executionMode ?? "auto";
|
|
@@ -225,7 +246,7 @@ export function createNodeBackendInternal(opts = {}) {
|
|
|
225
246
|
if (executionMode === "inline") {
|
|
226
247
|
return createNodeBackendInlineInternal(opts);
|
|
227
248
|
}
|
|
228
|
-
const requestedDrawlistVersion =
|
|
249
|
+
const requestedDrawlistVersion = ZR_DRAWLIST_VERSION_V1;
|
|
229
250
|
const maxEventBytes = parseBoundedPositiveIntOrThrow("maxEventBytes", cfg.maxEventBytes, DEFAULT_MAX_EVENT_BYTES, MAX_SAFE_EVENT_BYTES);
|
|
230
251
|
const frameTransportMode = cfg.frameTransport === "transfer" || cfg.frameTransport === "sab" ? cfg.frameTransport : "auto";
|
|
231
252
|
const frameSabSlotCount = parsePositiveIntOr(cfg.frameSabSlotCount, FRAME_SAB_SLOT_COUNT_DEFAULT);
|
|
@@ -280,6 +301,7 @@ export function createNodeBackendInternal(opts = {}) {
|
|
|
280
301
|
let nextFrameSeq = 1;
|
|
281
302
|
const frameAcceptedWaiters = new Map();
|
|
282
303
|
const frameCompletionWaiters = new Map();
|
|
304
|
+
const frameAuditBySeq = new Map();
|
|
283
305
|
const eventQueue = [];
|
|
284
306
|
const eventWaiters = [];
|
|
285
307
|
const capsWaiters = [];
|
|
@@ -329,10 +351,85 @@ export function createNodeBackendInternal(opts = {}) {
|
|
|
329
351
|
waiter.reject(err);
|
|
330
352
|
}
|
|
331
353
|
frameCompletionWaiters.clear();
|
|
354
|
+
if (frameAudit.enabled) {
|
|
355
|
+
for (const [seq, meta] of frameAuditBySeq.entries()) {
|
|
356
|
+
frameAudit.emit("frame.aborted", {
|
|
357
|
+
reason: err.message,
|
|
358
|
+
ageMs: Math.max(0, Date.now() - meta.submitAtMs),
|
|
359
|
+
...meta,
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
frameAuditBySeq.clear();
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
function registerFrameAudit(frameSeq, submitPath, transport, bytes, slotIndex, slotToken) {
|
|
366
|
+
if (!frameAudit.enabled)
|
|
367
|
+
return;
|
|
368
|
+
const fp = drawlistFingerprint(bytes);
|
|
369
|
+
const meta = {
|
|
370
|
+
frameSeq,
|
|
371
|
+
submitAtMs: Date.now(),
|
|
372
|
+
submitPath,
|
|
373
|
+
transport,
|
|
374
|
+
byteLen: fp.byteLen,
|
|
375
|
+
hash32: fp.hash32,
|
|
376
|
+
prefixHash32: fp.prefixHash32,
|
|
377
|
+
cmdCount: fp.cmdCount,
|
|
378
|
+
totalSize: fp.totalSize,
|
|
379
|
+
head16: fp.head16,
|
|
380
|
+
tail16: fp.tail16,
|
|
381
|
+
...(slotIndex === undefined ? {} : { slotIndex }),
|
|
382
|
+
...(slotToken === undefined ? {} : { slotToken }),
|
|
383
|
+
};
|
|
384
|
+
frameAuditBySeq.set(frameSeq, meta);
|
|
385
|
+
maybeDumpDrawlistBytes("backend", submitPath, frameSeq, bytes);
|
|
386
|
+
frameAudit.emit("frame.submitted", meta);
|
|
387
|
+
}
|
|
388
|
+
function markAcceptedFramesUpTo(acceptedSeq) {
|
|
389
|
+
if (!frameAudit.enabled)
|
|
390
|
+
return;
|
|
391
|
+
for (const [seq, meta] of frameAuditBySeq.entries()) {
|
|
392
|
+
if (seq > acceptedSeq)
|
|
393
|
+
continue;
|
|
394
|
+
if (meta.acceptedLogged === true)
|
|
395
|
+
continue;
|
|
396
|
+
frameAudit.emit("frame.accepted", {
|
|
397
|
+
acceptedSeq,
|
|
398
|
+
ageMs: Math.max(0, Date.now() - meta.submitAtMs),
|
|
399
|
+
...meta,
|
|
400
|
+
});
|
|
401
|
+
meta.acceptedLogged = true;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
function markCoalescedFramesBefore(acceptedSeq) {
|
|
405
|
+
if (!frameAudit.enabled)
|
|
406
|
+
return;
|
|
407
|
+
for (const [seq, meta] of frameAuditBySeq.entries()) {
|
|
408
|
+
if (seq >= acceptedSeq)
|
|
409
|
+
continue;
|
|
410
|
+
frameAudit.emit("frame.coalesced", {
|
|
411
|
+
acceptedSeq,
|
|
412
|
+
ageMs: Math.max(0, Date.now() - meta.submitAtMs),
|
|
413
|
+
...meta,
|
|
414
|
+
});
|
|
415
|
+
frameAuditBySeq.delete(seq);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
function markCompletedFrame(frameSeq, completedResult) {
|
|
419
|
+
if (!frameAudit.enabled)
|
|
420
|
+
return;
|
|
421
|
+
const meta = frameAuditBySeq.get(frameSeq);
|
|
422
|
+
frameAudit.emit("frame.completed", {
|
|
423
|
+
completedResult,
|
|
424
|
+
ageMs: meta ? Math.max(0, Date.now() - meta.submitAtMs) : null,
|
|
425
|
+
...(meta ?? {}),
|
|
426
|
+
});
|
|
427
|
+
frameAuditBySeq.delete(frameSeq);
|
|
332
428
|
}
|
|
333
429
|
function resolveAcceptedFramesUpTo(acceptedSeq) {
|
|
334
430
|
if (!Number.isInteger(acceptedSeq) || acceptedSeq <= 0)
|
|
335
431
|
return;
|
|
432
|
+
markAcceptedFramesUpTo(acceptedSeq);
|
|
336
433
|
for (const [seq, waiter] of frameAcceptedWaiters.entries()) {
|
|
337
434
|
if (seq > acceptedSeq)
|
|
338
435
|
continue;
|
|
@@ -343,6 +440,7 @@ export function createNodeBackendInternal(opts = {}) {
|
|
|
343
440
|
function resolveCoalescedCompletionFramesUpTo(acceptedSeq) {
|
|
344
441
|
if (!Number.isInteger(acceptedSeq) || acceptedSeq <= 0)
|
|
345
442
|
return;
|
|
443
|
+
markCoalescedFramesBefore(acceptedSeq);
|
|
346
444
|
for (const [seq, waiter] of frameCompletionWaiters.entries()) {
|
|
347
445
|
if (seq >= acceptedSeq)
|
|
348
446
|
continue;
|
|
@@ -351,6 +449,7 @@ export function createNodeBackendInternal(opts = {}) {
|
|
|
351
449
|
}
|
|
352
450
|
}
|
|
353
451
|
function settleCompletedFrame(frameSeq, completedResult) {
|
|
452
|
+
markCompletedFrame(frameSeq, completedResult);
|
|
354
453
|
const waiter = frameCompletionWaiters.get(frameSeq);
|
|
355
454
|
if (waiter === undefined)
|
|
356
455
|
return;
|
|
@@ -361,6 +460,26 @@ export function createNodeBackendInternal(opts = {}) {
|
|
|
361
460
|
}
|
|
362
461
|
waiter.resolve(undefined);
|
|
363
462
|
}
|
|
463
|
+
function reserveFramePromise(frameSeq) {
|
|
464
|
+
const frameAcceptedDef = deferred();
|
|
465
|
+
frameAcceptedWaiters.set(frameSeq, frameAcceptedDef);
|
|
466
|
+
const frameCompletionDef = deferred();
|
|
467
|
+
frameCompletionWaiters.set(frameSeq, frameCompletionDef);
|
|
468
|
+
const framePromise = frameCompletionDef.promise;
|
|
469
|
+
Object.defineProperty(framePromise, FRAME_ACCEPTED_ACK_MARKER, {
|
|
470
|
+
value: frameAcceptedDef.promise,
|
|
471
|
+
configurable: false,
|
|
472
|
+
enumerable: false,
|
|
473
|
+
writable: false,
|
|
474
|
+
});
|
|
475
|
+
return framePromise;
|
|
476
|
+
}
|
|
477
|
+
function releaseFrameReservation(frameSeq) {
|
|
478
|
+
frameAcceptedWaiters.delete(frameSeq);
|
|
479
|
+
frameCompletionWaiters.delete(frameSeq);
|
|
480
|
+
if (frameAudit.enabled)
|
|
481
|
+
frameAuditBySeq.delete(frameSeq);
|
|
482
|
+
}
|
|
364
483
|
function failAll(err) {
|
|
365
484
|
while (eventWaiters.length > 0)
|
|
366
485
|
eventWaiters.shift()?.reject(err);
|
|
@@ -420,6 +539,13 @@ export function createNodeBackendInternal(opts = {}) {
|
|
|
420
539
|
return;
|
|
421
540
|
}
|
|
422
541
|
case "frameStatus": {
|
|
542
|
+
if (frameAudit.enabled) {
|
|
543
|
+
frameAudit.emit("worker.frameStatus", {
|
|
544
|
+
acceptedSeq: msg.acceptedSeq,
|
|
545
|
+
completedSeq: msg.completedSeq ?? null,
|
|
546
|
+
completedResult: msg.completedResult ?? null,
|
|
547
|
+
});
|
|
548
|
+
}
|
|
423
549
|
if (!Number.isInteger(msg.acceptedSeq) || msg.acceptedSeq <= 0) {
|
|
424
550
|
fatal = new ZrUiError("ZRUI_BACKEND_ERROR", `invalid frameStatus.acceptedSeq: ${String(msg.acceptedSeq)}`);
|
|
425
551
|
failAll(fatal);
|
|
@@ -700,6 +826,13 @@ export function createNodeBackendInternal(opts = {}) {
|
|
|
700
826
|
? undefined
|
|
701
827
|
: { nativeShimModule: opts.nativeShimModule };
|
|
702
828
|
worker = new Worker(entry, { workerData });
|
|
829
|
+
if (frameAudit.enabled) {
|
|
830
|
+
frameAudit.emit("worker.spawn", {
|
|
831
|
+
frameTransport: frameTransportWire.kind,
|
|
832
|
+
frameSabSlotCount: frameSabSlotCount,
|
|
833
|
+
frameSabSlotBytes: frameSabSlotBytes,
|
|
834
|
+
});
|
|
835
|
+
}
|
|
703
836
|
exitDef = deferred();
|
|
704
837
|
worker.on("message", handleWorkerMessage);
|
|
705
838
|
worker.on("error", (err) => {
|
|
@@ -774,37 +907,44 @@ export function createNodeBackendInternal(opts = {}) {
|
|
|
774
907
|
if (worker === null)
|
|
775
908
|
return Promise.reject(new Error("NodeBackend: worker not available"));
|
|
776
909
|
const frameSeq = nextFrameSeq++;
|
|
777
|
-
const
|
|
778
|
-
frameAcceptedWaiters.set(frameSeq, frameAcceptedDef);
|
|
779
|
-
const frameCompletionDef = deferred();
|
|
780
|
-
frameCompletionWaiters.set(frameSeq, frameCompletionDef);
|
|
781
|
-
const framePromise = frameCompletionDef.promise;
|
|
782
|
-
Object.defineProperty(framePromise, FRAME_ACCEPTED_ACK_MARKER, {
|
|
783
|
-
value: frameAcceptedDef.promise,
|
|
784
|
-
configurable: false,
|
|
785
|
-
enumerable: false,
|
|
786
|
-
writable: false,
|
|
787
|
-
});
|
|
910
|
+
const framePromise = reserveFramePromise(frameSeq);
|
|
788
911
|
if (sabFrameTransport !== null && drawlist.byteLength <= sabFrameTransport.slotBytes) {
|
|
789
912
|
const slotIndex = acquireSabSlot(sabFrameTransport);
|
|
790
913
|
if (slotIndex >= 0) {
|
|
791
914
|
const slotToken = frameSeqToSlotToken(frameSeq);
|
|
915
|
+
registerFrameAudit(frameSeq, "requestFrame", FRAME_TRANSPORT_SAB_V1, drawlist, slotIndex, slotToken);
|
|
792
916
|
const slotOffset = slotIndex * sabFrameTransport.slotBytes;
|
|
793
917
|
sabFrameTransport.dataBytes.set(drawlist, slotOffset);
|
|
794
918
|
Atomics.store(sabFrameTransport.tokens, slotIndex, slotToken);
|
|
795
919
|
Atomics.store(sabFrameTransport.states, slotIndex, FRAME_SAB_SLOT_STATE_READY);
|
|
796
920
|
publishSabFrame(sabFrameTransport, frameSeq, slotIndex, slotToken, drawlist.byteLength);
|
|
921
|
+
if (frameAudit.enabled) {
|
|
922
|
+
frameAudit.emit("frame.sab.publish", {
|
|
923
|
+
frameSeq,
|
|
924
|
+
slotIndex,
|
|
925
|
+
slotToken,
|
|
926
|
+
byteLen: drawlist.byteLength,
|
|
927
|
+
});
|
|
928
|
+
}
|
|
797
929
|
// SAB consumers wake on futex notify instead of per-frame
|
|
798
930
|
// MessagePort frameKick round-trips.
|
|
799
931
|
Atomics.notify(sabFrameTransport.controlHeader, FRAME_SAB_CONTROL_PUBLISHED_SEQ_WORD, 1);
|
|
800
932
|
return framePromise;
|
|
801
933
|
}
|
|
934
|
+
if (frameAudit.enabled) {
|
|
935
|
+
frameAudit.emit("frame.sab.fallback_transfer", {
|
|
936
|
+
frameSeq,
|
|
937
|
+
byteLen: drawlist.byteLength,
|
|
938
|
+
reason: "no-slot-available",
|
|
939
|
+
});
|
|
940
|
+
}
|
|
802
941
|
}
|
|
803
942
|
// Transfer fallback participates in the same ACK model:
|
|
804
943
|
// - accepted ACK (hidden marker) can unblock app scheduling early
|
|
805
944
|
// - completion promise settles on worker completion/coalescing status
|
|
806
945
|
const buf = new ArrayBuffer(drawlist.byteLength);
|
|
807
946
|
copyInto(buf, drawlist);
|
|
947
|
+
registerFrameAudit(frameSeq, "requestFrame", FRAME_TRANSPORT_TRANSFER_V1, drawlist);
|
|
808
948
|
try {
|
|
809
949
|
send({
|
|
810
950
|
type: "frame",
|
|
@@ -815,10 +955,21 @@ export function createNodeBackendInternal(opts = {}) {
|
|
|
815
955
|
}, [buf]);
|
|
816
956
|
}
|
|
817
957
|
catch (err) {
|
|
818
|
-
|
|
819
|
-
|
|
958
|
+
releaseFrameReservation(frameSeq);
|
|
959
|
+
if (frameAudit.enabled) {
|
|
960
|
+
frameAudit.emit("frame.transfer.publish_error", {
|
|
961
|
+
frameSeq,
|
|
962
|
+
detail: safeErr(err).message,
|
|
963
|
+
});
|
|
964
|
+
}
|
|
820
965
|
return Promise.reject(safeErr(err));
|
|
821
966
|
}
|
|
967
|
+
if (frameAudit.enabled) {
|
|
968
|
+
frameAudit.emit("frame.transfer.publish", {
|
|
969
|
+
frameSeq,
|
|
970
|
+
byteLen: drawlist.byteLength,
|
|
971
|
+
});
|
|
972
|
+
}
|
|
822
973
|
return framePromise;
|
|
823
974
|
},
|
|
824
975
|
pollEvents() {
|
|
@@ -1060,6 +1211,107 @@ export function createNodeBackendInternal(opts = {}) {
|
|
|
1060
1211
|
return snapshot;
|
|
1061
1212
|
}),
|
|
1062
1213
|
};
|
|
1214
|
+
const beginFrameMetrics = {
|
|
1215
|
+
success: 0,
|
|
1216
|
+
fallbackToRequestFrame: 0,
|
|
1217
|
+
readyReclaims: 0,
|
|
1218
|
+
};
|
|
1219
|
+
const beginFrame = sabFrameTransport === null
|
|
1220
|
+
? null
|
|
1221
|
+
: (minCapacity) => {
|
|
1222
|
+
if (disposed)
|
|
1223
|
+
return null;
|
|
1224
|
+
if (fatal !== null)
|
|
1225
|
+
return null;
|
|
1226
|
+
if (stopRequested || !started || worker === null)
|
|
1227
|
+
return null;
|
|
1228
|
+
const required = typeof minCapacity === "number" && Number.isInteger(minCapacity) && minCapacity > 0
|
|
1229
|
+
? minCapacity
|
|
1230
|
+
: 0;
|
|
1231
|
+
if (required > sabFrameTransport.slotBytes)
|
|
1232
|
+
return null;
|
|
1233
|
+
const result = acquireSabSlotTracked(sabFrameTransport);
|
|
1234
|
+
if (result.slotIndex < 0) {
|
|
1235
|
+
beginFrameMetrics.fallbackToRequestFrame++;
|
|
1236
|
+
if (frameAudit.enabled) {
|
|
1237
|
+
frameAudit.emit("frame.beginFrame.fallback", {
|
|
1238
|
+
reason: "no-slot-available",
|
|
1239
|
+
metrics: { ...beginFrameMetrics },
|
|
1240
|
+
});
|
|
1241
|
+
}
|
|
1242
|
+
return null;
|
|
1243
|
+
}
|
|
1244
|
+
if (result.reclaimedReady) {
|
|
1245
|
+
beginFrameMetrics.readyReclaims++;
|
|
1246
|
+
}
|
|
1247
|
+
beginFrameMetrics.success++;
|
|
1248
|
+
const slotIndex = result.slotIndex;
|
|
1249
|
+
const slotOffset = slotIndex * sabFrameTransport.slotBytes;
|
|
1250
|
+
const buf = sabFrameTransport.dataBytes.subarray(slotOffset, slotOffset + sabFrameTransport.slotBytes);
|
|
1251
|
+
let finalized = false;
|
|
1252
|
+
return {
|
|
1253
|
+
buf,
|
|
1254
|
+
commit: (byteLen) => {
|
|
1255
|
+
if (finalized) {
|
|
1256
|
+
return Promise.reject(new Error("NodeBackend: beginFrame writer already finalized"));
|
|
1257
|
+
}
|
|
1258
|
+
finalized = true;
|
|
1259
|
+
if (disposed) {
|
|
1260
|
+
Atomics.store(sabFrameTransport.tokens, slotIndex, 0);
|
|
1261
|
+
Atomics.store(sabFrameTransport.states, slotIndex, FRAME_SAB_SLOT_STATE_FREE);
|
|
1262
|
+
return Promise.reject(new Error("NodeBackend: disposed"));
|
|
1263
|
+
}
|
|
1264
|
+
if (fatal !== null) {
|
|
1265
|
+
Atomics.store(sabFrameTransport.tokens, slotIndex, 0);
|
|
1266
|
+
Atomics.store(sabFrameTransport.states, slotIndex, FRAME_SAB_SLOT_STATE_FREE);
|
|
1267
|
+
return Promise.reject(fatal);
|
|
1268
|
+
}
|
|
1269
|
+
if (stopRequested || !started || worker === null) {
|
|
1270
|
+
Atomics.store(sabFrameTransport.tokens, slotIndex, 0);
|
|
1271
|
+
Atomics.store(sabFrameTransport.states, slotIndex, FRAME_SAB_SLOT_STATE_FREE);
|
|
1272
|
+
return Promise.reject(new Error("NodeBackend: stopped"));
|
|
1273
|
+
}
|
|
1274
|
+
if (!Number.isInteger(byteLen) ||
|
|
1275
|
+
byteLen < 0 ||
|
|
1276
|
+
byteLen > sabFrameTransport.slotBytes) {
|
|
1277
|
+
Atomics.store(sabFrameTransport.tokens, slotIndex, 0);
|
|
1278
|
+
Atomics.store(sabFrameTransport.states, slotIndex, FRAME_SAB_SLOT_STATE_FREE);
|
|
1279
|
+
return Promise.reject(new Error("NodeBackend: beginFrame commit byteLen out of range"));
|
|
1280
|
+
}
|
|
1281
|
+
const frameSeq = nextFrameSeq++;
|
|
1282
|
+
const framePromise = reserveFramePromise(frameSeq);
|
|
1283
|
+
const slotToken = frameSeqToSlotToken(frameSeq);
|
|
1284
|
+
registerFrameAudit(frameSeq, "beginFrame", FRAME_TRANSPORT_SAB_V1, buf.subarray(0, byteLen), slotIndex, slotToken);
|
|
1285
|
+
Atomics.store(sabFrameTransport.tokens, slotIndex, slotToken);
|
|
1286
|
+
Atomics.store(sabFrameTransport.states, slotIndex, FRAME_SAB_SLOT_STATE_READY);
|
|
1287
|
+
publishSabFrame(sabFrameTransport, frameSeq, slotIndex, slotToken, byteLen);
|
|
1288
|
+
if (frameAudit.enabled) {
|
|
1289
|
+
frameAudit.emit("frame.beginFrame.publish", {
|
|
1290
|
+
frameSeq,
|
|
1291
|
+
slotIndex,
|
|
1292
|
+
slotToken,
|
|
1293
|
+
byteLen,
|
|
1294
|
+
reclaimedReady: result.reclaimedReady,
|
|
1295
|
+
metrics: { ...beginFrameMetrics },
|
|
1296
|
+
});
|
|
1297
|
+
}
|
|
1298
|
+
Atomics.notify(sabFrameTransport.controlHeader, FRAME_SAB_CONTROL_PUBLISHED_SEQ_WORD, 1);
|
|
1299
|
+
return framePromise;
|
|
1300
|
+
},
|
|
1301
|
+
abort: () => {
|
|
1302
|
+
if (finalized)
|
|
1303
|
+
return;
|
|
1304
|
+
finalized = true;
|
|
1305
|
+
if (frameAudit.enabled) {
|
|
1306
|
+
frameAudit.emit("frame.beginFrame.abort", {
|
|
1307
|
+
slotIndex,
|
|
1308
|
+
});
|
|
1309
|
+
}
|
|
1310
|
+
Atomics.store(sabFrameTransport.tokens, slotIndex, 0);
|
|
1311
|
+
Atomics.store(sabFrameTransport.states, slotIndex, FRAME_SAB_SLOT_STATE_FREE);
|
|
1312
|
+
},
|
|
1313
|
+
};
|
|
1314
|
+
};
|
|
1063
1315
|
const out = Object.assign(backend, { debug, perf });
|
|
1064
1316
|
Object.defineProperties(out, {
|
|
1065
1317
|
[BACKEND_DRAWLIST_VERSION_MARKER]: {
|
|
@@ -1096,6 +1348,14 @@ export function createNodeBackendInternal(opts = {}) {
|
|
|
1096
1348
|
configurable: false,
|
|
1097
1349
|
},
|
|
1098
1350
|
});
|
|
1351
|
+
if (beginFrame !== null) {
|
|
1352
|
+
Object.defineProperty(out, BACKEND_BEGIN_FRAME_MARKER, {
|
|
1353
|
+
value: beginFrame,
|
|
1354
|
+
writable: false,
|
|
1355
|
+
enumerable: false,
|
|
1356
|
+
configurable: false,
|
|
1357
|
+
});
|
|
1358
|
+
}
|
|
1099
1359
|
return out;
|
|
1100
1360
|
}
|
|
1101
1361
|
//# sourceMappingURL=nodeBackend.js.map
|