@rezi-ui/node 0.1.0-alpha.6 → 0.1.0-alpha.60
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/README.md +26 -6
- package/dist/backend/backendSharedConfig.d.ts +11 -0
- package/dist/backend/backendSharedConfig.d.ts.map +1 -0
- package/dist/backend/backendSharedConfig.js +99 -0
- package/dist/backend/backendSharedConfig.js.map +1 -0
- package/dist/backend/backendSharedDebug.d.ts +4 -0
- package/dist/backend/backendSharedDebug.d.ts.map +1 -0
- package/dist/backend/backendSharedDebug.js +29 -0
- package/dist/backend/backendSharedDebug.js.map +1 -0
- package/dist/backend/backendSharedMarkers.d.ts +10 -0
- package/dist/backend/backendSharedMarkers.d.ts.map +1 -0
- package/dist/backend/backendSharedMarkers.js +50 -0
- package/dist/backend/backendSharedMarkers.js.map +1 -0
- package/dist/backend/emojiWidthPolicy.d.ts +15 -0
- package/dist/backend/emojiWidthPolicy.d.ts.map +1 -0
- package/dist/backend/emojiWidthPolicy.js +229 -0
- package/dist/backend/emojiWidthPolicy.js.map +1 -0
- package/dist/backend/nodeBackend.d.ts +30 -2
- package/dist/backend/nodeBackend.d.ts.map +1 -1
- package/dist/backend/nodeBackend.js +418 -66
- package/dist/backend/nodeBackend.js.map +1 -1
- package/dist/backend/nodeBackendInline.d.ts.map +1 -1
- package/dist/backend/nodeBackendInline.js +131 -55
- package/dist/backend/nodeBackendInline.js.map +1 -1
- package/dist/backend/terminalProfile.d.ts +5 -0
- package/dist/backend/terminalProfile.d.ts.map +1 -0
- package/dist/backend/terminalProfile.js +117 -0
- package/dist/backend/terminalProfile.js.map +1 -0
- package/dist/dev/hotStateReload.d.ts +65 -0
- package/dist/dev/hotStateReload.d.ts.map +1 -0
- package/dist/dev/hotStateReload.js +438 -0
- package/dist/dev/hotStateReload.js.map +1 -0
- package/dist/dev/nodeAppHotReload.d.ts +11 -0
- package/dist/dev/nodeAppHotReload.d.ts.map +1 -0
- package/dist/dev/nodeAppHotReload.js +78 -0
- package/dist/dev/nodeAppHotReload.js.map +1 -0
- 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/image.d.ts +4 -0
- package/dist/image.d.ts.map +1 -0
- package/dist/image.js +43 -0
- package/dist/image.js.map +1 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +198 -0
- package/dist/index.js.map +1 -1
- package/dist/repro/index.d.ts +3 -0
- package/dist/repro/index.d.ts.map +1 -0
- package/dist/repro/index.js +2 -0
- package/dist/repro/index.js.map +1 -0
- package/dist/repro/recorder.d.ts +30 -0
- package/dist/repro/recorder.d.ts.map +1 -0
- package/dist/repro/recorder.js +321 -0
- package/dist/repro/recorder.js.map +1 -0
- package/dist/streams/tail.d.ts +6 -0
- package/dist/streams/tail.d.ts.map +1 -0
- package/dist/streams/tail.js +113 -0
- package/dist/streams/tail.js.map +1 -0
- package/dist/worker/engineWorker.js +487 -14
- package/dist/worker/engineWorker.js.map +1 -1
- package/dist/worker/protocol.d.ts +3 -0
- package/dist/worker/protocol.d.ts.map +1 -1
- package/dist/worker/testShims/invalidPollBytesNative.d.ts +22 -0
- package/dist/worker/testShims/invalidPollBytesNative.d.ts.map +1 -0
- package/dist/worker/testShims/invalidPollBytesNative.js +65 -0
- package/dist/worker/testShims/invalidPollBytesNative.js.map +1 -0
- package/dist/worker/testShims/limitsExpectNative.d.ts +22 -0
- package/dist/worker/testShims/limitsExpectNative.d.ts.map +1 -0
- package/dist/worker/testShims/limitsExpectNative.js +85 -0
- package/dist/worker/testShims/limitsExpectNative.js.map +1 -0
- package/dist/worker/testShims/limitsNative.d.ts +22 -0
- package/dist/worker/testShims/limitsNative.d.ts.map +1 -0
- package/dist/worker/testShims/limitsNative.js +90 -0
- package/dist/worker/testShims/limitsNative.js.map +1 -0
- package/dist/worker/tickTiming.d.ts +7 -0
- package/dist/worker/tickTiming.d.ts.map +1 -0
- package/dist/worker/tickTiming.js +26 -0
- package/dist/worker/tickTiming.js.map +1 -0
- package/package.json +12 -8
- package/dist/__e2e__/fixtures/terminal-app.d.ts +0 -2
- package/dist/__e2e__/fixtures/terminal-app.d.ts.map +0 -1
- package/dist/__e2e__/fixtures/terminal-app.js +0 -42
- package/dist/__e2e__/fixtures/terminal-app.js.map +0 -1
- package/dist/__e2e__/terminal-render.e2e.test.d.ts +0 -2
- package/dist/__e2e__/terminal-render.e2e.test.d.ts.map +0 -1
- package/dist/__e2e__/terminal-render.e2e.test.js +0 -125
- package/dist/__e2e__/terminal-render.e2e.test.js.map +0 -1
- package/dist/__tests__/worker_integration.test.d.ts +0 -2
- package/dist/__tests__/worker_integration.test.d.ts.map +0 -1
- package/dist/__tests__/worker_integration.test.js +0 -569
- package/dist/__tests__/worker_integration.test.js.map +0 -1
|
@@ -6,11 +6,19 @@
|
|
|
6
6
|
* @see docs/dev/style-guide.md
|
|
7
7
|
* @see docs/backend/native.md
|
|
8
8
|
*/
|
|
9
|
+
import { existsSync } from "node:fs";
|
|
9
10
|
import { Worker } from "node:worker_threads";
|
|
10
11
|
import { DEFAULT_TERMINAL_CAPS, FRAME_ACCEPTED_ACK_MARKER } from "@rezi-ui/core";
|
|
11
|
-
import { ZR_DRAWLIST_VERSION_V1,
|
|
12
|
+
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";
|
|
13
|
+
import { createFrameAuditLogger, drawlistFingerprint, maybeDumpDrawlistBytes, } from "../frameAudit.js";
|
|
12
14
|
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";
|
|
15
|
+
import { DEFAULT_FPS_CAP, DEFAULT_MAX_EVENT_BYTES, MAX_SAFE_EVENT_BYTES, MAX_SAFE_FPS_CAP, normalizeBackendNativeConfig, parseBoundedPositiveIntOrThrow, parsePositiveInt, parsePositiveIntOr, resolveTargetFps, } from "./backendSharedConfig.js";
|
|
16
|
+
import { DEBUG_QUERY_DEFAULT_RECORDS, DEBUG_QUERY_MAX_RECORDS } from "./backendSharedDebug.js";
|
|
17
|
+
import { attachBackendMarkers } from "./backendSharedMarkers.js";
|
|
18
|
+
import { applyEmojiWidthPolicy, resolveBackendEmojiWidthPolicy } from "./emojiWidthPolicy.js";
|
|
13
19
|
import { createNodeBackendInlineInternal } from "./nodeBackendInline.js";
|
|
20
|
+
import { terminalProfileFromNodeEnv } from "./terminalProfile.js";
|
|
21
|
+
const WIDTH_POLICY_KEY = "widthPolicy";
|
|
14
22
|
function deferred() {
|
|
15
23
|
let resolve;
|
|
16
24
|
let reject;
|
|
@@ -20,37 +28,48 @@ function deferred() {
|
|
|
20
28
|
});
|
|
21
29
|
return { promise, resolve, reject };
|
|
22
30
|
}
|
|
23
|
-
function
|
|
24
|
-
|
|
25
|
-
return fallback;
|
|
26
|
-
if (!Number.isFinite(n))
|
|
27
|
-
return fallback;
|
|
28
|
-
if (!Number.isInteger(n))
|
|
29
|
-
return fallback;
|
|
30
|
-
if (n <= 0)
|
|
31
|
-
return fallback;
|
|
32
|
-
return n;
|
|
31
|
+
function safeErr(err) {
|
|
32
|
+
return err instanceof Error ? err : new Error(String(err));
|
|
33
33
|
}
|
|
34
|
-
function
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (
|
|
38
|
-
return
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
34
|
+
function resolveWorkerEntry(workerData) {
|
|
35
|
+
const options = { workerData };
|
|
36
|
+
const workerEntryJs = new URL("../worker/engineWorker.js", import.meta.url);
|
|
37
|
+
if (existsSync(workerEntryJs)) {
|
|
38
|
+
return { entry: workerEntryJs, options };
|
|
39
|
+
}
|
|
40
|
+
// Source-mode worktrees do not emit sibling .js worker files under src.
|
|
41
|
+
// Use a JS bootstrap that registers tsx and then imports engineWorker.ts.
|
|
42
|
+
const workerEntryBootstrapJs = new URL("../worker/engineWorker.bootstrap.js", import.meta.url);
|
|
43
|
+
if (existsSync(workerEntryBootstrapJs)) {
|
|
44
|
+
return { entry: workerEntryBootstrapJs, options };
|
|
45
|
+
}
|
|
46
|
+
throw new ZrUiError("ZRUI_BACKEND_ERROR", "Unable to locate worker entry (expected engineWorker.js or engineWorker.bootstrap.js)");
|
|
44
47
|
}
|
|
45
|
-
function
|
|
46
|
-
|
|
47
|
-
return parsePositiveInt(targetFpsCfg.targetFps) ?? parsePositiveInt(targetFpsCfg.target_fps);
|
|
48
|
+
function hasInteractiveTty() {
|
|
49
|
+
return (process.stdin.isTTY === true || process.stdout.isTTY === true || process.stderr.isTTY === true);
|
|
48
50
|
}
|
|
49
|
-
function
|
|
50
|
-
|
|
51
|
+
export function selectNodeBackendExecutionMode(input) {
|
|
52
|
+
const { requestedExecutionMode, fpsCap } = input;
|
|
53
|
+
const resolvedExecutionMode = requestedExecutionMode === "inline"
|
|
54
|
+
? "inline"
|
|
55
|
+
: requestedExecutionMode === "worker"
|
|
56
|
+
? "worker"
|
|
57
|
+
: fpsCap <= 30
|
|
58
|
+
? "inline"
|
|
59
|
+
: "worker";
|
|
60
|
+
return {
|
|
61
|
+
resolvedExecutionMode,
|
|
62
|
+
selectedExecutionMode: resolvedExecutionMode,
|
|
63
|
+
fallbackReason: null,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function assertWorkerEnvironmentSupported(nativeShimModule) {
|
|
67
|
+
if (nativeShimModule !== undefined)
|
|
68
|
+
return;
|
|
69
|
+
if (hasInteractiveTty())
|
|
70
|
+
return;
|
|
71
|
+
throw new ZrUiError("ZRUI_BACKEND_ERROR", 'Worker backend requires a TTY when using @rezi-ui/native. Use `executionMode: "inline"` for headless runs or pass `nativeShimModule` in test harnesses.');
|
|
51
72
|
}
|
|
52
|
-
const DEBUG_QUERY_DEFAULT_RECORDS = 4096;
|
|
53
|
-
const DEBUG_QUERY_MAX_RECORDS = 16384;
|
|
54
73
|
const FRAME_SAB_SLOT_COUNT_DEFAULT = 8;
|
|
55
74
|
const FRAME_SAB_SLOT_BYTES_DEFAULT = 1 << 20;
|
|
56
75
|
function copyInto(buf, bytes) {
|
|
@@ -124,6 +143,38 @@ function acquireSabSlot(t) {
|
|
|
124
143
|
}
|
|
125
144
|
return -1;
|
|
126
145
|
}
|
|
146
|
+
function acquireSabSlotTracked(t) {
|
|
147
|
+
const start = t.nextSlot.value % t.slotCount;
|
|
148
|
+
for (let i = 0; i < t.slotCount; i++) {
|
|
149
|
+
const slot = (start + i) % t.slotCount;
|
|
150
|
+
const prev = Atomics.compareExchange(t.states, slot, FRAME_SAB_SLOT_STATE_FREE, FRAME_SAB_SLOT_STATE_WRITING);
|
|
151
|
+
if (prev === FRAME_SAB_SLOT_STATE_FREE) {
|
|
152
|
+
t.nextSlot.value = (slot + 1) % t.slotCount;
|
|
153
|
+
return { slotIndex: slot, reclaimedReady: false };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
for (let i = 0; i < t.slotCount; i++) {
|
|
157
|
+
const slot = (start + i) % t.slotCount;
|
|
158
|
+
const prev = Atomics.compareExchange(t.states, slot, FRAME_SAB_SLOT_STATE_READY, FRAME_SAB_SLOT_STATE_WRITING);
|
|
159
|
+
if (prev === FRAME_SAB_SLOT_STATE_READY) {
|
|
160
|
+
t.nextSlot.value = (slot + 1) % t.slotCount;
|
|
161
|
+
return { slotIndex: slot, reclaimedReady: true };
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return { slotIndex: -1, reclaimedReady: false };
|
|
165
|
+
}
|
|
166
|
+
function acquireSabFreeSlot(t) {
|
|
167
|
+
const start = t.nextSlot.value % t.slotCount;
|
|
168
|
+
for (let i = 0; i < t.slotCount; i++) {
|
|
169
|
+
const slot = (start + i) % t.slotCount;
|
|
170
|
+
const prev = Atomics.compareExchange(t.states, slot, FRAME_SAB_SLOT_STATE_FREE, FRAME_SAB_SLOT_STATE_WRITING);
|
|
171
|
+
if (prev === FRAME_SAB_SLOT_STATE_FREE) {
|
|
172
|
+
t.nextSlot.value = (slot + 1) % t.slotCount;
|
|
173
|
+
return slot;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return -1;
|
|
177
|
+
}
|
|
127
178
|
function publishSabFrame(t, frameSeq, slotIndex, slotToken, byteLen) {
|
|
128
179
|
Atomics.store(t.controlHeader, FRAME_SAB_CONTROL_PUBLISHED_SLOT_WORD, slotIndex);
|
|
129
180
|
Atomics.store(t.controlHeader, FRAME_SAB_CONTROL_PUBLISHED_BYTES_WORD, byteLen);
|
|
@@ -131,21 +182,30 @@ function publishSabFrame(t, frameSeq, slotIndex, slotToken, byteLen) {
|
|
|
131
182
|
Atomics.store(t.controlHeader, FRAME_SAB_CONTROL_PUBLISHED_SEQ_WORD, frameSeq);
|
|
132
183
|
}
|
|
133
184
|
export function createNodeBackendInternal(opts = {}) {
|
|
185
|
+
const frameAudit = createFrameAuditLogger("backend");
|
|
134
186
|
const cfg = opts.config ?? {};
|
|
135
|
-
const fpsCap =
|
|
187
|
+
const fpsCap = parseBoundedPositiveIntOrThrow("fpsCap", cfg.fpsCap, DEFAULT_FPS_CAP, MAX_SAFE_FPS_CAP);
|
|
136
188
|
const requestedExecutionMode = cfg.executionMode ?? "auto";
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
189
|
+
const executionModeSelection = selectNodeBackendExecutionMode({
|
|
190
|
+
requestedExecutionMode,
|
|
191
|
+
fpsCap,
|
|
192
|
+
hasAnyTty: hasInteractiveTty(),
|
|
193
|
+
...(opts.nativeShimModule === undefined ? {} : { nativeShimModule: opts.nativeShimModule }),
|
|
194
|
+
});
|
|
195
|
+
const executionMode = executionModeSelection.selectedExecutionMode;
|
|
196
|
+
if (executionModeSelection.fallbackReason !== null && frameAudit.enabled) {
|
|
197
|
+
frameAudit.emit("backend.executionModeFallback", {
|
|
198
|
+
requestedExecutionMode,
|
|
199
|
+
resolvedExecutionMode: executionModeSelection.resolvedExecutionMode,
|
|
200
|
+
selectedExecutionMode: executionMode,
|
|
201
|
+
reason: executionModeSelection.fallbackReason,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
144
204
|
if (executionMode === "inline") {
|
|
145
205
|
return createNodeBackendInlineInternal(opts);
|
|
146
206
|
}
|
|
147
|
-
const
|
|
148
|
-
const
|
|
207
|
+
const requestedDrawlistVersion = ZR_DRAWLIST_VERSION_V1;
|
|
208
|
+
const maxEventBytes = parseBoundedPositiveIntOrThrow("maxEventBytes", cfg.maxEventBytes, DEFAULT_MAX_EVENT_BYTES, MAX_SAFE_EVENT_BYTES);
|
|
149
209
|
const frameTransportMode = cfg.frameTransport === "transfer" || cfg.frameTransport === "sab" ? cfg.frameTransport : "auto";
|
|
150
210
|
const frameSabSlotCount = parsePositiveIntOr(cfg.frameSabSlotCount, FRAME_SAB_SLOT_COUNT_DEFAULT);
|
|
151
211
|
const frameSabSlotBytes = parsePositiveIntOr(cfg.frameSabSlotBytes, FRAME_SAB_SLOT_BYTES_DEFAULT);
|
|
@@ -165,28 +225,24 @@ export function createNodeBackendInternal(opts = {}) {
|
|
|
165
225
|
kind: FRAME_TRANSPORT_TRANSFER_V1,
|
|
166
226
|
version: FRAME_TRANSPORT_VERSION,
|
|
167
227
|
};
|
|
168
|
-
const nativeConfig =
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
? cfg.nativeConfig
|
|
172
|
-
: Object.freeze({});
|
|
173
|
-
const nativeTargetFps = readNativeTargetFps(nativeConfig) ?? fpsCap;
|
|
174
|
-
const initConfig = {
|
|
228
|
+
const nativeConfig = normalizeBackendNativeConfig(cfg.nativeConfig);
|
|
229
|
+
const nativeTargetFps = resolveTargetFps(fpsCap, nativeConfig);
|
|
230
|
+
const initConfigBase = {
|
|
175
231
|
...nativeConfig,
|
|
176
|
-
//
|
|
177
|
-
// explicitly overridden in nativeConfig.
|
|
232
|
+
// fpsCap is the single frame-scheduling knob; native target fps must align.
|
|
178
233
|
targetFps: nativeTargetFps,
|
|
179
234
|
// Negotiation pins (docs/16 + docs/01)
|
|
180
235
|
requestedEngineAbiMajor: ZR_ENGINE_ABI_MAJOR,
|
|
181
236
|
requestedEngineAbiMinor: ZR_ENGINE_ABI_MINOR,
|
|
182
237
|
requestedEngineAbiPatch: ZR_ENGINE_ABI_PATCH,
|
|
183
|
-
requestedDrawlistVersion:
|
|
238
|
+
requestedDrawlistVersion: requestedDrawlistVersion,
|
|
184
239
|
requestedEventBatchVersion: ZR_EVENT_BATCH_VERSION_V1,
|
|
185
240
|
// Node worker runtime caps
|
|
186
241
|
fpsCap,
|
|
187
242
|
maxEventBytes,
|
|
188
243
|
frameTransport: frameTransportWire,
|
|
189
244
|
};
|
|
245
|
+
let initConfigResolved = null;
|
|
190
246
|
let worker = null;
|
|
191
247
|
let disposed = false;
|
|
192
248
|
let started = false;
|
|
@@ -199,6 +255,7 @@ export function createNodeBackendInternal(opts = {}) {
|
|
|
199
255
|
let nextFrameSeq = 1;
|
|
200
256
|
const frameAcceptedWaiters = new Map();
|
|
201
257
|
const frameCompletionWaiters = new Map();
|
|
258
|
+
const frameAuditBySeq = new Map();
|
|
202
259
|
const eventQueue = [];
|
|
203
260
|
const eventWaiters = [];
|
|
204
261
|
const capsWaiters = [];
|
|
@@ -248,10 +305,85 @@ export function createNodeBackendInternal(opts = {}) {
|
|
|
248
305
|
waiter.reject(err);
|
|
249
306
|
}
|
|
250
307
|
frameCompletionWaiters.clear();
|
|
308
|
+
if (frameAudit.enabled) {
|
|
309
|
+
for (const [seq, meta] of frameAuditBySeq.entries()) {
|
|
310
|
+
frameAudit.emit("frame.aborted", {
|
|
311
|
+
reason: err.message,
|
|
312
|
+
ageMs: Math.max(0, Date.now() - meta.submitAtMs),
|
|
313
|
+
...meta,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
frameAuditBySeq.clear();
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
function registerFrameAudit(frameSeq, submitPath, transport, bytes, slotIndex, slotToken) {
|
|
320
|
+
if (!frameAudit.enabled)
|
|
321
|
+
return;
|
|
322
|
+
const fp = drawlistFingerprint(bytes);
|
|
323
|
+
const meta = {
|
|
324
|
+
frameSeq,
|
|
325
|
+
submitAtMs: Date.now(),
|
|
326
|
+
submitPath,
|
|
327
|
+
transport,
|
|
328
|
+
byteLen: fp.byteLen,
|
|
329
|
+
hash32: fp.hash32,
|
|
330
|
+
prefixHash32: fp.prefixHash32,
|
|
331
|
+
cmdCount: fp.cmdCount,
|
|
332
|
+
totalSize: fp.totalSize,
|
|
333
|
+
head16: fp.head16,
|
|
334
|
+
tail16: fp.tail16,
|
|
335
|
+
...(slotIndex === undefined ? {} : { slotIndex }),
|
|
336
|
+
...(slotToken === undefined ? {} : { slotToken }),
|
|
337
|
+
};
|
|
338
|
+
frameAuditBySeq.set(frameSeq, meta);
|
|
339
|
+
maybeDumpDrawlistBytes("backend", submitPath, frameSeq, bytes);
|
|
340
|
+
frameAudit.emit("frame.submitted", meta);
|
|
341
|
+
}
|
|
342
|
+
function markAcceptedFramesUpTo(acceptedSeq) {
|
|
343
|
+
if (!frameAudit.enabled)
|
|
344
|
+
return;
|
|
345
|
+
for (const [seq, meta] of frameAuditBySeq.entries()) {
|
|
346
|
+
if (seq > acceptedSeq)
|
|
347
|
+
continue;
|
|
348
|
+
if (meta.acceptedLogged === true)
|
|
349
|
+
continue;
|
|
350
|
+
frameAudit.emit("frame.accepted", {
|
|
351
|
+
acceptedSeq,
|
|
352
|
+
ageMs: Math.max(0, Date.now() - meta.submitAtMs),
|
|
353
|
+
...meta,
|
|
354
|
+
});
|
|
355
|
+
meta.acceptedLogged = true;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
function markCoalescedFramesBefore(acceptedSeq) {
|
|
359
|
+
if (!frameAudit.enabled)
|
|
360
|
+
return;
|
|
361
|
+
for (const [seq, meta] of frameAuditBySeq.entries()) {
|
|
362
|
+
if (seq >= acceptedSeq)
|
|
363
|
+
continue;
|
|
364
|
+
frameAudit.emit("frame.coalesced", {
|
|
365
|
+
acceptedSeq,
|
|
366
|
+
ageMs: Math.max(0, Date.now() - meta.submitAtMs),
|
|
367
|
+
...meta,
|
|
368
|
+
});
|
|
369
|
+
frameAuditBySeq.delete(seq);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
function markCompletedFrame(frameSeq, completedResult) {
|
|
373
|
+
if (!frameAudit.enabled)
|
|
374
|
+
return;
|
|
375
|
+
const meta = frameAuditBySeq.get(frameSeq);
|
|
376
|
+
frameAudit.emit("frame.completed", {
|
|
377
|
+
completedResult,
|
|
378
|
+
ageMs: meta ? Math.max(0, Date.now() - meta.submitAtMs) : null,
|
|
379
|
+
...(meta ?? {}),
|
|
380
|
+
});
|
|
381
|
+
frameAuditBySeq.delete(frameSeq);
|
|
251
382
|
}
|
|
252
383
|
function resolveAcceptedFramesUpTo(acceptedSeq) {
|
|
253
384
|
if (!Number.isInteger(acceptedSeq) || acceptedSeq <= 0)
|
|
254
385
|
return;
|
|
386
|
+
markAcceptedFramesUpTo(acceptedSeq);
|
|
255
387
|
for (const [seq, waiter] of frameAcceptedWaiters.entries()) {
|
|
256
388
|
if (seq > acceptedSeq)
|
|
257
389
|
continue;
|
|
@@ -262,6 +394,7 @@ export function createNodeBackendInternal(opts = {}) {
|
|
|
262
394
|
function resolveCoalescedCompletionFramesUpTo(acceptedSeq) {
|
|
263
395
|
if (!Number.isInteger(acceptedSeq) || acceptedSeq <= 0)
|
|
264
396
|
return;
|
|
397
|
+
markCoalescedFramesBefore(acceptedSeq);
|
|
265
398
|
for (const [seq, waiter] of frameCompletionWaiters.entries()) {
|
|
266
399
|
if (seq >= acceptedSeq)
|
|
267
400
|
continue;
|
|
@@ -270,6 +403,7 @@ export function createNodeBackendInternal(opts = {}) {
|
|
|
270
403
|
}
|
|
271
404
|
}
|
|
272
405
|
function settleCompletedFrame(frameSeq, completedResult) {
|
|
406
|
+
markCompletedFrame(frameSeq, completedResult);
|
|
273
407
|
const waiter = frameCompletionWaiters.get(frameSeq);
|
|
274
408
|
if (waiter === undefined)
|
|
275
409
|
return;
|
|
@@ -280,6 +414,26 @@ export function createNodeBackendInternal(opts = {}) {
|
|
|
280
414
|
}
|
|
281
415
|
waiter.resolve(undefined);
|
|
282
416
|
}
|
|
417
|
+
function reserveFramePromise(frameSeq) {
|
|
418
|
+
const frameAcceptedDef = deferred();
|
|
419
|
+
frameAcceptedWaiters.set(frameSeq, frameAcceptedDef);
|
|
420
|
+
const frameCompletionDef = deferred();
|
|
421
|
+
frameCompletionWaiters.set(frameSeq, frameCompletionDef);
|
|
422
|
+
const framePromise = frameCompletionDef.promise;
|
|
423
|
+
Object.defineProperty(framePromise, FRAME_ACCEPTED_ACK_MARKER, {
|
|
424
|
+
value: frameAcceptedDef.promise,
|
|
425
|
+
configurable: false,
|
|
426
|
+
enumerable: false,
|
|
427
|
+
writable: false,
|
|
428
|
+
});
|
|
429
|
+
return framePromise;
|
|
430
|
+
}
|
|
431
|
+
function releaseFrameReservation(frameSeq) {
|
|
432
|
+
frameAcceptedWaiters.delete(frameSeq);
|
|
433
|
+
frameCompletionWaiters.delete(frameSeq);
|
|
434
|
+
if (frameAudit.enabled)
|
|
435
|
+
frameAuditBySeq.delete(frameSeq);
|
|
436
|
+
}
|
|
283
437
|
function failAll(err) {
|
|
284
438
|
while (eventWaiters.length > 0)
|
|
285
439
|
eventWaiters.shift()?.reject(err);
|
|
@@ -339,6 +493,13 @@ export function createNodeBackendInternal(opts = {}) {
|
|
|
339
493
|
return;
|
|
340
494
|
}
|
|
341
495
|
case "frameStatus": {
|
|
496
|
+
if (frameAudit.enabled) {
|
|
497
|
+
frameAudit.emit("worker.frameStatus", {
|
|
498
|
+
acceptedSeq: msg.acceptedSeq,
|
|
499
|
+
completedSeq: msg.completedSeq ?? null,
|
|
500
|
+
completedResult: msg.completedResult ?? null,
|
|
501
|
+
});
|
|
502
|
+
}
|
|
342
503
|
if (!Number.isInteger(msg.acceptedSeq) || msg.acceptedSeq <= 0) {
|
|
343
504
|
fatal = new ZrUiError("ZRUI_BACKEND_ERROR", `invalid frameStatus.acceptedSeq: ${String(msg.acceptedSeq)}`);
|
|
344
505
|
failAll(fatal);
|
|
@@ -369,6 +530,26 @@ export function createNodeBackendInternal(opts = {}) {
|
|
|
369
530
|
return;
|
|
370
531
|
}
|
|
371
532
|
case "events": {
|
|
533
|
+
if (!Number.isInteger(msg.byteLen) || msg.byteLen < 0) {
|
|
534
|
+
fatal = new ZrUiError("ZRUI_BACKEND_ERROR", `events: invalid byteLen=${String(msg.byteLen)}`);
|
|
535
|
+
failAll(fatal);
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
if (msg.byteLen > msg.batch.byteLength) {
|
|
539
|
+
fatal = new ZrUiError("ZRUI_BACKEND_ERROR", `events: byteLen=${String(msg.byteLen)} exceeds batch.byteLength=${String(msg.batch.byteLength)}`);
|
|
540
|
+
failAll(fatal);
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
if (msg.byteLen > maxEventBytes) {
|
|
544
|
+
fatal = new ZrUiError("ZRUI_BACKEND_ERROR", `events: byteLen=${String(msg.byteLen)} exceeds maxEventBytes=${String(maxEventBytes)}`);
|
|
545
|
+
failAll(fatal);
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
if (!Number.isInteger(msg.droppedSinceLast) || msg.droppedSinceLast < 0) {
|
|
549
|
+
fatal = new ZrUiError("ZRUI_BACKEND_ERROR", `events: invalid droppedSinceLast=${String(msg.droppedSinceLast)}`);
|
|
550
|
+
failAll(fatal);
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
372
553
|
const waiter = eventWaiters.shift();
|
|
373
554
|
if (waiter !== undefined) {
|
|
374
555
|
const buf = msg.batch;
|
|
@@ -526,6 +707,9 @@ export function createNodeBackendInternal(opts = {}) {
|
|
|
526
707
|
supportsScrollRegion: msg.supportsScrollRegion,
|
|
527
708
|
supportsCursorShape: msg.supportsCursorShape,
|
|
528
709
|
supportsOutputWaitWritable: msg.supportsOutputWaitWritable,
|
|
710
|
+
supportsUnderlineStyles: msg.supportsUnderlineStyles,
|
|
711
|
+
supportsColoredUnderlines: msg.supportsColoredUnderlines,
|
|
712
|
+
supportsHyperlinks: msg.supportsHyperlinks,
|
|
529
713
|
sgrAttrsSupported: msg.sgrAttrsSupported,
|
|
530
714
|
};
|
|
531
715
|
cachedCaps = caps;
|
|
@@ -568,7 +752,23 @@ export function createNodeBackendInternal(opts = {}) {
|
|
|
568
752
|
throw fatal;
|
|
569
753
|
if (started)
|
|
570
754
|
return;
|
|
755
|
+
assertWorkerEnvironmentSupported(opts.nativeShimModule);
|
|
571
756
|
if (worker === null) {
|
|
757
|
+
if (initConfigResolved === null) {
|
|
758
|
+
const resolvedEmojiWidthPolicy = await resolveBackendEmojiWidthPolicy(cfg.emojiWidthPolicy, nativeConfig);
|
|
759
|
+
const nativeWidthPolicy = applyEmojiWidthPolicy(resolvedEmojiWidthPolicy);
|
|
760
|
+
initConfigResolved = {
|
|
761
|
+
...initConfigBase,
|
|
762
|
+
widthPolicy: nativeWidthPolicy,
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
else {
|
|
766
|
+
// Keep core measurement policy deterministic across stop/start cycles.
|
|
767
|
+
const widthPolicy = initConfigResolved[WIDTH_POLICY_KEY];
|
|
768
|
+
if (typeof widthPolicy === "number") {
|
|
769
|
+
setTextMeasureEmojiPolicy(widthPolicy === 0 ? "narrow" : "wide");
|
|
770
|
+
}
|
|
771
|
+
}
|
|
572
772
|
startDef = deferred();
|
|
573
773
|
startSettled = false;
|
|
574
774
|
stopDef = null;
|
|
@@ -576,11 +776,19 @@ export function createNodeBackendInternal(opts = {}) {
|
|
|
576
776
|
stopRequested = false;
|
|
577
777
|
if (sabFrameTransport !== null)
|
|
578
778
|
resetSabFrameTransport(sabFrameTransport);
|
|
579
|
-
const entry = new URL("../worker/engineWorker.js", import.meta.url);
|
|
580
779
|
const workerData = opts.nativeShimModule === undefined
|
|
581
780
|
? undefined
|
|
582
781
|
: { nativeShimModule: opts.nativeShimModule };
|
|
583
|
-
|
|
782
|
+
const workerEntry = resolveWorkerEntry(workerData);
|
|
783
|
+
worker = new Worker(workerEntry.entry, workerEntry.options);
|
|
784
|
+
if (frameAudit.enabled) {
|
|
785
|
+
frameAudit.emit("worker.spawn", {
|
|
786
|
+
frameTransport: frameTransportWire.kind,
|
|
787
|
+
frameSabSlotCount: frameSabSlotCount,
|
|
788
|
+
frameSabSlotBytes: frameSabSlotBytes,
|
|
789
|
+
workerEntry: workerEntry.entry.href,
|
|
790
|
+
});
|
|
791
|
+
}
|
|
584
792
|
exitDef = deferred();
|
|
585
793
|
worker.on("message", handleWorkerMessage);
|
|
586
794
|
worker.on("error", (err) => {
|
|
@@ -592,11 +800,27 @@ export function createNodeBackendInternal(opts = {}) {
|
|
|
592
800
|
worker.on("exit", (code) => {
|
|
593
801
|
handleWorkerExit(code);
|
|
594
802
|
});
|
|
595
|
-
send({ type: "init", config:
|
|
803
|
+
send({ type: "init", config: initConfigResolved });
|
|
596
804
|
}
|
|
597
805
|
if (startDef === null)
|
|
598
806
|
throw new Error("NodeBackend: invariant violated (startDef is null)");
|
|
599
|
-
|
|
807
|
+
try {
|
|
808
|
+
await startDef.promise;
|
|
809
|
+
}
|
|
810
|
+
catch (err) {
|
|
811
|
+
// Startup fatals can race with worker teardown. Waiting for exit keeps
|
|
812
|
+
// caller shutdown paths deterministic and avoids process-level teardown
|
|
813
|
+
// races when user code exits immediately after a start() rejection.
|
|
814
|
+
if (exitDef !== null) {
|
|
815
|
+
try {
|
|
816
|
+
await exitDef.promise;
|
|
817
|
+
}
|
|
818
|
+
catch {
|
|
819
|
+
// ignore teardown wait failures
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
throw err;
|
|
823
|
+
}
|
|
600
824
|
},
|
|
601
825
|
async stop() {
|
|
602
826
|
if (disposed)
|
|
@@ -655,37 +879,44 @@ export function createNodeBackendInternal(opts = {}) {
|
|
|
655
879
|
if (worker === null)
|
|
656
880
|
return Promise.reject(new Error("NodeBackend: worker not available"));
|
|
657
881
|
const frameSeq = nextFrameSeq++;
|
|
658
|
-
const
|
|
659
|
-
frameAcceptedWaiters.set(frameSeq, frameAcceptedDef);
|
|
660
|
-
const frameCompletionDef = deferred();
|
|
661
|
-
frameCompletionWaiters.set(frameSeq, frameCompletionDef);
|
|
662
|
-
const framePromise = frameCompletionDef.promise;
|
|
663
|
-
Object.defineProperty(framePromise, FRAME_ACCEPTED_ACK_MARKER, {
|
|
664
|
-
value: frameAcceptedDef.promise,
|
|
665
|
-
configurable: false,
|
|
666
|
-
enumerable: false,
|
|
667
|
-
writable: false,
|
|
668
|
-
});
|
|
882
|
+
const framePromise = reserveFramePromise(frameSeq);
|
|
669
883
|
if (sabFrameTransport !== null && drawlist.byteLength <= sabFrameTransport.slotBytes) {
|
|
670
884
|
const slotIndex = acquireSabSlot(sabFrameTransport);
|
|
671
885
|
if (slotIndex >= 0) {
|
|
672
886
|
const slotToken = frameSeqToSlotToken(frameSeq);
|
|
887
|
+
registerFrameAudit(frameSeq, "requestFrame", FRAME_TRANSPORT_SAB_V1, drawlist, slotIndex, slotToken);
|
|
673
888
|
const slotOffset = slotIndex * sabFrameTransport.slotBytes;
|
|
674
889
|
sabFrameTransport.dataBytes.set(drawlist, slotOffset);
|
|
675
890
|
Atomics.store(sabFrameTransport.tokens, slotIndex, slotToken);
|
|
676
891
|
Atomics.store(sabFrameTransport.states, slotIndex, FRAME_SAB_SLOT_STATE_READY);
|
|
677
892
|
publishSabFrame(sabFrameTransport, frameSeq, slotIndex, slotToken, drawlist.byteLength);
|
|
893
|
+
if (frameAudit.enabled) {
|
|
894
|
+
frameAudit.emit("frame.sab.publish", {
|
|
895
|
+
frameSeq,
|
|
896
|
+
slotIndex,
|
|
897
|
+
slotToken,
|
|
898
|
+
byteLen: drawlist.byteLength,
|
|
899
|
+
});
|
|
900
|
+
}
|
|
678
901
|
// SAB consumers wake on futex notify instead of per-frame
|
|
679
902
|
// MessagePort frameKick round-trips.
|
|
680
903
|
Atomics.notify(sabFrameTransport.controlHeader, FRAME_SAB_CONTROL_PUBLISHED_SEQ_WORD, 1);
|
|
681
904
|
return framePromise;
|
|
682
905
|
}
|
|
906
|
+
if (frameAudit.enabled) {
|
|
907
|
+
frameAudit.emit("frame.sab.fallback_transfer", {
|
|
908
|
+
frameSeq,
|
|
909
|
+
byteLen: drawlist.byteLength,
|
|
910
|
+
reason: "no-slot-available",
|
|
911
|
+
});
|
|
912
|
+
}
|
|
683
913
|
}
|
|
684
914
|
// Transfer fallback participates in the same ACK model:
|
|
685
915
|
// - accepted ACK (hidden marker) can unblock app scheduling early
|
|
686
916
|
// - completion promise settles on worker completion/coalescing status
|
|
687
917
|
const buf = new ArrayBuffer(drawlist.byteLength);
|
|
688
918
|
copyInto(buf, drawlist);
|
|
919
|
+
registerFrameAudit(frameSeq, "requestFrame", FRAME_TRANSPORT_TRANSFER_V1, drawlist);
|
|
689
920
|
try {
|
|
690
921
|
send({
|
|
691
922
|
type: "frame",
|
|
@@ -696,10 +927,21 @@ export function createNodeBackendInternal(opts = {}) {
|
|
|
696
927
|
}, [buf]);
|
|
697
928
|
}
|
|
698
929
|
catch (err) {
|
|
699
|
-
|
|
700
|
-
|
|
930
|
+
releaseFrameReservation(frameSeq);
|
|
931
|
+
if (frameAudit.enabled) {
|
|
932
|
+
frameAudit.emit("frame.transfer.publish_error", {
|
|
933
|
+
frameSeq,
|
|
934
|
+
detail: safeErr(err).message,
|
|
935
|
+
});
|
|
936
|
+
}
|
|
701
937
|
return Promise.reject(safeErr(err));
|
|
702
938
|
}
|
|
939
|
+
if (frameAudit.enabled) {
|
|
940
|
+
frameAudit.emit("frame.transfer.publish", {
|
|
941
|
+
frameSeq,
|
|
942
|
+
byteLen: drawlist.byteLength,
|
|
943
|
+
});
|
|
944
|
+
}
|
|
703
945
|
return framePromise;
|
|
704
946
|
},
|
|
705
947
|
pollEvents() {
|
|
@@ -763,6 +1005,10 @@ export function createNodeBackendInternal(opts = {}) {
|
|
|
763
1005
|
send({ type: "getCaps" });
|
|
764
1006
|
return d.promise;
|
|
765
1007
|
},
|
|
1008
|
+
async getTerminalProfile() {
|
|
1009
|
+
const caps = await backend.getCaps();
|
|
1010
|
+
return terminalProfileFromNodeEnv(caps);
|
|
1011
|
+
},
|
|
766
1012
|
};
|
|
767
1013
|
const debug = {
|
|
768
1014
|
debugEnable: (config) => enqueueDebug(async () => {
|
|
@@ -937,6 +1183,112 @@ export function createNodeBackendInternal(opts = {}) {
|
|
|
937
1183
|
return snapshot;
|
|
938
1184
|
}),
|
|
939
1185
|
};
|
|
940
|
-
|
|
1186
|
+
const beginFrameMetrics = {
|
|
1187
|
+
success: 0,
|
|
1188
|
+
fallbackToRequestFrame: 0,
|
|
1189
|
+
readyReclaims: 0,
|
|
1190
|
+
};
|
|
1191
|
+
const beginFrame = sabFrameTransport === null
|
|
1192
|
+
? null
|
|
1193
|
+
: (minCapacity) => {
|
|
1194
|
+
if (disposed)
|
|
1195
|
+
return null;
|
|
1196
|
+
if (fatal !== null)
|
|
1197
|
+
return null;
|
|
1198
|
+
if (stopRequested || !started || worker === null)
|
|
1199
|
+
return null;
|
|
1200
|
+
const required = typeof minCapacity === "number" && Number.isInteger(minCapacity) && minCapacity > 0
|
|
1201
|
+
? minCapacity
|
|
1202
|
+
: 0;
|
|
1203
|
+
if (required > sabFrameTransport.slotBytes)
|
|
1204
|
+
return null;
|
|
1205
|
+
const result = acquireSabSlotTracked(sabFrameTransport);
|
|
1206
|
+
if (result.slotIndex < 0) {
|
|
1207
|
+
beginFrameMetrics.fallbackToRequestFrame++;
|
|
1208
|
+
if (frameAudit.enabled) {
|
|
1209
|
+
frameAudit.emit("frame.beginFrame.fallback", {
|
|
1210
|
+
reason: "no-slot-available",
|
|
1211
|
+
metrics: { ...beginFrameMetrics },
|
|
1212
|
+
});
|
|
1213
|
+
}
|
|
1214
|
+
return null;
|
|
1215
|
+
}
|
|
1216
|
+
if (result.reclaimedReady) {
|
|
1217
|
+
beginFrameMetrics.readyReclaims++;
|
|
1218
|
+
}
|
|
1219
|
+
beginFrameMetrics.success++;
|
|
1220
|
+
const slotIndex = result.slotIndex;
|
|
1221
|
+
const slotOffset = slotIndex * sabFrameTransport.slotBytes;
|
|
1222
|
+
const buf = sabFrameTransport.dataBytes.subarray(slotOffset, slotOffset + sabFrameTransport.slotBytes);
|
|
1223
|
+
let finalized = false;
|
|
1224
|
+
return {
|
|
1225
|
+
buf,
|
|
1226
|
+
commit: (byteLen) => {
|
|
1227
|
+
if (finalized) {
|
|
1228
|
+
return Promise.reject(new Error("NodeBackend: beginFrame writer already finalized"));
|
|
1229
|
+
}
|
|
1230
|
+
finalized = true;
|
|
1231
|
+
if (disposed) {
|
|
1232
|
+
Atomics.store(sabFrameTransport.tokens, slotIndex, 0);
|
|
1233
|
+
Atomics.store(sabFrameTransport.states, slotIndex, FRAME_SAB_SLOT_STATE_FREE);
|
|
1234
|
+
return Promise.reject(new Error("NodeBackend: disposed"));
|
|
1235
|
+
}
|
|
1236
|
+
if (fatal !== null) {
|
|
1237
|
+
Atomics.store(sabFrameTransport.tokens, slotIndex, 0);
|
|
1238
|
+
Atomics.store(sabFrameTransport.states, slotIndex, FRAME_SAB_SLOT_STATE_FREE);
|
|
1239
|
+
return Promise.reject(fatal);
|
|
1240
|
+
}
|
|
1241
|
+
if (stopRequested || !started || worker === null) {
|
|
1242
|
+
Atomics.store(sabFrameTransport.tokens, slotIndex, 0);
|
|
1243
|
+
Atomics.store(sabFrameTransport.states, slotIndex, FRAME_SAB_SLOT_STATE_FREE);
|
|
1244
|
+
return Promise.reject(new Error("NodeBackend: stopped"));
|
|
1245
|
+
}
|
|
1246
|
+
if (!Number.isInteger(byteLen) ||
|
|
1247
|
+
byteLen < 0 ||
|
|
1248
|
+
byteLen > sabFrameTransport.slotBytes) {
|
|
1249
|
+
Atomics.store(sabFrameTransport.tokens, slotIndex, 0);
|
|
1250
|
+
Atomics.store(sabFrameTransport.states, slotIndex, FRAME_SAB_SLOT_STATE_FREE);
|
|
1251
|
+
return Promise.reject(new Error("NodeBackend: beginFrame commit byteLen out of range"));
|
|
1252
|
+
}
|
|
1253
|
+
const frameSeq = nextFrameSeq++;
|
|
1254
|
+
const framePromise = reserveFramePromise(frameSeq);
|
|
1255
|
+
const slotToken = frameSeqToSlotToken(frameSeq);
|
|
1256
|
+
registerFrameAudit(frameSeq, "beginFrame", FRAME_TRANSPORT_SAB_V1, buf.subarray(0, byteLen), slotIndex, slotToken);
|
|
1257
|
+
Atomics.store(sabFrameTransport.tokens, slotIndex, slotToken);
|
|
1258
|
+
Atomics.store(sabFrameTransport.states, slotIndex, FRAME_SAB_SLOT_STATE_READY);
|
|
1259
|
+
publishSabFrame(sabFrameTransport, frameSeq, slotIndex, slotToken, byteLen);
|
|
1260
|
+
if (frameAudit.enabled) {
|
|
1261
|
+
frameAudit.emit("frame.beginFrame.publish", {
|
|
1262
|
+
frameSeq,
|
|
1263
|
+
slotIndex,
|
|
1264
|
+
slotToken,
|
|
1265
|
+
byteLen,
|
|
1266
|
+
reclaimedReady: result.reclaimedReady,
|
|
1267
|
+
metrics: { ...beginFrameMetrics },
|
|
1268
|
+
});
|
|
1269
|
+
}
|
|
1270
|
+
Atomics.notify(sabFrameTransport.controlHeader, FRAME_SAB_CONTROL_PUBLISHED_SEQ_WORD, 1);
|
|
1271
|
+
return framePromise;
|
|
1272
|
+
},
|
|
1273
|
+
abort: () => {
|
|
1274
|
+
if (finalized)
|
|
1275
|
+
return;
|
|
1276
|
+
finalized = true;
|
|
1277
|
+
if (frameAudit.enabled) {
|
|
1278
|
+
frameAudit.emit("frame.beginFrame.abort", {
|
|
1279
|
+
slotIndex,
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
Atomics.store(sabFrameTransport.tokens, slotIndex, 0);
|
|
1283
|
+
Atomics.store(sabFrameTransport.states, slotIndex, FRAME_SAB_SLOT_STATE_FREE);
|
|
1284
|
+
},
|
|
1285
|
+
};
|
|
1286
|
+
};
|
|
1287
|
+
return attachBackendMarkers(Object.assign(backend, { debug, perf }), {
|
|
1288
|
+
requestedDrawlistVersion,
|
|
1289
|
+
maxEventBytes,
|
|
1290
|
+
fpsCap,
|
|
1291
|
+
beginFrame,
|
|
1292
|
+
});
|
|
941
1293
|
}
|
|
942
1294
|
//# sourceMappingURL=nodeBackend.js.map
|