fix : improve engine workers management
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 126 KiB |
@@ -12,19 +12,18 @@ const LinearProgressBar = (
|
|||||||
if (props.value === 0) return null;
|
if (props.value === 0) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid container alignItems="center" justifyContent="center" size={12}>
|
|
||||||
<Typography variant="caption" align="center">
|
|
||||||
{props.label}
|
|
||||||
</Typography>
|
|
||||||
<Grid
|
<Grid
|
||||||
container
|
container
|
||||||
width="90%"
|
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
justifyContent="center"
|
justifyContent="center"
|
||||||
wrap="nowrap"
|
wrap="nowrap"
|
||||||
|
width="90%"
|
||||||
columnGap={2}
|
columnGap={2}
|
||||||
size={12}
|
size={12}
|
||||||
>
|
>
|
||||||
|
<Typography variant="caption" align="center">
|
||||||
|
{props.label}
|
||||||
|
</Typography>
|
||||||
<Grid sx={{ width: "100%" }}>
|
<Grid sx={{ width: "100%" }}>
|
||||||
<LinearProgress
|
<LinearProgress
|
||||||
variant="determinate"
|
variant="determinate"
|
||||||
@@ -34,9 +33,7 @@ const LinearProgressBar = (
|
|||||||
height: "5px",
|
height: "5px",
|
||||||
[`&.${linearProgressClasses.colorPrimary}`]: {
|
[`&.${linearProgressClasses.colorPrimary}`]: {
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
theme.palette.grey[
|
theme.palette.grey[theme.palette.mode === "light" ? 200 : 700],
|
||||||
theme.palette.mode === "light" ? 200 : 700
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
[`& .${linearProgressClasses.bar}`]: {
|
[`& .${linearProgressClasses.bar}`]: {
|
||||||
borderRadius: 5,
|
borderRadius: 5,
|
||||||
@@ -51,7 +48,6 @@ const LinearProgressBar = (
|
|||||||
)}%`}</Typography>
|
)}%`}</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -7,10 +7,7 @@ import { UciEngine } from "@/lib/engine/uciEngine";
|
|||||||
import { EngineName } from "@/types/enums";
|
import { EngineName } from "@/types/enums";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
export const useEngine = (
|
export const useEngine = (engineName: EngineName | undefined) => {
|
||||||
engineName: EngineName | undefined,
|
|
||||||
workersNb?: number
|
|
||||||
) => {
|
|
||||||
const [engine, setEngine] = useState<UciEngine | null>(null);
|
const [engine, setEngine] = useState<UciEngine | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -20,35 +17,32 @@ export const useEngine = (
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
pickEngine(engineName, workersNb).then((newEngine) => {
|
pickEngine(engineName).then((newEngine) => {
|
||||||
setEngine((prev) => {
|
setEngine((prev) => {
|
||||||
prev?.shutdown();
|
prev?.shutdown();
|
||||||
return newEngine;
|
return newEngine;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, [engineName, workersNb]);
|
}, [engineName]);
|
||||||
|
|
||||||
return engine;
|
return engine;
|
||||||
};
|
};
|
||||||
|
|
||||||
const pickEngine = (
|
const pickEngine = (engine: EngineName): Promise<UciEngine> => {
|
||||||
engine: EngineName,
|
|
||||||
workersNb?: number
|
|
||||||
): Promise<UciEngine> => {
|
|
||||||
switch (engine) {
|
switch (engine) {
|
||||||
case EngineName.Stockfish17:
|
case EngineName.Stockfish17:
|
||||||
return Stockfish17.create(false, workersNb);
|
return Stockfish17.create(false);
|
||||||
case EngineName.Stockfish17Lite:
|
case EngineName.Stockfish17Lite:
|
||||||
return Stockfish17.create(true, workersNb);
|
return Stockfish17.create(true);
|
||||||
case EngineName.Stockfish16_1:
|
case EngineName.Stockfish16_1:
|
||||||
return Stockfish16_1.create(false, workersNb);
|
return Stockfish16_1.create(false);
|
||||||
case EngineName.Stockfish16_1Lite:
|
case EngineName.Stockfish16_1Lite:
|
||||||
return Stockfish16_1.create(true, workersNb);
|
return Stockfish16_1.create(true);
|
||||||
case EngineName.Stockfish16:
|
case EngineName.Stockfish16:
|
||||||
return Stockfish16.create(false, workersNb);
|
return Stockfish16.create(false);
|
||||||
case EngineName.Stockfish16NNUE:
|
case EngineName.Stockfish16NNUE:
|
||||||
return Stockfish16.create(true, workersNb);
|
return Stockfish16.create(true);
|
||||||
case EngineName.Stockfish11:
|
case EngineName.Stockfish11:
|
||||||
return Stockfish11.create(workersNb);
|
return Stockfish11.create();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import { EngineName } from "@/types/enums";
|
import { EngineName } from "@/types/enums";
|
||||||
import { UciEngine } from "./uciEngine";
|
import { UciEngine } from "./uciEngine";
|
||||||
import { getEngineWorkers } from "./worker";
|
|
||||||
|
|
||||||
export class Stockfish11 {
|
export class Stockfish11 {
|
||||||
public static async create(workersNb?: number): Promise<UciEngine> {
|
public static async create(): Promise<UciEngine> {
|
||||||
const workers = getEngineWorkers("engines/stockfish-11.js", workersNb);
|
const enginePath = "engines/stockfish-11.js";
|
||||||
|
|
||||||
return UciEngine.create(EngineName.Stockfish11, workers);
|
return UciEngine.create(EngineName.Stockfish11, enginePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static isSupported() {
|
public static isSupported() {
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
import { EngineName } from "@/types/enums";
|
import { EngineName } from "@/types/enums";
|
||||||
import { UciEngine } from "./uciEngine";
|
import { UciEngine } from "./uciEngine";
|
||||||
import { isMultiThreadSupported, isWasmSupported } from "./shared";
|
import { isMultiThreadSupported, isWasmSupported } from "./shared";
|
||||||
import { getEngineWorkers } from "./worker";
|
import { sendCommandsToWorker } from "./worker";
|
||||||
|
import { EngineWorker } from "@/types/engine";
|
||||||
|
|
||||||
export class Stockfish16 {
|
export class Stockfish16 {
|
||||||
public static async create(
|
public static async create(nnue?: boolean): Promise<UciEngine> {
|
||||||
nnue?: boolean,
|
|
||||||
workersNb?: number
|
|
||||||
): Promise<UciEngine> {
|
|
||||||
if (!Stockfish16.isSupported()) {
|
if (!Stockfish16.isSupported()) {
|
||||||
throw new Error("Stockfish 16 is not supported");
|
throw new Error("Stockfish 16 is not supported");
|
||||||
}
|
}
|
||||||
@@ -19,10 +17,9 @@ export class Stockfish16 {
|
|||||||
? "engines/stockfish-16/stockfish-nnue-16.js"
|
? "engines/stockfish-16/stockfish-nnue-16.js"
|
||||||
: "engines/stockfish-16/stockfish-nnue-16-single.js";
|
: "engines/stockfish-16/stockfish-nnue-16-single.js";
|
||||||
|
|
||||||
const customEngineInit = async (
|
const customEngineInit = async (worker: EngineWorker) => {
|
||||||
sendCommands: UciEngine["sendCommands"]
|
await sendCommandsToWorker(
|
||||||
) => {
|
worker,
|
||||||
await sendCommands(
|
|
||||||
[`setoption name Use NNUE value ${!!nnue}`, "isready"],
|
[`setoption name Use NNUE value ${!!nnue}`, "isready"],
|
||||||
"readyok"
|
"readyok"
|
||||||
);
|
);
|
||||||
@@ -32,9 +29,7 @@ export class Stockfish16 {
|
|||||||
? EngineName.Stockfish16NNUE
|
? EngineName.Stockfish16NNUE
|
||||||
: EngineName.Stockfish16;
|
: EngineName.Stockfish16;
|
||||||
|
|
||||||
const workers = getEngineWorkers(enginePath, workersNb);
|
return UciEngine.create(engineName, enginePath, customEngineInit);
|
||||||
|
|
||||||
return UciEngine.create(engineName, workers, customEngineInit);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static isSupported() {
|
public static isSupported() {
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
import { EngineName } from "@/types/enums";
|
import { EngineName } from "@/types/enums";
|
||||||
import { UciEngine } from "./uciEngine";
|
import { UciEngine } from "./uciEngine";
|
||||||
import { isMultiThreadSupported, isWasmSupported } from "./shared";
|
import { isMultiThreadSupported, isWasmSupported } from "./shared";
|
||||||
import { getEngineWorkers } from "./worker";
|
|
||||||
|
|
||||||
export class Stockfish16_1 {
|
export class Stockfish16_1 {
|
||||||
public static async create(
|
public static async create(lite?: boolean): Promise<UciEngine> {
|
||||||
lite?: boolean,
|
|
||||||
workersNb?: number
|
|
||||||
): Promise<UciEngine> {
|
|
||||||
if (!Stockfish16_1.isSupported()) {
|
if (!Stockfish16_1.isSupported()) {
|
||||||
throw new Error("Stockfish 16.1 is not supported");
|
throw new Error("Stockfish 16.1 is not supported");
|
||||||
}
|
}
|
||||||
@@ -23,9 +19,7 @@ export class Stockfish16_1 {
|
|||||||
? EngineName.Stockfish16_1Lite
|
? EngineName.Stockfish16_1Lite
|
||||||
: EngineName.Stockfish16_1;
|
: EngineName.Stockfish16_1;
|
||||||
|
|
||||||
const workers = getEngineWorkers(enginePath, workersNb);
|
return UciEngine.create(engineName, enginePath);
|
||||||
|
|
||||||
return UciEngine.create(engineName, workers);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static isSupported() {
|
public static isSupported() {
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
import { EngineName } from "@/types/enums";
|
import { EngineName } from "@/types/enums";
|
||||||
import { UciEngine } from "./uciEngine";
|
import { UciEngine } from "./uciEngine";
|
||||||
import { isMultiThreadSupported, isWasmSupported } from "./shared";
|
import { isMultiThreadSupported, isWasmSupported } from "./shared";
|
||||||
import { getEngineWorkers } from "./worker";
|
|
||||||
|
|
||||||
export class Stockfish17 {
|
export class Stockfish17 {
|
||||||
public static async create(
|
public static async create(lite?: boolean): Promise<UciEngine> {
|
||||||
lite?: boolean,
|
|
||||||
workersNb?: number
|
|
||||||
): Promise<UciEngine> {
|
|
||||||
if (!Stockfish17.isSupported()) {
|
if (!Stockfish17.isSupported()) {
|
||||||
throw new Error("Stockfish 17 is not supported");
|
throw new Error("Stockfish 17 is not supported");
|
||||||
}
|
}
|
||||||
@@ -23,9 +19,7 @@ export class Stockfish17 {
|
|||||||
? EngineName.Stockfish17Lite
|
? EngineName.Stockfish17Lite
|
||||||
: EngineName.Stockfish17;
|
: EngineName.Stockfish17;
|
||||||
|
|
||||||
const workers = getEngineWorkers(enginePath, workersNb);
|
return UciEngine.create(engineName, enginePath);
|
||||||
|
|
||||||
return UciEngine.create(engineName, workers);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static isSupported() {
|
public static isSupported() {
|
||||||
|
|||||||
@@ -15,38 +15,41 @@ import { getLichessEval } from "../lichess";
|
|||||||
import { getMovesClassification } from "./helpers/moveClassification";
|
import { getMovesClassification } from "./helpers/moveClassification";
|
||||||
import { computeEstimatedElo } from "./helpers/estimateElo";
|
import { computeEstimatedElo } from "./helpers/estimateElo";
|
||||||
import { EngineWorker, WorkerJob } from "@/types/engine";
|
import { EngineWorker, WorkerJob } from "@/types/engine";
|
||||||
|
import { getEngineWorker, sendCommandsToWorker } from "./worker";
|
||||||
|
|
||||||
export class UciEngine {
|
export class UciEngine {
|
||||||
private workers: EngineWorker[];
|
public readonly name: EngineName;
|
||||||
|
private workers: EngineWorker[] = [];
|
||||||
private workerQueue: WorkerJob[] = [];
|
private workerQueue: WorkerJob[] = [];
|
||||||
private isReady = false;
|
private isReady = false;
|
||||||
private engineName: EngineName;
|
private enginePath: string;
|
||||||
|
private customEngineInit?:
|
||||||
|
| ((worker: EngineWorker) => Promise<void>)
|
||||||
|
| undefined = undefined;
|
||||||
private multiPv = 3;
|
private multiPv = 3;
|
||||||
private elo: number | undefined = undefined;
|
private elo: number | undefined = undefined;
|
||||||
|
|
||||||
private constructor(engineName: EngineName, workers: EngineWorker[]) {
|
private constructor(
|
||||||
this.engineName = engineName;
|
engineName: EngineName,
|
||||||
this.workers = workers;
|
enginePath: string,
|
||||||
|
customEngineInit: UciEngine["customEngineInit"]
|
||||||
|
) {
|
||||||
|
this.name = engineName;
|
||||||
|
this.enginePath = enginePath;
|
||||||
|
this.customEngineInit = customEngineInit;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async create(
|
public static async create(
|
||||||
engineName: EngineName,
|
engineName: EngineName,
|
||||||
workers: EngineWorker[],
|
enginePath: string,
|
||||||
customEngineInit?: (
|
customEngineInit?: UciEngine["customEngineInit"]
|
||||||
sendCommands: UciEngine["sendCommands"]
|
|
||||||
) => Promise<void>
|
|
||||||
): Promise<UciEngine> {
|
): Promise<UciEngine> {
|
||||||
const engine = new UciEngine(engineName, workers);
|
const engine = new UciEngine(engineName, enginePath, customEngineInit);
|
||||||
|
|
||||||
await engine.broadcastCommands(["uci"], "uciok");
|
await engine.addNewWorker();
|
||||||
await engine.setMultiPv(engine.multiPv, true);
|
|
||||||
await customEngineInit?.(engine.sendCommands.bind(engine));
|
|
||||||
for (const worker of workers) {
|
|
||||||
worker.isReady = true;
|
|
||||||
}
|
|
||||||
engine.isReady = true;
|
engine.isReady = true;
|
||||||
|
|
||||||
console.log(`${engineName} initialized with ${workers.length} workers`);
|
console.log(`${engineName} initialized`);
|
||||||
return engine;
|
return engine;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,31 +64,34 @@ export class UciEngine {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
private releaseWorker(worker: EngineWorker) {
|
private async releaseWorker(worker: EngineWorker) {
|
||||||
worker.isReady = true;
|
|
||||||
const nextJob = this.workerQueue.shift();
|
const nextJob = this.workerQueue.shift();
|
||||||
|
if (!nextJob) {
|
||||||
|
worker.isReady = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (nextJob) {
|
const res = await sendCommandsToWorker(
|
||||||
this.sendCommands(
|
worker,
|
||||||
nextJob.commands,
|
nextJob.commands,
|
||||||
nextJob.finalMessage,
|
nextJob.finalMessage,
|
||||||
nextJob.onNewMessage
|
nextJob.onNewMessage
|
||||||
).then(nextJob.resolve);
|
);
|
||||||
}
|
|
||||||
|
this.releaseWorker(worker);
|
||||||
|
nextJob.resolve(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async setMultiPv(multiPv: number, initCase = false) {
|
private async setMultiPv(multiPv: number) {
|
||||||
if (!initCase) {
|
|
||||||
if (multiPv === this.multiPv) return;
|
if (multiPv === this.multiPv) return;
|
||||||
|
|
||||||
this.throwErrorIfNotReady();
|
this.throwErrorIfNotReady();
|
||||||
}
|
|
||||||
|
|
||||||
if (multiPv < 2 || multiPv > 6) {
|
if (multiPv < 2 || multiPv > 6) {
|
||||||
throw new Error(`Invalid MultiPV value : ${multiPv}`);
|
throw new Error(`Invalid MultiPV value : ${multiPv}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.broadcastCommands(
|
await this.sendCommandsToEachWorker(
|
||||||
[`setoption name MultiPV value ${multiPv}`, "isready"],
|
[`setoption name MultiPV value ${multiPv}`, "isready"],
|
||||||
"readyok"
|
"readyok"
|
||||||
);
|
);
|
||||||
@@ -93,23 +99,21 @@ export class UciEngine {
|
|||||||
this.multiPv = multiPv;
|
this.multiPv = multiPv;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async setElo(elo: number, initCase = false) {
|
private async setElo(elo: number) {
|
||||||
if (!initCase) {
|
|
||||||
if (elo === this.elo) return;
|
if (elo === this.elo) return;
|
||||||
|
|
||||||
this.throwErrorIfNotReady();
|
this.throwErrorIfNotReady();
|
||||||
}
|
|
||||||
|
|
||||||
if (elo < 1320 || elo > 3190) {
|
if (elo < 1320 || elo > 3190) {
|
||||||
throw new Error(`Invalid Elo value : ${elo}`);
|
throw new Error(`Invalid Elo value : ${elo}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.broadcastCommands(
|
await this.sendCommandsToEachWorker(
|
||||||
["setoption name UCI_LimitStrength value true", "isready"],
|
["setoption name UCI_LimitStrength value true", "isready"],
|
||||||
"readyok"
|
"readyok"
|
||||||
);
|
);
|
||||||
|
|
||||||
await this.broadcastCommands(
|
await this.sendCommandsToEachWorker(
|
||||||
[`setoption name UCI_Elo value ${elo}`, "isready"],
|
[`setoption name UCI_Elo value ${elo}`, "isready"],
|
||||||
"readyok"
|
"readyok"
|
||||||
);
|
);
|
||||||
@@ -117,9 +121,13 @@ export class UciEngine {
|
|||||||
this.elo = elo;
|
this.elo = elo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getIsReady(): boolean {
|
||||||
|
return this.isReady;
|
||||||
|
}
|
||||||
|
|
||||||
private throwErrorIfNotReady() {
|
private throwErrorIfNotReady() {
|
||||||
if (!this.isReady) {
|
if (!this.isReady) {
|
||||||
throw new Error(`${this.engineName} is not ready`);
|
throw new Error(`${this.name} is not ready`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,21 +136,21 @@ export class UciEngine {
|
|||||||
this.workerQueue = [];
|
this.workerQueue = [];
|
||||||
|
|
||||||
for (const worker of this.workers) {
|
for (const worker of this.workers) {
|
||||||
|
this.terminateWorker(worker);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`${this.name} shutdown`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private terminateWorker(worker: EngineWorker) {
|
||||||
worker.uci("quit");
|
worker.uci("quit");
|
||||||
worker.terminate?.();
|
worker.terminate?.();
|
||||||
worker.isReady = false;
|
worker.isReady = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`${this.engineName} shutdown`);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getIsReady(): boolean {
|
|
||||||
return this.isReady;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async stopSearch(): Promise<void> {
|
public async stopSearch(): Promise<void> {
|
||||||
this.workerQueue = [];
|
this.workerQueue = [];
|
||||||
await this.broadcastCommands(["stop", "isready"], "readyok");
|
await this.sendCommandsToEachWorker(["stop", "isready"], "readyok");
|
||||||
|
|
||||||
for (const worker of this.workers) {
|
for (const worker of this.workers) {
|
||||||
this.releaseWorker(worker);
|
this.releaseWorker(worker);
|
||||||
@@ -167,46 +175,74 @@ export class UciEngine {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.sendCommandsToWorker(
|
const res = await sendCommandsToWorker(
|
||||||
worker,
|
worker,
|
||||||
commands,
|
commands,
|
||||||
finalMessage,
|
finalMessage,
|
||||||
onNewMessage
|
onNewMessage
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
private async sendCommandsToWorker(
|
|
||||||
worker: EngineWorker,
|
|
||||||
commands: string[],
|
|
||||||
finalMessage: string,
|
|
||||||
onNewMessage?: (messages: string[]) => void
|
|
||||||
): Promise<string[]> {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
const messages: string[] = [];
|
|
||||||
worker.listen = (data) => {
|
|
||||||
messages.push(data);
|
|
||||||
onNewMessage?.(messages);
|
|
||||||
|
|
||||||
if (data.startsWith(finalMessage)) {
|
|
||||||
this.releaseWorker(worker);
|
this.releaseWorker(worker);
|
||||||
resolve(messages);
|
return res;
|
||||||
}
|
|
||||||
};
|
|
||||||
for (const command of commands) {
|
|
||||||
worker.uci(command);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async broadcastCommands(
|
private async sendCommandsToEachWorker(
|
||||||
commands: string[],
|
commands: string[],
|
||||||
finalMessage: string,
|
finalMessage: string,
|
||||||
onNewMessage?: (messages: string[]) => void
|
onNewMessage?: (messages: string[]) => void
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
this.workers.map((worker) =>
|
this.workers.map(async (worker) => {
|
||||||
this.sendCommandsToWorker(worker, commands, finalMessage, onNewMessage)
|
await sendCommandsToWorker(
|
||||||
)
|
worker,
|
||||||
|
commands,
|
||||||
|
finalMessage,
|
||||||
|
onNewMessage
|
||||||
|
);
|
||||||
|
this.releaseWorker(worker);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async addNewWorker() {
|
||||||
|
const worker = getEngineWorker(this.enginePath);
|
||||||
|
|
||||||
|
await sendCommandsToWorker(worker, ["uci"], "uciok");
|
||||||
|
await sendCommandsToWorker(
|
||||||
|
worker,
|
||||||
|
[`setoption name MultiPV value ${this.multiPv}`, "isready"],
|
||||||
|
"readyok"
|
||||||
|
);
|
||||||
|
await this.customEngineInit?.(worker);
|
||||||
|
await sendCommandsToWorker(worker, ["ucinewgame", "isready"], "readyok");
|
||||||
|
|
||||||
|
this.workers.push(worker);
|
||||||
|
this.releaseWorker(worker);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async setWorkersNb(workersNb: number) {
|
||||||
|
if (workersNb === this.workers.length) return;
|
||||||
|
|
||||||
|
if (workersNb < 1) {
|
||||||
|
throw new Error(
|
||||||
|
`Number of workers must be greater than 0, got ${workersNb} instead`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (workersNb < this.workers.length) {
|
||||||
|
const workersToRemove = this.workers.slice(workersNb);
|
||||||
|
this.workers = this.workers.slice(0, workersNb);
|
||||||
|
|
||||||
|
for (const worker of workersToRemove) {
|
||||||
|
this.terminateWorker(worker);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const workersNbToCreate = workersNb - this.workers.length;
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
new Array(workersNbToCreate).fill(0).map(() => this.addNewWorker())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,13 +253,15 @@ export class UciEngine {
|
|||||||
multiPv = this.multiPv,
|
multiPv = this.multiPv,
|
||||||
setEvaluationProgress,
|
setEvaluationProgress,
|
||||||
playersRatings,
|
playersRatings,
|
||||||
|
workersNb = 1,
|
||||||
}: EvaluateGameParams): Promise<GameEval> {
|
}: EvaluateGameParams): Promise<GameEval> {
|
||||||
this.throwErrorIfNotReady();
|
this.throwErrorIfNotReady();
|
||||||
setEvaluationProgress?.(1);
|
setEvaluationProgress?.(1);
|
||||||
await this.setMultiPv(multiPv);
|
await this.setMultiPv(multiPv);
|
||||||
this.isReady = false;
|
this.isReady = false;
|
||||||
|
|
||||||
await this.broadcastCommands(["ucinewgame", "isready"], "readyok");
|
await this.sendCommandsToEachWorker(["ucinewgame", "isready"], "readyok");
|
||||||
|
this.setWorkersNb(workersNb);
|
||||||
|
|
||||||
const positions: PositionEval[] = new Array(fens.length);
|
const positions: PositionEval[] = new Array(fens.length);
|
||||||
let completed = 0;
|
let completed = 0;
|
||||||
@@ -267,10 +305,11 @@ export class UciEngine {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await this.evaluatePosition(fen, depth);
|
const result = await this.evaluatePosition(fen, depth, workersNb);
|
||||||
updateEval(i, result);
|
updateEval(i, result);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
await this.setWorkersNb(1);
|
||||||
|
|
||||||
const positionsWithClassification = getMovesClassification(
|
const positionsWithClassification = getMovesClassification(
|
||||||
positions,
|
positions,
|
||||||
@@ -290,7 +329,7 @@ export class UciEngine {
|
|||||||
estimatedElo,
|
estimatedElo,
|
||||||
accuracy,
|
accuracy,
|
||||||
settings: {
|
settings: {
|
||||||
engine: this.engineName,
|
engine: this.name,
|
||||||
date: new Date().toISOString(),
|
date: new Date().toISOString(),
|
||||||
depth,
|
depth,
|
||||||
multiPv,
|
multiPv,
|
||||||
@@ -300,9 +339,10 @@ export class UciEngine {
|
|||||||
|
|
||||||
private async evaluatePosition(
|
private async evaluatePosition(
|
||||||
fen: string,
|
fen: string,
|
||||||
depth = 16
|
depth = 16,
|
||||||
|
workersNb: number
|
||||||
): Promise<PositionEval> {
|
): Promise<PositionEval> {
|
||||||
if (this.workers.length < 2) {
|
if (workersNb < 2) {
|
||||||
const lichessEval = await getLichessEval(fen, this.multiPv);
|
const lichessEval = await getLichessEval(fen, this.multiPv);
|
||||||
if (
|
if (
|
||||||
lichessEval.lines.length >= this.multiPv &&
|
lichessEval.lines.length >= this.multiPv &&
|
||||||
|
|||||||
@@ -1,30 +1,8 @@
|
|||||||
import { EngineWorker } from "@/types/engine";
|
import { EngineWorker } from "@/types/engine";
|
||||||
|
|
||||||
export const getEngineWorkers = (
|
export const getEngineWorker = (enginePath: string): EngineWorker => {
|
||||||
enginePath: string,
|
console.log(`Creating worker from ${enginePath}`);
|
||||||
workersInputNb?: number
|
|
||||||
): EngineWorker[] => {
|
|
||||||
if (workersInputNb !== undefined && workersInputNb < 1) {
|
|
||||||
throw new Error(
|
|
||||||
`Number of workers must be greater than 0, got ${workersInputNb} instead`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const engineWorkers: EngineWorker[] = [];
|
|
||||||
|
|
||||||
const maxWorkersNb = Math.max(
|
|
||||||
1,
|
|
||||||
navigator.hardwareConcurrency - 4,
|
|
||||||
Math.ceil((navigator.hardwareConcurrency * 2) / 3)
|
|
||||||
);
|
|
||||||
const deviceMemory =
|
|
||||||
"deviceMemory" in navigator && typeof navigator.deviceMemory === "number"
|
|
||||||
? navigator.deviceMemory
|
|
||||||
: 4;
|
|
||||||
const workersNb = workersInputNb ?? Math.min(maxWorkersNb, deviceMemory, 10);
|
|
||||||
console.log(`Starting ${workersNb} workers from ${enginePath}`);
|
|
||||||
|
|
||||||
for (let i = 0; i < workersNb; i++) {
|
|
||||||
const worker = new window.Worker(enginePath);
|
const worker = new window.Worker(enginePath);
|
||||||
|
|
||||||
const engineWorker: EngineWorker = {
|
const engineWorker: EngineWorker = {
|
||||||
@@ -38,8 +16,44 @@ export const getEngineWorkers = (
|
|||||||
engineWorker.listen(event.data);
|
engineWorker.listen(event.data);
|
||||||
};
|
};
|
||||||
|
|
||||||
engineWorkers.push(engineWorker);
|
return engineWorker;
|
||||||
}
|
};
|
||||||
|
|
||||||
return engineWorkers;
|
export const sendCommandsToWorker = (
|
||||||
|
worker: EngineWorker,
|
||||||
|
commands: string[],
|
||||||
|
finalMessage: string,
|
||||||
|
onNewMessage?: (messages: string[]) => void
|
||||||
|
): Promise<string[]> => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const messages: string[] = [];
|
||||||
|
|
||||||
|
worker.listen = (data) => {
|
||||||
|
messages.push(data);
|
||||||
|
onNewMessage?.(messages);
|
||||||
|
|
||||||
|
if (data.startsWith(finalMessage)) {
|
||||||
|
resolve(messages);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const command of commands) {
|
||||||
|
worker.uci(command);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getRecommendedWorkersNb = (): number => {
|
||||||
|
const maxWorkersNbFromThreads = Math.max(
|
||||||
|
1,
|
||||||
|
navigator.hardwareConcurrency - 4,
|
||||||
|
Math.ceil((navigator.hardwareConcurrency * 2) / 3)
|
||||||
|
);
|
||||||
|
|
||||||
|
const maxWorkersNbFromMemory =
|
||||||
|
"deviceMemory" in navigator && typeof navigator.deviceMemory === "number"
|
||||||
|
? navigator.deviceMemory
|
||||||
|
: 4;
|
||||||
|
|
||||||
|
return Math.min(maxWorkersNbFromThreads, maxWorkersNbFromMemory, 10);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,15 +10,13 @@ import {
|
|||||||
import { CurrentPosition, PositionEval } from "@/types/eval";
|
import { CurrentPosition, PositionEval } from "@/types/eval";
|
||||||
import { useAtom, useAtomValue } from "jotai";
|
import { useAtom, useAtomValue } from "jotai";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useEngine } from "../../../hooks/useEngine";
|
|
||||||
import { EngineName } from "@/types/enums";
|
|
||||||
import { getEvaluateGameParams } from "@/lib/chess";
|
import { getEvaluateGameParams } from "@/lib/chess";
|
||||||
import { getMovesClassification } from "@/lib/engine/helpers/moveClassification";
|
import { getMovesClassification } from "@/lib/engine/helpers/moveClassification";
|
||||||
import { openings } from "@/data/openings";
|
import { openings } from "@/data/openings";
|
||||||
|
import { UciEngine } from "@/lib/engine/uciEngine";
|
||||||
|
|
||||||
export const useCurrentPosition = (engineName?: EngineName) => {
|
export const useCurrentPosition = (engine: UciEngine | null) => {
|
||||||
const [currentPosition, setCurrentPosition] = useAtom(currentPositionAtom);
|
const [currentPosition, setCurrentPosition] = useAtom(currentPositionAtom);
|
||||||
const engine = useEngine(engineName, 1);
|
|
||||||
const gameEval = useAtomValue(gameEvalAtom);
|
const gameEval = useAtomValue(gameEvalAtom);
|
||||||
const game = useAtomValue(gameAtom);
|
const game = useAtomValue(gameAtom);
|
||||||
const board = useAtomValue(boardAtom);
|
const board = useAtomValue(boardAtom);
|
||||||
@@ -77,7 +75,7 @@ export const useCurrentPosition = (engineName?: EngineName) => {
|
|||||||
if (
|
if (
|
||||||
!position.eval &&
|
!position.eval &&
|
||||||
engine?.getIsReady() &&
|
engine?.getIsReady() &&
|
||||||
engineName &&
|
engine.name &&
|
||||||
!board.isCheckmate() &&
|
!board.isCheckmate() &&
|
||||||
!board.isStalemate()
|
!board.isStalemate()
|
||||||
) {
|
) {
|
||||||
@@ -85,12 +83,13 @@ export const useCurrentPosition = (engineName?: EngineName) => {
|
|||||||
fen: string,
|
fen: string,
|
||||||
setPartialEval?: (positionEval: PositionEval) => void
|
setPartialEval?: (positionEval: PositionEval) => void
|
||||||
) => {
|
) => {
|
||||||
if (!engine?.getIsReady() || !engineName)
|
if (!engine.getIsReady()) {
|
||||||
throw new Error("Engine not ready");
|
throw new Error("Engine not ready");
|
||||||
|
}
|
||||||
const savedEval = savedEvals[fen];
|
const savedEval = savedEvals[fen];
|
||||||
if (
|
if (
|
||||||
savedEval &&
|
savedEval &&
|
||||||
savedEval.engine === engineName &&
|
savedEval.engine === engine.name &&
|
||||||
(savedEval.lines?.length ?? 0) >= multiPv &&
|
(savedEval.lines?.length ?? 0) >= multiPv &&
|
||||||
(savedEval.lines[0].depth ?? 0) >= depth
|
(savedEval.lines[0].depth ?? 0) >= depth
|
||||||
) {
|
) {
|
||||||
@@ -111,7 +110,7 @@ export const useCurrentPosition = (engineName?: EngineName) => {
|
|||||||
|
|
||||||
setSavedEvals((prev) => ({
|
setSavedEvals((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
[fen]: { ...rawPositionEval, engine: engineName },
|
[fen]: { ...rawPositionEval, engine: engine.name },
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return rawPositionEval;
|
return rawPositionEval;
|
||||||
@@ -165,7 +164,9 @@ export const useCurrentPosition = (engineName?: EngineName) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
if (engine?.getIsReady()) {
|
||||||
engine?.stopSearch();
|
engine?.stopSearch();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [gameEval, board, game, engine, depth, multiPv]);
|
}, [gameEval, board, game, engine, depth, multiPv]);
|
||||||
|
|||||||
@@ -7,13 +7,12 @@ import {
|
|||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
import {
|
import {
|
||||||
boardAtom,
|
boardAtom,
|
||||||
|
currentPositionAtom,
|
||||||
engineMultiPvAtom,
|
engineMultiPvAtom,
|
||||||
engineNameAtom,
|
|
||||||
gameAtom,
|
gameAtom,
|
||||||
gameEvalAtom,
|
gameEvalAtom,
|
||||||
} from "../../states";
|
} from "../../states";
|
||||||
import LineEvaluation from "./lineEvaluation";
|
import LineEvaluation from "./lineEvaluation";
|
||||||
import { useCurrentPosition } from "../../hooks/useCurrentPosition";
|
|
||||||
import { LineEval } from "@/types/eval";
|
import { LineEval } from "@/types/eval";
|
||||||
import PlayersMetric from "./playersMetric";
|
import PlayersMetric from "./playersMetric";
|
||||||
import MoveInfo from "./moveInfo";
|
import MoveInfo from "./moveInfo";
|
||||||
@@ -21,8 +20,7 @@ import Opening from "./opening";
|
|||||||
|
|
||||||
export default function AnalysisTab(props: GridProps) {
|
export default function AnalysisTab(props: GridProps) {
|
||||||
const linesNumber = useAtomValue(engineMultiPvAtom);
|
const linesNumber = useAtomValue(engineMultiPvAtom);
|
||||||
const engineName = useAtomValue(engineNameAtom);
|
const position = useAtomValue(currentPositionAtom);
|
||||||
const position = useCurrentPosition(engineName);
|
|
||||||
const game = useAtomValue(gameAtom);
|
const game = useAtomValue(gameAtom);
|
||||||
const board = useAtomValue(boardAtom);
|
const board = useAtomValue(boardAtom);
|
||||||
const gameEval = useAtomValue(gameEvalAtom);
|
const gameEval = useAtomValue(gameEvalAtom);
|
||||||
|
|||||||
@@ -18,10 +18,13 @@ import { SavedEvals } from "@/types/eval";
|
|||||||
import { useEffect, useCallback } from "react";
|
import { useEffect, useCallback } from "react";
|
||||||
import { usePlayersData } from "@/hooks/usePlayersData";
|
import { usePlayersData } from "@/hooks/usePlayersData";
|
||||||
import { Typography } from "@mui/material";
|
import { Typography } from "@mui/material";
|
||||||
|
import { useCurrentPosition } from "../hooks/useCurrentPosition";
|
||||||
|
import { getRecommendedWorkersNb } from "@/lib/engine/worker";
|
||||||
|
|
||||||
export default function AnalyzeButton() {
|
export default function AnalyzeButton() {
|
||||||
const engineName = useAtomValue(engineNameAtom);
|
const engineName = useAtomValue(engineNameAtom);
|
||||||
const engine = useEngine(engineName);
|
const engine = useEngine(engineName);
|
||||||
|
useCurrentPosition(engine);
|
||||||
const [evaluationProgress, setEvaluationProgress] = useAtom(
|
const [evaluationProgress, setEvaluationProgress] = useAtom(
|
||||||
evaluationProgressAtom
|
evaluationProgressAtom
|
||||||
);
|
);
|
||||||
@@ -55,6 +58,7 @@ export default function AnalyzeButton() {
|
|||||||
white: white?.rating,
|
white: white?.rating,
|
||||||
black: black?.rating,
|
black: black?.rating,
|
||||||
},
|
},
|
||||||
|
workersNb: getRecommendedWorkersNb(),
|
||||||
});
|
});
|
||||||
|
|
||||||
setEval(newGameEval);
|
setEval(newGameEval);
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import { usePlayersData } from "@/hooks/usePlayersData";
|
|||||||
export default function BoardContainer() {
|
export default function BoardContainer() {
|
||||||
const screenSize = useScreenSize();
|
const screenSize = useScreenSize();
|
||||||
const engineName = useAtomValue(enginePlayNameAtom);
|
const engineName = useAtomValue(enginePlayNameAtom);
|
||||||
const engine = useEngine(engineName, 1);
|
const engine = useEngine(engineName);
|
||||||
const game = useAtomValue(gameAtom);
|
const game = useAtomValue(gameAtom);
|
||||||
const { white, black } = usePlayersData(gameAtom);
|
const { white, black } = usePlayersData(gameAtom);
|
||||||
const playerColor = useAtomValue(playerColorAtom);
|
const playerColor = useAtomValue(playerColorAtom);
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ export interface EvaluateGameParams {
|
|||||||
multiPv?: number;
|
multiPv?: number;
|
||||||
setEvaluationProgress?: (value: number) => void;
|
setEvaluationProgress?: (value: number) => void;
|
||||||
playersRatings?: { white?: number; black?: number };
|
playersRatings?: { white?: number; black?: number };
|
||||||
|
workersNb?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SavedEval {
|
export interface SavedEval {
|
||||||
|
|||||||
Reference in New Issue
Block a user