// luma.gl // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors import {luma, Adapter, Device} from '@luma.gl/core'; import {AnimationLoopTemplate} from './animation-loop-template'; import {AnimationLoop, AnimationLoopProps} from './animation-loop'; import type {AnimationProps} from './animation-props'; export type MakeAnimationLoopProps = Omit< AnimationLoopProps, 'onCreateDevice' | 'onInitialize' | 'onRedraw' | 'onFinalize' > & { /** List of adapters to use when creating the device */ adapters?: Adapter[]; }; /** * Instantiates an animation loop and initializes it with the template. * @note The application needs to call `start()` on the returned animation loop to start the rendering loop. */ export function makeAnimationLoop( AnimationLoopTemplateCtor: typeof AnimationLoopTemplate, props?: MakeAnimationLoopProps ): AnimationLoop { let renderLoop: AnimationLoopTemplate | null = null; const device = props?.device || luma.createDevice({id: 'animation-loop', adapters: props?.adapters, createCanvasContext: true}); // Create an animation loop; const animationLoop = new AnimationLoop({ ...props, device, async onInitialize(animationProps: AnimationProps): Promise { clearError(animationProps.animationLoop.device); try { // @ts-expect-error abstract to prevent instantiation renderLoop = new AnimationLoopTemplateCtor(animationProps); // Any async loading can be handled here return await renderLoop?.onInitialize(animationProps); } catch (error) { console.error(error); setError(animationProps.animationLoop.device, error as Error); return null; } }, onRender: (animationProps: AnimationProps) => renderLoop?.onRender(animationProps), onFinalize: (animationProps: AnimationProps) => renderLoop?.onFinalize(animationProps) }); // @ts-expect-error Hack: adds info for the website to find animationLoop.getInfo = () => { // @ts-ignore // eslint-disable-next-line no-invalid-this return this.AnimationLoopTemplateCtor.info; }; return animationLoop; } function setError(device: Device | null, error: Error): void { if (!device) { return; } const canvas = device.getDefaultCanvasContext().canvas; if (canvas instanceof HTMLCanvasElement) { canvas.style.overflow = 'visible'; let errorDiv = document.getElementById('animation-loop-error'); errorDiv?.remove(); errorDiv = document.createElement('h1'); errorDiv.id = 'animation-loop-error'; errorDiv.innerHTML = error.message; errorDiv.style.position = 'absolute'; errorDiv.style.top = '10px'; // left: 50%; transform: translate(-50%, -50%);'; errorDiv.style.left = '10px'; errorDiv.style.color = 'black'; errorDiv.style.backgroundColor = 'red'; canvas.parentElement?.appendChild(errorDiv); // canvas.style.position = 'absolute'; } } function clearError(device: Device | null): void { if (!device) { return; } const errorDiv = document.getElementById('animation-loop-error'); if (errorDiv) { errorDiv.remove(); } }