Skip to content

Commit bbfd18d

Browse files
committed
style engine error dialog, and use it everywhere
1 parent ecbcb5e commit bbfd18d

8 files changed

+103
-45
lines changed

ui/ceval/css/_ctrl.scss

+36
Original file line numberDiff line numberDiff line change
@@ -182,3 +182,39 @@
182182
animation: bar-anim 1000s linear infinite;
183183
}
184184
}
185+
186+
.engine-error {
187+
@extend %flex-column;
188+
align-items: center;
189+
row-gap: 1em;
190+
padding: 3em;
191+
text-align: left;
192+
max-width: 60vw;
193+
194+
@media (max-width: $mq-width-medium) {
195+
max-width: 80vw;
196+
}
197+
@media (max-width: $mq-width-xx-small) {
198+
max-width: unset;
199+
width: 96vw;
200+
}
201+
202+
h2 {
203+
font-family: 'Noto Sans';
204+
font-size: 1.3em;
205+
}
206+
li {
207+
margin: 0 2em;
208+
line-height: 1.5em;
209+
list-style: disc;
210+
}
211+
pre {
212+
width: 100%;
213+
border: 1px solid #888;
214+
box-shadow: inset 0 0 2px $c-font-dim;
215+
padding: 1em;
216+
margin-bottom: 1em;
217+
white-space: pre-wrap;
218+
word-wrap: break-word;
219+
}
220+
}

ui/ceval/src/ctrl.ts

+16-5
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { hasFeature } from 'common/device';
1111
import { Result } from '@badrap/result';
1212
import { storedIntProp } from 'common/storage';
1313
import { Rules } from 'chessops';
14-
import { domDialog } from 'common/dialog';
14+
import { type Dialog, domDialog } from 'common/dialog';
1515

1616
const cevalDisabledSentinel = '1';
1717

@@ -239,11 +239,22 @@ export default class CevalCtrl {
239239

240240
engineFailed(msg: string) {
241241
domDialog({
242-
show: 'modal',
242+
class: 'engine-error',
243243
htmlText:
244-
`<div><p>${this.engines.active?.name ?? 'Engine'} failed:</p><hr><code>${msg}</code><hr>` +
245-
'<p>Things you can try:</p><p>Decrease memory slider in engine settings. Update your browser. ' +
246-
'Clear site settings for lichess.org. Or select another engine</p></div>',
244+
`<h2>${lichess.escapeHtml(this.engines.active?.name ?? 'Engine')} <bad>error</bad></h2>` +
245+
`<pre tabindex="0" class="err">${lichess.escapeHtml(msg)}</pre><h2>Things to try</h2><ul>` +
246+
'<li>Decrease memory slider in engine settings</li><li>Clear site settings for lichess.org</li>' +
247+
'<li>Select another engine</li><li>Update your browser</li></ul>',
248+
}).then((dlg: Dialog) => {
249+
const select = () =>
250+
setTimeout(() => {
251+
const range = document.createRange();
252+
range.selectNodeContents(dlg.view.querySelector('.err')!);
253+
window.getSelection()?.removeAllRanges();
254+
window.getSelection()?.addRange(range);
255+
}, 0);
256+
dlg.view.querySelector('.err')?.addEventListener('focus', select);
257+
dlg.showModal();
247258
});
248259
}
249260

ui/ceval/src/engines/engines.ts

+15-15
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@ export class Engines {
2424
this.selectProp = storedStringProp('ceval.engine', this.localEngines[0].id);
2525
}
2626

27-
makeEngineMap() {
28-
const progress = (download?: { bytes: number; total: number }, error?: string) => {
29-
if (this.ctrl.enabled()) this.ctrl.download = download;
30-
if (error) this.ctrl.engineFailed(error);
31-
this.ctrl.opts.redraw();
32-
};
27+
status = (status: { download?: { bytes: number; total: number }; error?: string } = {}) => {
28+
if (this.ctrl.enabled()) this.ctrl.download = status.download;
29+
if (status.error) this.ctrl.engineFailed(status.error);
30+
this.ctrl.opts.redraw();
31+
};
3332

33+
makeEngineMap() {
3434
return new Map<string, WithMake>(
3535
[
3636
{
@@ -47,7 +47,7 @@ export class Engines {
4747
js: 'linrock-nnue-7.js',
4848
},
4949
},
50-
make: (e: BrowserEngineInfo) => new StockfishWebEngine(e, progress),
50+
make: (e: BrowserEngineInfo) => new StockfishWebEngine(e, this.status),
5151
},
5252
{
5353
info: {
@@ -63,7 +63,7 @@ export class Engines {
6363
js: 'sf-nnue-40.js',
6464
},
6565
},
66-
make: (e: BrowserEngineInfo) => new StockfishWebEngine(e, progress),
66+
make: (e: BrowserEngineInfo) => new StockfishWebEngine(e, this.status),
6767
},
6868
{
6969
info: {
@@ -80,7 +80,7 @@ export class Engines {
8080
wasm: 'stockfish.wasm',
8181
},
8282
},
83-
make: (e: BrowserEngineInfo) => new ThreadedEngine(e, progress),
83+
make: (e: BrowserEngineInfo) => new ThreadedEngine(e, this.status),
8484
},
8585
{
8686
info: {
@@ -105,7 +105,7 @@ export class Engines {
105105
},
106106
},
107107
make: (e: BrowserEngineInfo) =>
108-
new StockfishWebEngine(e, progress, v => (v === 'threeCheck' ? '3check' : v.toLowerCase())),
108+
new StockfishWebEngine(e, this.status, v => (v === 'threeCheck' ? '3check' : v.toLowerCase())),
109109
},
110110
{
111111
info: {
@@ -131,7 +131,7 @@ export class Engines {
131131
},
132132
},
133133
make: (e: BrowserEngineInfo) =>
134-
new ThreadedEngine(e, progress, (v: VariantKey) =>
134+
new ThreadedEngine(e, this.status, (v: VariantKey) =>
135135
v === 'antichess' ? 'giveaway' : lichessRules(v),
136136
),
137137
},
@@ -149,7 +149,7 @@ export class Engines {
149149
wasm: 'stockfish.wasm',
150150
},
151151
},
152-
make: (e: BrowserEngineInfo) => new ThreadedEngine(e, progress),
152+
make: (e: BrowserEngineInfo) => new ThreadedEngine(e, this.status),
153153
},
154154
{
155155
info: {
@@ -166,7 +166,7 @@ export class Engines {
166166
js: 'stockfish.wasm.js',
167167
},
168168
},
169-
make: (e: BrowserEngineInfo) => new SimpleEngine(e, progress),
169+
make: (e: BrowserEngineInfo) => new SimpleEngine(e, this.status),
170170
},
171171
{
172172
info: {
@@ -182,7 +182,7 @@ export class Engines {
182182
js: 'stockfish.js',
183183
},
184184
},
185-
make: (e: BrowserEngineInfo) => new SimpleEngine(e, progress),
185+
make: (e: BrowserEngineInfo) => new SimpleEngine(e, this.status),
186186
},
187187
]
188188
.filter(
@@ -249,7 +249,7 @@ export class Engines {
249249

250250
return e.tech !== 'EXTERNAL'
251251
? this.localEngineMap.get(e.id)!.make(e as BrowserEngineInfo)
252-
: new ExternalEngine(e as ExternalEngineInfo, this.ctrl.opts.redraw);
252+
: new ExternalEngine(e as ExternalEngineInfo, this.status);
253253
}
254254
}
255255

ui/ceval/src/engines/externalEngine.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Work, ExternalEngineInfo, CevalEngine, CevalState } from '../types';
1+
import { Work, ExternalEngineInfo, CevalEngine, CevalState, EngineNotifier } from '../types';
22
import { randomToken } from 'common/random';
33
import { readNdJson } from 'common/ndjson';
44
import throttle from 'common/throttle';
@@ -22,7 +22,7 @@ export class ExternalEngine implements CevalEngine {
2222

2323
constructor(
2424
private opts: ExternalEngineInfo,
25-
private progress?: (download?: { bytes: number; total: number }) => void,
25+
private status?: EngineNotifier,
2626
) {}
2727

2828
getState() {
@@ -84,16 +84,16 @@ export class ExternalEngine implements CevalEngine {
8484
});
8585

8686
this.state = CevalState.Initial;
87+
this.status?.();
8788
} catch (err: unknown) {
8889
if ((err as Error).name !== 'AbortError') {
8990
console.error(err);
9091
this.state = CevalState.Failed;
92+
this.status?.({ error: String(err) });
9193
} else {
9294
this.state = CevalState.Initial;
9395
}
9496
}
95-
96-
this.progress?.();
9797
}
9898

9999
stop() {

ui/ceval/src/engines/simpleEngine.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import { Protocol } from '../protocol';
2-
import { Work, CevalState, CevalEngine, BrowserEngineInfo } from '../types';
2+
import { Work, CevalState, CevalEngine, BrowserEngineInfo, EngineNotifier } from '../types';
33

44
export class SimpleEngine implements CevalEngine {
5-
private failed = false;
5+
private failed: Error;
66
private protocol = new Protocol();
77
private worker: Worker | undefined;
88
url: string;
99

1010
constructor(
1111
readonly info: BrowserEngineInfo,
12-
readonly progress?: (download?: { bytes: number; total: number }) => void,
12+
readonly status?: EngineNotifier,
1313
) {
1414
this.url = `${info.assets.root}/${info.assets.js}`;
1515
}
@@ -40,8 +40,8 @@ export class SimpleEngine implements CevalEngine {
4040
'error',
4141
err => {
4242
console.error(err);
43-
this.failed = true;
44-
this.progress?.();
43+
this.failed = err.error;
44+
this.status?.({ error: String(this.failed) });
4545
},
4646
true,
4747
);

ui/ceval/src/engines/stockfishWebEngine.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Work, CevalEngine, CevalState, BrowserEngineInfo } from '../types';
1+
import { Work, CevalEngine, CevalState, BrowserEngineInfo, EngineNotifier } from '../types';
22
import { Protocol } from '../protocol';
33
import { objectStorage } from 'common/objectStorage';
44
import { sharedWasmMemory } from '../util';
@@ -11,14 +11,14 @@ export class StockfishWebEngine implements CevalEngine {
1111

1212
constructor(
1313
readonly info: BrowserEngineInfo,
14-
readonly progress?: (download?: { bytes: number; total: number }, error?: string) => void,
14+
readonly status?: EngineNotifier,
1515
readonly variantMap?: (v: string) => string,
1616
) {
1717
this.protocol = new Protocol(variantMap);
1818
this.boot().catch(e => {
1919
console.error(e);
2020
this.failed = e;
21-
progress?.(undefined, e.message);
21+
this.status?.({ error: String(e) });
2222
});
2323
}
2424

@@ -56,7 +56,7 @@ export class StockfishWebEngine implements CevalEngine {
5656
}, 2000);
5757
} else {
5858
console.error(msg);
59-
this.progress?.(undefined, msg);
59+
this.status?.({ error: msg });
6060
}
6161
};
6262
let nnueBuffer = await nnueStore?.get(nnueFilename).catch(() => undefined);
@@ -66,7 +66,7 @@ export class StockfishWebEngine implements CevalEngine {
6666

6767
req.open('get', lichess.assetUrl(`lifat/nnue/${nnueFilename}`, { noVersion: true }), true);
6868
req.responseType = 'arraybuffer';
69-
req.onprogress = e => this.progress?.({ bytes: e.loaded, total: e.total });
69+
req.onprogress = e => this.status?.({ download: { bytes: e.loaded, total: e.total } });
7070

7171
nnueBuffer = await new Promise((resolve, reject) => {
7272
req.onerror = () => reject(new Error(`NNUE download failed: ${req.status}`));
@@ -76,7 +76,7 @@ export class StockfishWebEngine implements CevalEngine {
7676
};
7777
req.send();
7878
});
79-
this.progress?.();
79+
this.status?.();
8080
nnueStore?.put(nnueFilename, nnueBuffer!).catch(() => console.warn('IDB store failed'));
8181
}
8282
module.setNnueBuffer(nnueBuffer!);

ui/ceval/src/engines/threadedEngine.ts

+16-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Protocol } from '../protocol';
2-
import { Work, CevalEngine, CevalState, BrowserEngineInfo } from '../types';
2+
import { Work, CevalEngine, CevalState, BrowserEngineInfo, EngineNotifier } from '../types';
33
import { sharedWasmMemory } from '../util';
44
import { Cache } from '../cache';
55

@@ -8,6 +8,8 @@ interface WasmModule {
88
wasmBinary?: ArrayBuffer;
99
locateFile(path: string): string;
1010
wasmMemory: WebAssembly.Memory;
11+
printErr(msg: string): void;
12+
onError(err: Error): void;
1113
}): Promise<Stockfish>;
1214
}
1315

@@ -24,16 +26,22 @@ declare global {
2426
}
2527

2628
export class ThreadedEngine implements CevalEngine {
27-
failed: boolean;
29+
failed: Error;
2830
protocol: Protocol;
2931
module?: Stockfish;
3032

3133
constructor(
3234
readonly info: BrowserEngineInfo,
33-
readonly progress?: (download?: { bytes: number; total: number }) => void,
35+
readonly status?: EngineNotifier,
3436
readonly variantMap?: (v: string) => string,
3537
) {}
3638

39+
onError = (err: Error) => {
40+
console.error(err);
41+
this.failed = err;
42+
this.status?.({ error: String(err) });
43+
};
44+
3745
getInfo() {
3846
return this.info;
3947
}
@@ -76,9 +84,9 @@ export class ThreadedEngine implements CevalEngine {
7684
req.open('GET', lichess.assetUrl(wasmPath, { version }), true);
7785
req.responseType = 'arraybuffer';
7886
req.onerror = event => reject(event);
79-
req.onprogress = event => this.progress?.({ bytes: event.loaded, total: event.total });
87+
req.onprogress = event => this.status?.({ download: { bytes: event.loaded, total: event.total } });
8088
req.onload = _ => {
81-
this.progress?.();
89+
this.status?.();
8290
resolve(req.response);
8391
};
8492
req.send();
@@ -95,6 +103,8 @@ export class ThreadedEngine implements CevalEngine {
95103
await lichess.loadIife(`${root}/${js}`, { version });
96104
const sf = await window[this.info.id === '__sf11mv' ? 'StockfishMv' : 'Stockfish']!({
97105
wasmBinary,
106+
printErr: (msg: string) => this.onError(new Error(msg)),
107+
onError: this.onError,
98108
locateFile: (path: string) =>
99109
lichess.assetUrl(`${root}/${path}`, { version, sameDomain: path.endsWith('.worker.js') }),
100110
wasmMemory: sharedWasmMemory(this.info.minMem!),
@@ -108,11 +118,7 @@ export class ThreadedEngine implements CevalEngine {
108118
async start(work: Work) {
109119
if (!this.protocol) {
110120
this.protocol = new Protocol(this.variantMap);
111-
this.boot().catch(err => {
112-
console.error(err);
113-
this.failed = true;
114-
this.progress?.();
115-
});
121+
this.boot().catch(this.onError);
116122
}
117123
this.protocol.compute(work);
118124
}

ui/ceval/src/types.ts

+5
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ export interface BrowserEngineInfo extends EngineInfo {
4747
assets: { root?: string; js?: string; wasm?: string; version?: string };
4848
}
4949

50+
export type EngineNotifier = (status?: {
51+
download?: { bytes: number; total: number };
52+
error?: string;
53+
}) => void;
54+
5055
export enum CevalState {
5156
Initial,
5257
Loading,

0 commit comments

Comments
 (0)