返回 DeepSeek-Reasonix
rafBatch.ts
根目录 / desktop / frontend / src / lib / rafBatch.ts
1 // Coalesces text/reasoning stream deltas into one flush per animation frame.
2 // Non-text events must drain() first so causal ordering is preserved.
3 //
4 // A best-effort timer backs up rAF when frame callbacks pause but the JS task
5 // queue still runs, as can happen in a throttled or occluded WebView. It is not
6 // a main-thread watchdog: browser timer throttling and long tasks can delay
7 // both callbacks. Whichever callback runs first flushes and cancels the other,
8 // preserving one-flush-per-frame behavior while frames are being produced.
9
10 type Flush<T> = (batch: T[]) => void;
11
12 interface BatchHandle<T> {
13 push: (item: T) => void;
14 drain: () => void;
15 size: () => number;
16 }
17
18 // Request the fallback after 200ms. Timers are minimum-delay only, so a
19 // throttled WebView or blocked task queue can deliver it later.
20 const STALL_TIMEOUT_MS = 200;
21
22 export function createRafBatch<T>(flush: Flush<T>): BatchHandle<T> {
23 let buffer: T[] = [];
24 let scheduled: number | null = null; // rAF id; 1 = microtask fallback (no rAF)
25 let stallTimer: ReturnType<typeof setTimeout> | null = null;
26
27 const clearScheduled = () => {
28 if (scheduled !== null && scheduled !== 1 && typeof cancelAnimationFrame !== "undefined") {
29 cancelAnimationFrame(scheduled);
30 }
31 scheduled = null;
32 };
33
34 const clearStallTimer = () => {
35 if (stallTimer !== null) {
36 clearTimeout(stallTimer);
37 stallTimer = null;
38 }
39 };
40
41 const run = () => {
42 clearScheduled();
43 clearStallTimer();
44 // Snapshot + clear before flushing so a re-entrant push() lands next frame.
45 const out = buffer;
46 buffer = [];
47 if (out.length > 0) flush(out);
48 };
49
50 const arm = () => {
51 if (scheduled === null && typeof requestAnimationFrame !== "undefined") {
52 scheduled = requestAnimationFrame(run);
53 } else if (scheduled === null) {
54 // No rAF (SSR / JSDOM) — fall back to a microtask.
55 scheduled = 1;
56 Promise.resolve().then(run);
57 }
58 if (stallTimer === null && typeof setTimeout !== "undefined") {
59 stallTimer = setTimeout(run, STALL_TIMEOUT_MS);
60 }
61 };
62
63 const handle: BatchHandle<T> = {
64 push(item: T) {
65 buffer.push(item);
66 if (scheduled === null) arm();
67 },
68 drain() {
69 clearScheduled();
70 clearStallTimer();
71 run();
72 },
73 size() {
74 return buffer.length;
75 },
76 };
77 return handle;
78 }
79
79 lines TYPESCRIPT