48 lines
1.5 KiB
TypeScript
48 lines
1.5 KiB
TypeScript
import { useEffect, type RefObject } from "react";
|
|
import type { AppStore } from "@editor/store";
|
|
import { createRenderer, type RenderFrame } from "@renderer/index";
|
|
import { canvasRenderFramesEqual, selectCanvasRenderFrame } from "./renderFrame";
|
|
|
|
export function useCanvasRenderer(canvasRef: RefObject<HTMLCanvasElement | null>, store: AppStore) {
|
|
useEffect(() => {
|
|
const canvas = canvasRef.current;
|
|
if (!canvas) return;
|
|
|
|
const renderer = createRenderer(canvas);
|
|
let renderedFrame = selectCanvasRenderFrame(store.getState());
|
|
let pendingFrame: RenderFrame | undefined;
|
|
let animationFrame: number | undefined;
|
|
|
|
const flushRender = () => {
|
|
animationFrame = undefined;
|
|
const frame = pendingFrame;
|
|
if (!frame) return;
|
|
|
|
pendingFrame = undefined;
|
|
renderedFrame = frame;
|
|
renderer.render(frame);
|
|
};
|
|
|
|
const scheduleRender = (frame: RenderFrame) => {
|
|
pendingFrame = frame;
|
|
if (animationFrame !== undefined) return;
|
|
animationFrame = requestAnimationFrame(flushRender);
|
|
};
|
|
|
|
scheduleRender(renderedFrame);
|
|
|
|
const unsubscribe = store.subscribe((state) => {
|
|
const nextFrame = selectCanvasRenderFrame(state);
|
|
const currentFrame = pendingFrame ?? renderedFrame;
|
|
if (canvasRenderFramesEqual(currentFrame, nextFrame)) return;
|
|
scheduleRender(nextFrame);
|
|
});
|
|
|
|
return () => {
|
|
unsubscribe();
|
|
if (animationFrame !== undefined) cancelAnimationFrame(animationFrame);
|
|
renderer.dispose();
|
|
};
|
|
}, [canvasRef, store]);
|
|
}
|