cancel previous opening explorer stream when opening a new one

openingexplorer3
Thibault Duplessis 2021-10-19 09:30:00 +02:00
parent 4d51f36805
commit c9fa4ea868
3 changed files with 57 additions and 41 deletions

View File

@ -17,6 +17,7 @@ import {
SimpleTablebaseHit,
ExplorerOpts,
} from './interfaces';
import { CancellableStream } from 'common/ndjson';
function pieceCount(fen: Fen) {
const parts = fen.split(/\s/);
@ -48,7 +49,8 @@ export default function (root: AnalyseCtrl, opts: ExplorerOpts, allow: boolean):
failing = prop<Error | null>(null),
hovering = prop<Hovering | null>(null),
movesAway = prop(0),
gameMenu = prop<string | null>(null);
gameMenu = prop<string | null>(null),
lastStream = prop<Promise<CancellableStream> | null>(null);
const checkHash = (e?: HashChangeEvent) => {
if ((location.hash === '#explorer' || location.hash === '#opening') && !root.embed) {
@ -85,27 +87,31 @@ export default function (root: AnalyseCtrl, opts: ExplorerOpts, allow: boolean):
failing(err);
root.redraw();
};
const prev = lastStream();
if (prev) prev.then(stream => stream.cancel());
if (withGames && tablebaseRelevant(effectiveVariant, fen))
xhr.tablebase(opts.tablebaseEndpoint, effectiveVariant, fen).then(processData, onError);
else
xhr.opening(
{
endpoint: opts.endpoint,
endpoint3: opts.endpoint3,
db: config.data.db.selected() as ExplorerDb,
personal: {
player: config.data.playerName.value(),
color: root.getOrientation(),
lastStream(
xhr.opening(
{
endpoint: opts.endpoint,
endpoint3: opts.endpoint3,
db: config.data.db.selected() as ExplorerDb,
personal: {
player: config.data.playerName.value(),
color: root.getOrientation(),
},
variant: effectiveVariant,
rootFen: root.nodeList[0].fen,
play: root.nodeList.slice(1).map(s => s.uci!),
fen,
speeds: config.data.speed.selected(),
ratings: config.data.rating.selected(),
withGames,
},
variant: effectiveVariant,
rootFen: root.nodeList[0].fen,
play: root.nodeList.slice(1).map(s => s.uci!),
fen,
speeds: config.data.speed.selected(),
ratings: config.data.rating.selected(),
withGames,
},
processData
processData
)
);
},
250,

View File

@ -1,6 +1,6 @@
import { ExplorerData, ExplorerDb, ExplorerSpeed, OpeningData, TablebaseData } from './interfaces';
import * as xhr from 'common/xhr';
import { readNdJson } from 'common/ndjson';
import { readNdJson, CancellableStream } from 'common/ndjson';
interface OpeningXhrOpts {
endpoint: string;
@ -19,7 +19,7 @@ interface OpeningXhrOpts {
withGames?: boolean;
}
export function opening(opts: OpeningXhrOpts, processData: (data: ExplorerData) => void): Promise<void> {
export function opening(opts: OpeningXhrOpts, processData: (data: ExplorerData) => void): Promise<CancellableStream> {
const endpoint = opts.db == 'player' ? opts.endpoint3 : opts.endpoint;
const url = new URL(opts.db === 'lichess' ? '/lichess' : opts.db == 'player' ? '/personal' : '/master', endpoint);
const params = url.searchParams;

View File

@ -1,31 +1,41 @@
export type ProcessLine = (line: any) => void;
export interface CancellableStream {
cancel(): void;
end: Promise<void>;
}
/*
* Utility function to read a ND-JSON HTTP stream.
* `processLine` is a function taking a JSON object. It will be called with each element of the stream.
* `response` is the result of a `fetch` request.
* https://gist.github.com/ornicar/a097406810939cf7be1df8ea30e94f3e
*/
export const readNdJson = (processLine: ProcessLine) => (response: Response) => {
const stream = response.body!.getReader();
const matcher = /\r?\n/;
const decoder = new TextDecoder();
let buf = '';
export const readNdJson =
(processLine: ProcessLine) =>
(response: Response): CancellableStream => {
const stream = response.body!.getReader();
const matcher = /\r?\n/;
const decoder = new TextDecoder();
let buf = '';
const loop = (): Promise<void> =>
stream.read().then(({ done, value }) => {
if (done) {
if (buf.length > 0) processLine(JSON.parse(buf));
return Promise.resolve();
} else {
const chunk = decoder.decode(value, { stream: true });
buf += chunk;
const parts = buf.split(matcher);
buf = parts.pop() || '';
for (const i of parts.filter(p => p)) processLine(JSON.parse(i));
return loop();
}
});
const loop = (): Promise<void> =>
stream.read().then(({ done, value }) => {
if (done) {
if (buf.length > 0) processLine(JSON.parse(buf));
return Promise.resolve();
} else {
const chunk = decoder.decode(value, { stream: true });
buf += chunk;
const parts = buf.split(matcher);
buf = parts.pop() || '';
for (const i of parts.filter(p => p)) processLine(JSON.parse(i));
return loop();
}
});
return loop();
};
return {
cancel: () => stream.cancel(),
end: loop(),
};
};