Add error handler

This commit is contained in:
Andras Schmelczer 2023-04-30 16:57:14 +01:00
commit c44a6858e0
No known key found for this signature in database
GPG key ID: FC8F2C3D3D1A718C
6 changed files with 104 additions and 39 deletions

View file

@ -34,9 +34,7 @@
<canvas></canvas> <canvas></canvas>
<section class="errors-container"> <section class="errors-container">
<pre class="errors"> <noscript>JavaScript is required for this website.</noscript>
<noscript>JavaScript is required for this website.</noscript>
</pre>
</section> </section>
</main> </main>

View file

@ -40,7 +40,7 @@ html {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
display: none; margin: var(--normal-margin);
pre { pre {
font-size: 20px; font-size: 20px;

View file

@ -2,6 +2,7 @@ import '../assets/icons/info.svg';
import GameLoop from './game-loop/game-loop'; import GameLoop from './game-loop/game-loop';
import './index.scss'; import './index.scss';
import { applyArrayPlugins } from './utils/array'; import { applyArrayPlugins } from './utils/array';
import { ErrorHandler, Severity } from './utils/error-handler';
import { FullScreenHandler } from './utils/full-screen-handler'; import { FullScreenHandler } from './utils/full-screen-handler';
import { initializeGPU } from './utils/graphics/initialize-gpu'; import { initializeGPU } from './utils/graphics/initialize-gpu';
@ -34,43 +35,49 @@ const getElements = () => ({
restartButton: document.querySelector('button.restart') as HTMLButtonElement, restartButton: document.querySelector('button.restart') as HTMLButtonElement,
canvas: document.querySelector('canvas') as HTMLCanvasElement, canvas: document.querySelector('canvas') as HTMLCanvasElement,
canvasContainer: document.querySelector('main.canvas-container') as HTMLCanvasElement, canvasContainer: document.querySelector('main.canvas-container') as HTMLCanvasElement,
errorContainer: document.querySelector('.errors') as HTMLDivElement, errorContainer: document.querySelector('.errors-container') as HTMLDivElement,
}); });
const main = async () => { const main = async () => {
applyArrayPlugins();
const elements = getElements(); const elements = getElements();
const defaultTimeToLive = 3500; ErrorHandler.addOnErrorListener((error, metadata) => {
const interval = 50; elements.errorContainer.innerHTML += `
let timeToLive = defaultTimeToLive; <pre class="${error.severity}">${error.message}</div>
setInterval(() => { <p>${JSON.stringify(metadata, null, 2)}</p>
timeToLive = Math.max(0, timeToLive - interval); `;
elements.aside.style.opacity = });
timeToLive == 0 && FullScreenHandler.isInFullScreenMode() ? '0' : '1';
}, interval);
elements.aside.addEventListener('mouseover', () => (timeToLive = defaultTimeToLive)); try {
applyArrayPlugins();
new FullScreenHandler( const defaultTimeToLive = 3500;
elements.minimizeFullScreenButton, const interval = 50;
elements.maximizeFullScreenButton, let timeToLive = defaultTimeToLive;
document.body setInterval(() => {
); timeToLive = Math.max(0, timeToLive - interval);
elements.aside.style.opacity =
timeToLive == 0 && FullScreenHandler.isInFullScreenMode() ? '0' : '1';
}, interval);
elements.aside.addEventListener('mouseover', () => (timeToLive = defaultTimeToLive));
const gpu = await initializeGPU(); new FullScreenHandler(
elements.minimizeFullScreenButton,
elements.maximizeFullScreenButton,
document.body
);
let game: GameLoop | null = null; const gpu = await initializeGPU();
let game: GameLoop | null = null;
elements.restartButton.addEventListener('click', () => game?.destroy()); elements.restartButton.addEventListener('click', () => game?.destroy());
while (true) { while (true) {
try {
game = new GameLoop(elements.canvas, gpu); game = new GameLoop(elements.canvas, gpu);
await game.start(); await game.start();
} catch (e) {
elements.errorContainer.innerHTML = e.message;
} }
} catch (e) {
ErrorHandler.addError(Severity.ERROR, e.message);
} }
}; };

View file

@ -0,0 +1,45 @@
export enum Severity {
INFO = 'info',
WARNING = 'warning',
ERROR = 'error',
}
export interface ErrorHandlerError {
severity: Severity;
message: string;
}
export type ErrorMetadata = { [key: string]: any };
export class ErrorHandler {
private static readonly errors: Array<ErrorHandlerError> = [];
private static metadata: ErrorMetadata = {};
private static onErrorListeners: Array<
(error: ErrorHandlerError, metadata: ErrorMetadata) => void
> = [];
public static addException(exception: Error) {
ErrorHandler.addError(Severity.ERROR, exception.message);
}
public static addError(severity: Severity, message: string) {
ErrorHandler.errors.push({ severity, message });
ErrorHandler.onErrorListeners.forEach((listener) =>
listener({ severity, message }, ErrorHandler.metadata)
);
}
public static addMetadata(key: string, value: any) {
const serialized = {};
for (const k in value) {
serialized[k] = value[k];
}
ErrorHandler.metadata[key] = serialized;
}
public static addOnErrorListener(
listener: (error: ErrorHandlerError, metadata: ErrorMetadata) => void
) {
ErrorHandler.onErrorListeners.push(listener);
}
}

View file

@ -1,7 +1,9 @@
import { ErrorHandler, Severity } from '../error-handler';
export const initializeGPU = async (): Promise<GPUDevice> => { export const initializeGPU = async (): Promise<GPUDevice> => {
const gpu = navigator.gpu; const gpu = navigator.gpu;
if (!gpu) { if (!gpu) {
throw new Error('WebGPU is not supported'); throw new Error('WebGPU is not supported in your browser');
} }
const adapter = await gpu.requestAdapter({ const adapter = await gpu.requestAdapter({
@ -12,5 +14,15 @@ export const initializeGPU = async (): Promise<GPUDevice> => {
throw new Error('Could not request adatper'); throw new Error('Could not request adatper');
} }
return await adapter.requestDevice(); // could request more resources ErrorHandler.addMetadata('adapter', await adapter.requestAdapterInfo());
ErrorHandler.addMetadata('features', adapter.features);
ErrorHandler.addMetadata('limits', adapter.limits);
const gpuDevice = await adapter.requestDevice(); // could request more resources
gpuDevice.addEventListener('uncapturederror', (event: GPUUncapturedErrorEvent) =>
ErrorHandler.addError(Severity.ERROR, event.error.message)
);
return gpuDevice;
}; };

View file

@ -1,3 +1,5 @@
import { ErrorHandler, Severity } from '../error-handler';
export const smartCompile = (device: GPUDevice, ...code: Array<string>) => { export const smartCompile = (device: GPUDevice, ...code: Array<string>) => {
const concatenated = code.join('\n\n'); const concatenated = code.join('\n\n');
@ -5,17 +7,18 @@ export const smartCompile = (device: GPUDevice, ...code: Array<string>) => {
code: concatenated, code: concatenated,
}); });
module module.getCompilationInfo().then((info) =>
.getCompilationInfo() info.messages.forEach((message) =>
.then((info) => ErrorHandler.addError(
info.messages.forEach((message) => {
console.warn( info: Severity.INFO,
message.type, warning: Severity.WARNING,
message.message, error: Severity.ERROR,
concatenated.split('\n')[message.lineNum - 1] }[message.type],
) `${message.message}\n${concatenated.split('\n')[message.lineNum - 1]}`
) )
); )
);
return module; return module;
}; };