mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-05 17:27:42 +02:00
[eric] canvas: deferred shot encodes drain one per idle slot with a breath between, never as one frameless run
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C9zwUaHucUgrdxvK8FvjYT
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
84772545a9
commit
b645d4e03f
@@ -57,3 +57,15 @@ test('an encoder that throws reports an empty string rather than killing the cal
|
||||
await wait(40);
|
||||
assert.equal(out, '');
|
||||
});
|
||||
|
||||
test('shots that come due together encode one at a time with a breath between, never as one run', async () => {
|
||||
const ticks: number[] = [];
|
||||
const t0 = Date.now();
|
||||
for (let i = 0; i < 4; i += 1) {
|
||||
const log: string[] = [];
|
||||
encodeShotWhenIdle(fakeImage(300 + i, log), 640, () => { ticks.push(Date.now() - t0); });
|
||||
}
|
||||
await wait(400);
|
||||
assert.equal(ticks.length, 4);
|
||||
for (let i = 1; i < ticks.length; i += 1) assert.ok(ticks[i] - ticks[i - 1] >= 40, `encodes ${i - 1} and ${i} ran ${ticks[i] - ticks[i - 1]} ms apart`);
|
||||
});
|
||||
|
||||
@@ -6,31 +6,54 @@ import { perfBaseline } from '@/shared/perfBaseline';
|
||||
const MAX_GESTURE_WAITS = 6;
|
||||
const GESTURE_WAIT_MS = 400;
|
||||
const IDLE_TIMEOUT_MS = 1500;
|
||||
// One encode per idle slot with a breath between: a board full of shots coming due together used to land as one frameless run of encodes.
|
||||
const BETWEEN_ENCODES_MS = 50;
|
||||
|
||||
// PNG-encoding a full-page capture blocks the main thread for ~180 ms; shrinking first and encoding in an idle slot keeps it off every gesture frame.
|
||||
interface PendingShot { image: ElectronNativeImage; maxWidth: number; done: (dataUrl: string) => void; waits: number }
|
||||
const queue: PendingShot[] = [];
|
||||
let pumping = false;
|
||||
|
||||
function encodeNow(job: PendingShot): void {
|
||||
let dataUrl = '';
|
||||
try {
|
||||
const sized = job.image.getSize().width > job.maxWidth ? job.image.resize({ width: job.maxWidth, quality: 'good' }) : job.image;
|
||||
dataUrl = sized.toDataURL();
|
||||
} catch {
|
||||
dataUrl = '';
|
||||
}
|
||||
job.done(dataUrl);
|
||||
}
|
||||
|
||||
function whenIdle(fn: () => void): void {
|
||||
if (typeof requestIdleCallback === 'function') requestIdleCallback(() => fn(), { timeout: IDLE_TIMEOUT_MS });
|
||||
else window.setTimeout(fn, 0);
|
||||
}
|
||||
|
||||
function pump(): void {
|
||||
const job = queue[0];
|
||||
if (!job) { pumping = false; return; }
|
||||
if (interactionActive() && job.waits < MAX_GESTURE_WAITS) {
|
||||
job.waits += 1;
|
||||
window.setTimeout(pump, GESTURE_WAIT_MS);
|
||||
return;
|
||||
}
|
||||
queue.shift();
|
||||
encodeNow(job);
|
||||
if (queue.length === 0) { pumping = false; return; }
|
||||
window.setTimeout(() => whenIdle(pump), BETWEEN_ENCODES_MS);
|
||||
}
|
||||
|
||||
// PNG-encoding a full-page capture blocks the main thread for ~180 ms; shrinking first and encoding one per idle slot keeps it off every gesture frame.
|
||||
export function encodeShotWhenIdle(
|
||||
image: ElectronNativeImage,
|
||||
maxWidth: number,
|
||||
done: (dataUrl: string) => void,
|
||||
): void {
|
||||
let waits = 0;
|
||||
const run = (): void => {
|
||||
if (!perfBaseline() && interactionActive() && waits < MAX_GESTURE_WAITS) {
|
||||
waits += 1;
|
||||
window.setTimeout(run, GESTURE_WAIT_MS);
|
||||
return;
|
||||
}
|
||||
let dataUrl = '';
|
||||
try {
|
||||
const sized = image.getSize().width > maxWidth ? image.resize({ width: maxWidth, quality: 'good' }) : image;
|
||||
dataUrl = sized.toDataURL();
|
||||
} catch {
|
||||
dataUrl = '';
|
||||
}
|
||||
done(dataUrl);
|
||||
};
|
||||
const job: PendingShot = { image, maxWidth, done, waits: 0 };
|
||||
// The A/B seam keeps the old shape: encode right here, on whatever frame the capture landed in.
|
||||
if (perfBaseline()) run();
|
||||
else if (typeof requestIdleCallback === 'function') requestIdleCallback(() => run(), { timeout: IDLE_TIMEOUT_MS });
|
||||
else window.setTimeout(run, 0);
|
||||
if (perfBaseline()) { encodeNow(job); return; }
|
||||
queue.push(job);
|
||||
if (pumping) return;
|
||||
pumping = true;
|
||||
whenIdle(pump);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user