lila/ui/analyse/src/socket.ts

171 lines
4.3 KiB
TypeScript

import { initial as initialBoardFen } from 'chessground/fen';
import { ops as treeOps } from 'tree';
import AnalyseCtrl from './ctrl';
type DestCache = {
[fen: string]: DestCacheEntry;
};
type DestCacheEntry = {
path: string;
dests: string;
};
interface Handlers {
[key: string]: any; // TODO
}
// TODO: Split into request types
export interface Req {
ch?: string; // TODO: needs to be defined in studies before sending
sticky?: boolean;
write?: boolean;
path: string;
variant?: VariantKey;
fen?: string;
shapes?: Tree.Shape[];
jumpTo?: Tree.Path;
toMainline?: boolean;
force?: boolean;
}
export interface Socket {
send: SocketSend;
receive(type: string, data: any): boolean;
sendAnaMove(req: Req): void;
sendAnaDrop(req: Req): void;
sendAnaDests(req: Req): void;
sendForecasts(req: Req): void;
clearCache(): void;
}
export function make(send: SocketSend, ctrl: AnalyseCtrl): Socket {
let anaMoveTimeout: number | undefined;
let anaDestsTimeout: number | undefined;
let anaDestsCache: DestCache = {};
function clearCache() {
anaDestsCache =
ctrl.data.game.variant.key === 'standard' && ctrl.tree.root.fen.split(' ', 1)[0] === initialBoardFen
? {
'': {
path: '',
dests: 'iqy muC gvx ltB bqs pxF jrz nvD ksA owE',
},
}
: {};
}
clearCache();
// forecast mode: reload when opponent moves
if (!ctrl.synthetic)
setTimeout(function () {
send('startWatching', ctrl.data.game.id);
}, 1000);
function currentChapterId(): string | undefined {
if (ctrl.study) return ctrl.study.vm.chapterId;
return undefined;
}
function addStudyData(req: Req, isWrite = false): void {
const c = currentChapterId();
if (c) {
req.ch = c;
if (isWrite) {
if (ctrl.study!.isWriting()) {
if (!ctrl.study!.vm.mode.sticky) req.sticky = false;
} else req.write = false;
}
}
}
const handlers: Handlers = {
node(data) {
clearTimeout(anaMoveTimeout);
// no strict equality here!
if (data.ch == currentChapterId()) ctrl.addNode(data.node, data.path);
else console.log('socket handler node got wrong chapter id', data);
},
stepFailure() {
clearTimeout(anaMoveTimeout);
ctrl.reset();
},
dests(data) {
clearTimeout(anaDestsTimeout);
if (!data.ch || data.ch === currentChapterId()) {
anaDestsCache[data.path] = data;
ctrl.addDests(data.dests, data.path);
} else console.log('socket handler node got wrong chapter id', data);
},
destsFailure(data) {
console.log(data);
clearTimeout(anaDestsTimeout);
},
fen(e) {
if (ctrl.forecast && e.id === ctrl.data.game.id && treeOps.last(ctrl.mainline)!.fen.indexOf(e.fen) !== 0) {
ctrl.forecast.reloadToLastPly();
}
},
analysisProgress(data) {
ctrl.mergeAnalysisData(data);
},
evalHit(e) {
ctrl.evalCache.onCloudEval(e);
},
};
function withoutStandardVariant(obj) {
if (obj.variant === 'standard') delete obj.variant;
}
function sendAnaDests(req: Req) {
clearTimeout(anaDestsTimeout);
if (anaDestsCache[req.path])
setTimeout(function () {
handlers.dests(anaDestsCache[req.path]);
}, 300);
else {
withoutStandardVariant(req);
addStudyData(req);
send('anaDests', req);
anaDestsTimeout = setTimeout(function () {
console.log(req, 'resendAnaDests');
sendAnaDests(req);
}, 3000);
}
}
function sendAnaMove(req: Req) {
clearTimeout(anaMoveTimeout);
withoutStandardVariant(req);
addStudyData(req, true);
send('anaMove', req);
anaMoveTimeout = setTimeout(() => sendAnaMove(req), 3000);
}
function sendAnaDrop(req: Req) {
clearTimeout(anaMoveTimeout);
withoutStandardVariant(req);
addStudyData(req, true);
send('anaDrop', req);
anaMoveTimeout = setTimeout(() => sendAnaDrop(req), 3000);
}
return {
receive(type: string, data: any): boolean {
const handler = handlers[type];
if (handler) handler(data);
return !!ctrl.study && ctrl.study.socketHandler(type, data);
},
sendAnaMove,
sendAnaDrop,
sendAnaDests,
sendForecasts(req) {
send('forecasts', req);
},
clearCache,
send,
};
}