From 8600639023f868badf87396a61ffffd2450290d8 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Fri, 6 Aug 2021 13:48:08 +0000 Subject: [PATCH] Further fixes; improve error handling --- Makefile | 2 +- package-lock.json | 12 ++++++++++++ package.json | 2 ++ ui/index.js | 42 ++++++++++++++++++++++++++---------------- ui/ops.js | 20 +++++++++++--------- ui/preview.js | 6 ++++-- 6 files changed, 56 insertions(+), 28 deletions(-) diff --git a/Makefile b/Makefile index 7d27041..b917414 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ CPPFLAGS += -I $(GPHOTO2PREFIX)/include libapi.mjs: api.cpp - libtool --verbose --mode=link --tag=CXX $(CC) -std=c++17 $+ --bind -s ASYNCIFY -fexceptions -g -O2 -s ALLOW_MEMORY_GROWTH -s DYNAMIC_EXECUTION=0 -o $@ \ + libtool --verbose --mode=link --tag=CXX $(CC) -std=c++17 $+ --bind -s ASYNCIFY -fexceptions -Os -s ALLOW_MEMORY_GROWTH -s DYNAMIC_EXECUTION=0 -o $@ \ $(GPHOTO2PREFIX)/lib/libltdl.la \ $(GPHOTO2PREFIX)/lib/libgphoto2.la \ -dlpreopen $(GPHOTO2PREFIX)/lib/libgphoto2/2.5.27.1/ptp2.la \ diff --git a/package-lock.json b/package-lock.json index 898839d..3a5badc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,18 @@ "integrity": "sha512-DIOOg+POSrYl+OlNRHQuIEqCd8DCtynG57H862UCce16nXJX7J8eWxNGgOcf8Eyge8zXeSs27mz1UcFu8L/L7g==", "dev": true }, + "@types/requestidlecallback": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@types/requestidlecallback/-/requestidlecallback-0.3.4.tgz", + "integrity": "sha512-aTSyiZuRemRLTQkJPb25L7A4/eR2Teo5l4yJ1V6P3+MFxEZckTDkNKNtr/V1zEOMzS6H8DgxF22U6jPAPrzQvw==", + "dev": true + }, + "@types/stats.js": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.0.tgz", + "integrity": "sha512-9w+a7bR8PeB0dCT/HBULU2fMqf6BAzvKbxFboYhmDtDkKPiyXYbjoe2auwsXlEFI7CFNMF1dCv3dFH5Poy9R1w==", + "dev": true + }, "preact": { "version": "10.5.14", "resolved": "https://registry.npmjs.org/preact/-/preact-10.5.14.tgz", diff --git a/package.json b/package.json index 7efb360..59d9283 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,8 @@ { "devDependencies": { "@types/emscripten": "^1.39.5", + "@types/requestidlecallback": "^0.3.4", + "@types/stats.js": "^0.17.0", "preact": "^10.5.14" } } diff --git a/ui/index.js b/ui/index.js index 0d50e4b..19a0b48 100644 --- a/ui/index.js +++ b/ui/index.js @@ -1,6 +1,6 @@ import { h, render, Component } from 'preact'; import { CaptureButton } from './capture-button.js'; -import { connect } from './ops.js'; +import { connect, rethrowIfCritical } from './ops.js'; import { Preview } from './preview.js'; import { Widget } from './widget.js'; @@ -69,26 +69,36 @@ class App extends Component { } // We should reach this only once. while (this.connection) { - let config = await this.connection.schedule(context => - context.configToJS() - ); - if (!isDebug) { - delete config.children.actions; - delete config.children.other; + try { + let config = await this.connection.schedule(context => + context.configToJS() + ); + if (!isDebug) { + delete config.children.actions; + delete config.children.other; + } + this.setState({ + type: 'Config', + config + }); + } catch (err) { + rethrowIfCritical(err); + console.error('Could not refresh config:', err); } - this.setState({ - type: 'Config', - config - }); while (true) { await new Promise(resolve => requestIdleCallback(resolve, { timeout: 500 }) ); - let hadEvents = await this.connection.schedule(context => - context.consumeEvents() - ); - if (hadEvents) { - break; + try { + let hadEvents = await this.connection.schedule(context => + context.consumeEvents() + ); + if (hadEvents) { + break; + } + } catch (err) { + rethrowIfCritical(err); + console.error('Could not consume events:', err); } } } diff --git a/ui/ops.js b/ui/ops.js index 4a026bd..3a217f6 100644 --- a/ui/ops.js +++ b/ui/ops.js @@ -4,29 +4,31 @@ import initModule from '../libapi.mjs'; const ModulePromise = initModule(); +export function rethrowIfCritical(err) { + // If it's precisely Error, it's a custom error; anything else - SyntaxError, + // WebAssembly.RuntimeError, TypeError, etc. - is treated as critical here. + if (err.constructor !== Error) { + throw err; + } +} + export async function connect() { const Module = await ModulePromise; let context = await new Module.Context(); let supportedOps = await context.supportedOps(); + /** @type {Promise} */ let queue = Promise.resolve(); /** Schedules an exclusive async operation on the global context. - * @template T + * @template T,T2 * @param {(ctx: Context) => Promise} op * @returns {Promise} */ function schedule(op) { let res = queue.then(() => op(context)); - - // Queue should ignore result values as well as errors from singular ops. - queue = res.then( - () => {}, - () => {} - ); - - // Result should contain the unwrapped value or error. + queue = res.catch(rethrowIfCritical); return res; } diff --git a/ui/preview.js b/ui/preview.js index de1f403..8ef3327 100644 --- a/ui/preview.js +++ b/ui/preview.js @@ -1,4 +1,5 @@ import { h, Component, createRef } from 'preact'; +import { rethrowIfCritical } from './ops.js'; export const isDebug = new URLSearchParams(location.search).has('debug'); @@ -87,8 +88,9 @@ export class Preview extends Component { updateCanvasSize(); } canvasCtx.transferFromImageBitmap(img); - } catch (error) { - console.error(error); + } catch (err) { + rethrowIfCritical(err); + console.error('Could not refresh preview:', err); } await new Promise(resolve => requestAnimationFrame(resolve)); this.stats?.update();