summaryrefslogtreecommitdiff
path: root/packages/frontend/src/scripts/idle-render.ts
blob: ac1be50c739f0f92e8f1465097d1e258ddb22dc0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/*
 * SPDX-FileCopyrightText: syuilo and other misskey contributors
 * SPDX-License-Identifier: AGPL-3.0-only
 */

const requestIdleCallback: typeof globalThis.requestIdleCallback = globalThis.requestIdleCallback ?? ((callback) => {
	const start = performance.now();
	const timeoutId = setTimeout(() => {
		callback({
			didTimeout: false, // polyfill でタイムアウト発火することはない
			timeRemaining() {
				const diff = performance.now() - start;
				return Math.max(0, 50 - diff); // <https://www.w3.org/TR/requestidlecallback/#idle-periods>
			},
		});
	});
	return timeoutId;
});
const cancelIdleCallback: typeof globalThis.cancelIdleCallback = globalThis.cancelIdleCallback ?? ((timeoutId) => {
	clearTimeout(timeoutId);
});

class IdlingRenderScheduler {
	#renderers: Set<FrameRequestCallback>;
	#rafId: number;
	#ricId: number;

	constructor() {
		this.#renderers = new Set();
		this.#rafId = 0;
		this.#ricId = requestIdleCallback((deadline) => this.#schedule(deadline));
	}

	#schedule(deadline: IdleDeadline): void {
		if (deadline.timeRemaining()) {
			this.#rafId = requestAnimationFrame((time) => {
				for (const renderer of this.#renderers) {
					renderer(time);
				}
			});
		}
		this.#ricId = requestIdleCallback((arg) => this.#schedule(arg));
	}

	add(renderer: FrameRequestCallback): void {
		this.#renderers.add(renderer);
	}

	delete(renderer: FrameRequestCallback): void {
		this.#renderers.delete(renderer);
	}

	dispose(): void {
		this.#renderers.clear();
		cancelAnimationFrame(this.#rafId);
		cancelIdleCallback(this.#ricId);
	}
}

export const defaultIdlingRenderScheduler = new IdlingRenderScheduler();