Files
remoteconn-gitea/apps/miniprogram/pages/terminal/terminalStdoutRenderPolicy.js
2026-03-21 18:57:10 +08:00

234 lines
6.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* global module */
const MEDIUM_BACKLOG_MIN_REMAINING_BYTES = 8 * 1024;
const MEDIUM_BACKLOG_MIN_PENDING_BYTES = 8 * 1024;
const MEDIUM_BACKLOG_MAX_SLICES = 8;
const MEDIUM_BACKLOG_RENDER_COOLDOWN_MS = 220;
const LARGE_BACKLOG_MIN_REMAINING_BYTES = 64 * 1024;
const LARGE_BACKLOG_MIN_PENDING_BYTES = 32 * 1024;
const LARGE_BACKLOG_MAX_SLICES = 32;
const LARGE_BACKLOG_RENDER_COOLDOWN_MS = 320;
const CRITICAL_BACKLOG_MIN_TOTAL_BYTES = 64 * 1024;
const CRITICAL_BACKLOG_MIN_PENDING_BYTES = 24 * 1024;
const CRITICAL_BACKLOG_MIN_PENDING_SAMPLES = 128;
const CRITICAL_BACKLOG_MIN_SCHEDULER_WAIT_MS = 1200;
const CRITICAL_BACKLOG_MIN_ACTIVE_AGE_MS = 1200;
const CRITICAL_BACKLOG_FRAME_PENDING_BYTES = 24 * 1024;
const CRITICAL_BACKLOG_FRAME_MAX_SLICES = 20;
const CRITICAL_BACKLOG_RENDER_COOLDOWN_MS = 520;
const STDOUT_OVERLAY_SYNC_COOLDOWN_MS = 240;
function normalizeNonNegativeInteger(value, fallback) {
const numeric = Number(value);
if (!Number.isFinite(numeric)) {
return Math.max(0, Math.round(Number(fallback) || 0));
}
return Math.max(0, Math.round(numeric));
}
function shouldUseCriticalBacklogPolicy(options) {
const source = options && typeof options === "object" ? options : {};
const totalRawBytes = normalizeNonNegativeInteger(source.totalRawBytes, 0);
const pendingStdoutBytes = normalizeNonNegativeInteger(source.pendingStdoutBytes, 0);
const pendingStdoutSamples = normalizeNonNegativeInteger(source.pendingStdoutSamples, 0);
const schedulerWaitMs = normalizeNonNegativeInteger(source.schedulerWaitMs, 0);
const activeStdoutAgeMs = normalizeNonNegativeInteger(source.activeStdoutAgeMs, 0);
return (
pendingStdoutBytes >= CRITICAL_BACKLOG_MIN_PENDING_BYTES ||
pendingStdoutSamples >= CRITICAL_BACKLOG_MIN_PENDING_SAMPLES ||
schedulerWaitMs >= CRITICAL_BACKLOG_MIN_SCHEDULER_WAIT_MS ||
(totalRawBytes >= CRITICAL_BACKLOG_MIN_TOTAL_BYTES &&
activeStdoutAgeMs >= CRITICAL_BACKLOG_MIN_ACTIVE_AGE_MS)
);
}
/**
* stdout 的真正瓶颈不是 VT 解析,而是每个 slice 都把整份 `outputRenderLines`
* 重新通过 `setData` 送去视图层。
*
* 这里按 backlog 做两档降频:
* 1. 中等 backlog累计到 8KB 或 8 个 slice 再提交一次;
* 2. 大 backlog累计到 32KB 或 32 个 slice 再提交一次。
*
* 若用户刚有输入,或当前 slice 触发了终端响应帧,则立即提交,避免交互被延后。
*/
function resolveTerminalStdoutRenderDecision(options) {
const source = options && typeof options === "object" ? options : {};
if (source.yieldedToUserInput) {
return {
defer: false,
reason: "user_input",
policy: "interactive"
};
}
if (normalizeNonNegativeInteger(source.pendingResponseCount, 0) > 0) {
return {
defer: false,
reason: "pending_response",
policy: "interactive"
};
}
const remainingBytes = normalizeNonNegativeInteger(source.remainingBytes, 0);
const pendingReplayBytes = normalizeNonNegativeInteger(source.pendingReplayBytes, 0);
const nextSlicesSinceLastRender = normalizeNonNegativeInteger(source.nextSlicesSinceLastRender, 0);
const timeSinceLastRenderMs = normalizeNonNegativeInteger(
source.timeSinceLastRenderMs,
Number.MAX_SAFE_INTEGER
);
const taskDone = !!source.taskDone;
const usingCriticalPolicy = shouldUseCriticalBacklogPolicy(source);
if (usingCriticalPolicy) {
if (taskDone) {
return {
defer: false,
reason: "task_complete",
policy: "critical_backlog"
};
}
if (
nextSlicesSinceLastRender > 0 &&
timeSinceLastRenderMs < CRITICAL_BACKLOG_RENDER_COOLDOWN_MS
) {
return {
defer: true,
reason: "render_cooldown",
policy: "critical_backlog"
};
}
if (pendingReplayBytes >= CRITICAL_BACKLOG_FRAME_PENDING_BYTES) {
return {
defer: false,
reason: "pending_bytes_threshold",
policy: "critical_backlog"
};
}
if (nextSlicesSinceLastRender >= CRITICAL_BACKLOG_FRAME_MAX_SLICES) {
return {
defer: false,
reason: "slice_threshold",
policy: "critical_backlog"
};
}
return {
defer: true,
reason: "defer_critical_backlog",
policy: "critical_backlog"
};
}
const usingLargePolicy =
remainingBytes >= LARGE_BACKLOG_MIN_REMAINING_BYTES ||
pendingReplayBytes >= LARGE_BACKLOG_MIN_PENDING_BYTES;
const minRemainingBytes = usingLargePolicy
? LARGE_BACKLOG_MIN_REMAINING_BYTES
: MEDIUM_BACKLOG_MIN_REMAINING_BYTES;
const minPendingBytes = usingLargePolicy
? LARGE_BACKLOG_MIN_PENDING_BYTES
: MEDIUM_BACKLOG_MIN_PENDING_BYTES;
const maxSlicesSinceRender = usingLargePolicy ? LARGE_BACKLOG_MAX_SLICES : MEDIUM_BACKLOG_MAX_SLICES;
const renderCooldownMs = usingLargePolicy
? LARGE_BACKLOG_RENDER_COOLDOWN_MS
: MEDIUM_BACKLOG_RENDER_COOLDOWN_MS;
const policy = usingLargePolicy ? "large_backlog" : "medium_backlog";
if (taskDone) {
return {
defer: false,
reason: "task_complete",
policy
};
}
if (
nextSlicesSinceLastRender > 0 &&
timeSinceLastRenderMs < renderCooldownMs &&
pendingReplayBytes < minPendingBytes &&
nextSlicesSinceLastRender < maxSlicesSinceRender
) {
return {
defer: true,
reason: "render_cooldown",
policy
};
}
if (remainingBytes < minRemainingBytes) {
return {
defer: false,
reason: "remaining_below_threshold",
policy
};
}
if (pendingReplayBytes >= minPendingBytes) {
return {
defer: false,
reason: "pending_bytes_threshold",
policy
};
}
if (nextSlicesSinceLastRender >= maxSlicesSinceRender) {
return {
defer: false,
reason: "slice_threshold",
policy
};
}
return {
defer: true,
reason: "defer_backlog",
policy
};
}
function resolveTerminalStdoutOverlayDecision(options) {
const source = options && typeof options === "object" ? options : {};
if (source.isFinalRender) {
return {
sync: true,
reason: "task_complete"
};
}
if (source.yieldedToUserInput) {
return {
sync: true,
reason: "user_input"
};
}
const overlayPassCount = normalizeNonNegativeInteger(source.overlayPassCount, 0);
if (overlayPassCount <= 0) {
return {
sync: true,
reason: "first_render"
};
}
const timeSinceLastOverlayMs = normalizeNonNegativeInteger(
source.timeSinceLastOverlayMs,
Number.MAX_SAFE_INTEGER
);
if (timeSinceLastOverlayMs >= STDOUT_OVERLAY_SYNC_COOLDOWN_MS) {
return {
sync: true,
reason: "overlay_cooldown_elapsed"
};
}
return {
sync: false,
reason: "overlay_throttled"
};
}
function shouldDeferTerminalStdoutRender(options) {
return resolveTerminalStdoutRenderDecision(options).defer;
}
module.exports = {
resolveTerminalStdoutOverlayDecision,
resolveTerminalStdoutRenderDecision,
shouldDeferTerminalStdoutRender
};