perf(ui): enforce 750 ms live update cadence
Use one 750 ms interval for running download snapshots, progress updates, the bandwidth chart, and the header speed sparkline across every queue size. Remove the extra active-download renderer delay and add timing regressions for small, medium, and large queues.
This commit is contained in:
@@ -163,8 +163,9 @@ const ARCHIVE_SETTLE_MAX_WAIT_MS = 5000;
|
||||
|
||||
const MAX_SAME_DIRECT_URL_ATTEMPTS = 3;
|
||||
|
||||
const MAX_HTTP416_FRESH_RESTARTS = 2;
|
||||
const HTTP416_FRESH_RESTART_DELAY_MS = 8000;
|
||||
const MAX_HTTP416_FRESH_RESTARTS = 2;
|
||||
const HTTP416_FRESH_RESTART_DELAY_MS = 8000;
|
||||
const DOWNLOAD_LIVE_UPDATE_INTERVAL_MS = 750;
|
||||
|
||||
function getHttp416FreshRestartDelayMs(): number {
|
||||
const fromEnv = Number(process.env.RD_HTTP416_FRESH_RESTART_DELAY_MS ?? NaN);
|
||||
@@ -6561,14 +6562,7 @@ export class DownloadManager extends EventEmitter {
|
||||
if (this.stateEmitTimer) {
|
||||
return;
|
||||
}
|
||||
const itemCount = this.itemCount;
|
||||
const emitDelay = this.session.running
|
||||
? itemCount >= 1500
|
||||
? 700
|
||||
: itemCount >= 250
|
||||
? 500
|
||||
: 150
|
||||
: 200;
|
||||
const emitDelay = this.session.running ? DOWNLOAD_LIVE_UPDATE_INTERVAL_MS : 200;
|
||||
this.stateEmitTimer = setTimeout(() => {
|
||||
this.stateEmitTimer = null;
|
||||
this.lastStateEmitAt = nowMs();
|
||||
@@ -10579,12 +10573,7 @@ export class DownloadManager extends EventEmitter {
|
||||
let windowStarted = nowMs();
|
||||
let lastPackageLogAt = 0;
|
||||
let lastLoggedPercent = -1;
|
||||
const itemCount = this.itemCount;
|
||||
const uiUpdateIntervalMs = itemCount >= 1500
|
||||
? 700
|
||||
: itemCount >= 250
|
||||
? 500
|
||||
: 120;
|
||||
const uiUpdateIntervalMs = DOWNLOAD_LIVE_UPDATE_INTERVAL_MS;
|
||||
let lastUiEmitAt = 0;
|
||||
const stallTimeoutMs = getDownloadStallTimeoutMs();
|
||||
const drainTimeoutMs = Math.max(30000, Math.min(300000, stallTimeoutMs > 0 ? stallTimeoutMs * 12 : 120000));
|
||||
|
||||
@@ -880,7 +880,7 @@ const historyRetentionLabels: Record<RendererSettings["historyRetentionMode"], s
|
||||
const AUTO_RENDER_PACKAGE_LIMIT = 260;
|
||||
|
||||
export function getSnapshotRenderDelay(itemCount: number, running: boolean, activeTab: MainView): number {
|
||||
let delay = itemCount >= 250 ? 0 : 100;
|
||||
let delay = running && activeTab === "downloads" ? 0 : itemCount >= 250 ? 0 : 100;
|
||||
if (!running) delay = Math.min(delay, 200);
|
||||
if (activeTab !== "downloads") delay = Math.max(delay, 800);
|
||||
return delay;
|
||||
@@ -1272,7 +1272,7 @@ const BandwidthChart = memo(function BandwidthChart({ running, paused, speedHist
|
||||
const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
||||
const interval = setInterval(() => {
|
||||
drawChart();
|
||||
}, reducedMotion ? 1000 : 500);
|
||||
}, reducedMotion ? 1000 : 750);
|
||||
return () => clearInterval(interval);
|
||||
}, [drawChart, running, paused]);
|
||||
|
||||
@@ -1374,7 +1374,7 @@ const DownloadSpeedSparkline = memo(function DownloadSpeedSparkline({ speedBps,
|
||||
draw();
|
||||
};
|
||||
|
||||
const id = window.setInterval(tick, 500);
|
||||
const id = window.setInterval(tick, 750);
|
||||
return () => window.clearInterval(id);
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -50,10 +50,21 @@ describe("desktop shell", () => {
|
||||
expect(getSnapshotRenderDelay(2_470, true, "statistics")).toBe(800);
|
||||
});
|
||||
|
||||
it("renders medium download queues immediately after their calm half-second snapshot", () => {
|
||||
it("renders medium download queues immediately after their calm 750 ms snapshot", () => {
|
||||
expect(getSnapshotRenderDelay(661, true, "downloads")).toBe(0);
|
||||
});
|
||||
|
||||
it("does not add renderer delay after the 750 ms manager cadence for a small active queue", () => {
|
||||
expect(getSnapshotRenderDelay(69, true, "downloads")).toBe(0);
|
||||
});
|
||||
|
||||
it("redraws the header speed sparkline on the same 750 ms cadence", () => {
|
||||
const source = readFileSync(new URL("../src/renderer/App.tsx", import.meta.url), "utf8");
|
||||
const sparklineBlock = source.slice(source.indexOf("const DownloadSpeedSparkline"), source.indexOf("const initialCollectorTabs"));
|
||||
|
||||
expect(sparklineBlock).toContain("window.setInterval(tick, 750)");
|
||||
});
|
||||
|
||||
it("keeps application menus mounted for animated opening and closing", () => {
|
||||
const source = readFileSync(new URL("../src/renderer/App.tsx", import.meta.url), "utf8");
|
||||
const css = readFileSync(new URL("../src/renderer/styles.css", import.meta.url), "utf8");
|
||||
|
||||
@@ -45,6 +45,44 @@ describe("runWithLimitedConcurrency", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("download live update cadence", () => {
|
||||
it.each([69, 661, 2_470])("emits a running queue snapshot no sooner than 750 ms for %i items", async (itemCount) => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-live-cadence-"));
|
||||
tempDirs.push(root);
|
||||
const session = emptySession();
|
||||
session.running = true;
|
||||
const manager = new DownloadManager(defaultSettings(), session, createStoragePaths(path.join(root, "state")));
|
||||
const internal = manager as unknown as {
|
||||
emitState: () => void;
|
||||
itemCount: number;
|
||||
session: typeof session;
|
||||
stateEmitTimer: NodeJS.Timeout | null;
|
||||
};
|
||||
internal.itemCount = itemCount;
|
||||
internal.session.running = true;
|
||||
if (internal.stateEmitTimer) {
|
||||
clearTimeout(internal.stateEmitTimer);
|
||||
internal.stateEmitTimer = null;
|
||||
}
|
||||
let emitted = 0;
|
||||
manager.on("state", () => {
|
||||
emitted += 1;
|
||||
});
|
||||
|
||||
internal.emitState();
|
||||
await vi.advanceTimersByTimeAsync(749);
|
||||
expect(emitted).toBe(0);
|
||||
|
||||
await vi.advanceTimersByTimeAsync(1);
|
||||
expect(emitted).toBe(1);
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveUnrestrictTimeoutBudgetMs", () => {
|
||||
it("covers the complete provider plan and each later Real-Debrid account attempt", () => {
|
||||
const settings = {
|
||||
|
||||
@@ -507,7 +507,7 @@ describe("bandwidth chart palette", () => {
|
||||
expect(chartBlock).toContain('role="img"');
|
||||
expect(chartBlock).toContain('aria-label="Bandbreitenverlauf der letzten 60 Sekunden"');
|
||||
expect(chartBlock).toContain('window.matchMedia("(prefers-reduced-motion: reduce)")');
|
||||
expect(chartBlock).toContain("reducedMotion ? 1000 : 500");
|
||||
expect(chartBlock).toContain("reducedMotion ? 1000 : 750");
|
||||
});
|
||||
|
||||
it("asks for confirmation before deleting all saved download statistics", () => {
|
||||
|
||||
Reference in New Issue
Block a user