refacto : chess engine worker

This commit is contained in:
GuillaumeSD
2024-12-28 21:46:55 +01:00
parent fc4871ce2b
commit 2db361e2f6
9 changed files with 88 additions and 54 deletions

View File

@@ -16,30 +16,28 @@ export const useEngine = (engineName: EngineName | undefined) => {
return; return;
} }
const engine = pickEngine(engineName); pickEngine(engineName).then((newEngine) => {
engine.init().then(() => { setEngine((prev) => {
setEngine(engine); prev?.shutdown();
return newEngine;
});
}); });
return () => {
engine.shutdown();
};
}, [engineName]); }, [engineName]);
return engine; return engine;
}; };
const pickEngine = (engine: EngineName): UciEngine => { const pickEngine = (engine: EngineName): Promise<UciEngine> => {
switch (engine) { switch (engine) {
case EngineName.Stockfish16_1: case EngineName.Stockfish16_1:
return new Stockfish16_1(false); return Stockfish16_1.create(false);
case EngineName.Stockfish16_1Lite: case EngineName.Stockfish16_1Lite:
return new Stockfish16_1(true); return Stockfish16_1.create(true);
case EngineName.Stockfish16: case EngineName.Stockfish16:
return new Stockfish16(false); return Stockfish16.create(false);
case EngineName.Stockfish16NNUE: case EngineName.Stockfish16NNUE:
return new Stockfish16(true); return Stockfish16.create(true);
case EngineName.Stockfish11: case EngineName.Stockfish11:
return new Stockfish11(); return Stockfish11.create();
} }
}; };

View File

@@ -1,8 +1,11 @@
import { EngineName } from "@/types/enums"; import { EngineName } from "@/types/enums";
import { UciEngine } from "./uciEngine"; import { UciEngine } from "./uciEngine";
import { getEngineWorker } from "./worker";
export class Stockfish11 extends UciEngine { export class Stockfish11 {
constructor() { public static async create(): Promise<UciEngine> {
super(EngineName.Stockfish11, "engines/stockfish-11.js"); const worker = getEngineWorker("engines/stockfish-11.js");
return UciEngine.create(EngineName.Stockfish11, worker);
} }
} }

View File

@@ -1,9 +1,10 @@
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 { getEngineWorker } from "./worker";
export class Stockfish16 extends UciEngine { export class Stockfish16 {
constructor(nnue?: boolean) { public static async create(nnue?: boolean): Promise<UciEngine> {
if (!isWasmSupported()) { if (!isWasmSupported()) {
throw new Error("Stockfish 16 is not supported"); throw new Error("Stockfish 16 is not supported");
} }
@@ -15,13 +16,17 @@ export class Stockfish16 extends UciEngine {
? "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 (
await this.sendCommands( sendCommands: UciEngine["sendCommands"]
) => {
await sendCommands(
[`setoption name Use NNUE value ${!!nnue}`, "isready"], [`setoption name Use NNUE value ${!!nnue}`, "isready"],
"readyok" "readyok"
); );
}; };
super(EngineName.Stockfish16, enginePath, customEngineInit); const worker = getEngineWorker(enginePath);
return UciEngine.create(EngineName.Stockfish16, worker, customEngineInit);
} }
} }

View File

@@ -1,9 +1,10 @@
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 { getEngineWorker } from "./worker";
export class Stockfish16_1 extends UciEngine { export class Stockfish16_1 {
constructor(lite?: boolean) { public static async create(lite?: boolean): Promise<UciEngine> {
if (!isWasmSupported()) { if (!isWasmSupported()) {
throw new Error("Stockfish 16.1 is not supported"); throw new Error("Stockfish 16.1 is not supported");
} }
@@ -14,10 +15,12 @@ export class Stockfish16_1 extends UciEngine {
const enginePath = `engines/stockfish-16.1/stockfish-16.1${ const enginePath = `engines/stockfish-16.1/stockfish-16.1${
lite ? "-lite" : "" lite ? "-lite" : ""
}${multiThreadIsSupported ? "" : "-single"}.js`; }${multiThreadIsSupported ? "" : "-single"}.js`;
const engineName = lite
? EngineName.Stockfish16_1Lite
: EngineName.Stockfish16_1;
super( const worker = getEngineWorker(enginePath);
lite ? EngineName.Stockfish16_1Lite : EngineName.Stockfish16_1,
enginePath return UciEngine.create(engineName, worker);
);
} }
} }

View File

@@ -13,33 +13,36 @@ import { computeAccuracy } from "./helpers/accuracy";
import { getWhoIsCheckmated } from "../chess"; import { getWhoIsCheckmated } from "../chess";
import { getLichessEval } from "../lichess"; import { getLichessEval } from "../lichess";
import { getMovesClassification } from "./helpers/moveClassification"; import { getMovesClassification } from "./helpers/moveClassification";
import { EngineWorker } from "@/types/engine";
export abstract class UciEngine { export class UciEngine {
private worker: Worker; private worker: EngineWorker;
private ready = false; private ready = false;
private engineName: EngineName; private engineName: EngineName;
private multiPv = 3; private multiPv = 3;
private skillLevel: number | undefined = undefined; private skillLevel: number | undefined = undefined;
private customEngineInit?: () => Promise<void>;
constructor( private constructor(engineName: EngineName, worker: EngineWorker) {
engineName: EngineName,
enginePath: string,
customEngineInit?: () => Promise<void>
) {
this.engineName = engineName; this.engineName = engineName;
this.worker = new Worker(enginePath); this.worker = worker;
this.customEngineInit = customEngineInit;
console.log(`${engineName} created`);
} }
public async init(): Promise<void> { public static async create(
await this.sendCommands(["uci"], "uciok"); engineName: EngineName,
await this.setMultiPv(this.multiPv, true); worker: EngineWorker,
await this.customEngineInit?.(); customEngineInit?: (
this.ready = true; sendCommands: UciEngine["sendCommands"]
console.log(`${this.engineName} initialized`); ) => Promise<void>
): Promise<UciEngine> {
const engine = new UciEngine(engineName, worker);
await engine.sendCommands(["uci"], "uciok");
await engine.setMultiPv(engine.multiPv, true);
await customEngineInit?.(engine.sendCommands.bind(engine));
engine.ready = true;
console.log(`${engineName} initialized`);
return engine;
} }
private async setMultiPv(multiPv: number, initCase = false) { private async setMultiPv(multiPv: number, initCase = false) {
@@ -88,8 +91,8 @@ export abstract class UciEngine {
public shutdown(): void { public shutdown(): void {
this.ready = false; this.ready = false;
this.worker.postMessage("quit"); this.worker.uci("quit");
this.worker.terminate(); this.worker.terminate?.();
console.log(`${this.engineName} shutdown`); console.log(`${this.engineName} shutdown`);
} }
@@ -101,7 +104,7 @@ export abstract class UciEngine {
await this.sendCommands(["stop", "isready"], "readyok"); await this.sendCommands(["stop", "isready"], "readyok");
} }
protected async sendCommands( private async sendCommands(
commands: string[], commands: string[],
finalMessage: string, finalMessage: string,
onNewMessage?: (messages: string[]) => void onNewMessage?: (messages: string[]) => void
@@ -109,18 +112,17 @@ export abstract class UciEngine {
return new Promise((resolve) => { return new Promise((resolve) => {
const messages: string[] = []; const messages: string[] = [];
this.worker.onmessage = (event) => { this.worker.listen = (data) => {
const messageData: string = event.data; messages.push(data);
messages.push(messageData);
onNewMessage?.(messages); onNewMessage?.(messages);
if (messageData.startsWith(finalMessage)) { if (data.startsWith(finalMessage)) {
resolve(messages); resolve(messages);
} }
}; };
for (const command of commands) { for (const command of commands) {
this.worker.postMessage(command); this.worker.uci(command);
} }
}); });
} }
@@ -138,7 +140,7 @@ export abstract class UciEngine {
this.ready = false; this.ready = false;
await this.sendCommands(["ucinewgame", "isready"], "readyok"); await this.sendCommands(["ucinewgame", "isready"], "readyok");
this.worker.postMessage("position startpos"); this.worker.uci("position startpos");
const positions: PositionEval[] = []; const positions: PositionEval[] = [];
for (const fen of fens) { for (const fen of fens) {

17
src/lib/engine/worker.ts Normal file
View File

@@ -0,0 +1,17 @@
import { EngineWorker } from "@/types/engine";
export const getEngineWorker = (enginePath: string): EngineWorker => {
const worker = new Worker(enginePath);
const engineWorker: EngineWorker = {
uci: (command: string) => worker.postMessage(command),
listen: () => null,
terminate: () => worker.terminate(),
};
worker.onmessage = (event) => {
engineWorker.listen(event.data);
};
return engineWorker;
};

6
src/types/engine.ts Normal file
View File

@@ -0,0 +1,6 @@
export interface EngineWorker {
uci(command: string): void;
listen: (data: string) => void;
terminate?: () => void;
setNnueBuffer?: (data: Uint8Array, index?: number) => void;
}