lila/ui/analyse/src/evalCache.ts

104 lines
2.9 KiB
TypeScript

import { defined, prop } from 'common';
import throttle from 'common/throttle';
export interface EvalCacheOpts {
variant: VariantKey;
receive(ev: Tree.ClientEval, path: Tree.Path): void;
send(t: string, d: any): void;
getNode(): Tree.Node;
canPut(): boolean;
canGet(): boolean;
}
export interface EvalCache {
onCeval(): void
fetch(path: Tree.Path, multiPv: number): void
onCloudEval(serverEval): void
}
const evalPutMinDepth = 20;
const evalPutMinNodes = 3e6;
const evalPutMaxMoves = 10;
function qualityCheck(ev: Tree.ClientEval): boolean {
// below 500k nodes, the eval might come from an imminent threefold repetition
// and should therefore be ignored
return ev.nodes > 500000 && (
ev.depth >= evalPutMinDepth || ev.nodes > evalPutMinNodes
);
}
// from client eval to server eval
function toPutData(variant: VariantKey, ev: Tree.ClientEval) {
const data: any = {
fen: ev.fen,
knodes: Math.round(ev.nodes / 1000),
depth: ev.depth,
pvs: ev.pvs.map(pv => {
return {
cp: pv.cp,
mate: pv.mate,
moves: pv.moves.slice(0, evalPutMaxMoves).join(' ')
};
})
};
if (variant !== 'standard') data.variant = variant;
return data;
}
// from server eval to client eval
function toCeval(e: Tree.ServerEval) {
const res: any = {
fen: e.fen,
nodes: e.knodes * 1000,
depth: e.depth,
pvs: e.pvs.map(function(from) {
const to: any = {
moves: from.moves.split(' ')
};
if (defined(from.cp)) to.cp = from.cp;
else to.mate = from.mate;
return to;
}),
cloud: true
};
if (defined(res.pvs[0].cp)) res.cp = res.pvs[0].cp;
else res.mate = res.pvs[0].mate;
res.cloud = true;
return res;
}
export function make(opts: EvalCacheOpts): EvalCache {
const fetchedByFen = {};
const upgradable = prop(false);
lichess.pubsub.on('socket.in.crowd', d => upgradable(d.nb > 2));
return {
onCeval: throttle(500, function() {
const node = opts.getNode(), ev = node.ceval;
if (ev && !ev.cloud && node.fen in fetchedByFen && qualityCheck(ev) && opts.canPut()) {
opts.send('evalPut', toPutData(opts.variant, ev));
}
}),
fetch(path: Tree.Path, multiPv: number): void {
const node = opts.getNode();
if ((node.ceval && node.ceval.cloud) || !opts.canGet()) return;
const serverEval = fetchedByFen[node.fen];
if (serverEval) return opts.receive(toCeval(serverEval), path);
else if (node.fen in fetchedByFen) return; // waiting for response
else fetchedByFen[node.fen] = undefined; // mark as waiting
const obj: any = {
fen: node.fen,
path
};
if (opts.variant !== 'standard') obj.variant = opts.variant;
if (multiPv > 1) obj.mpv = multiPv;
if (upgradable()) obj.up = true;
opts.send('evalGet', obj);
},
onCloudEval(serverEval) {
fetchedByFen[serverEval.fen] = serverEval;
opts.receive(toCeval(serverEval), serverEval.path);
}
};
};