feat : add user input engineWorkersNb

This commit is contained in:
GuillaumeSD
2025-05-15 00:32:05 +02:00
parent 8af6194895
commit cc9a45a45d
8 changed files with 68 additions and 14 deletions

View File

@@ -18,35 +18,42 @@ export const STRONGEST_ENGINE: EngineName = EngineName.Stockfish17;
export const ENGINE_LABELS: Record< export const ENGINE_LABELS: Record<
EngineName, EngineName,
{ small: string; full: string } { small: string; full: string; sizeMb: number }
> = { > = {
[EngineName.Stockfish17]: { [EngineName.Stockfish17]: {
full: "Stockfish 17 (75MB)", full: "Stockfish 17 (75MB)",
small: "Stockfish 17", small: "Stockfish 17",
sizeMb: 75,
}, },
[EngineName.Stockfish17Lite]: { [EngineName.Stockfish17Lite]: {
full: "Stockfish 17 Lite (6MB)", full: "Stockfish 17 Lite (6MB)",
small: "Stockfish 17 Lite", small: "Stockfish 17 Lite",
sizeMb: 6,
}, },
[EngineName.Stockfish16_1]: { [EngineName.Stockfish16_1]: {
full: "Stockfish 16.1 (64MB)", full: "Stockfish 16.1 (64MB)",
small: "Stockfish 16.1", small: "Stockfish 16.1",
sizeMb: 64,
}, },
[EngineName.Stockfish16_1Lite]: { [EngineName.Stockfish16_1Lite]: {
full: "Stockfish 16.1 Lite (6MB)", full: "Stockfish 16.1 Lite (6MB)",
small: "Stockfish 16.1 Lite", small: "Stockfish 16.1 Lite",
sizeMb: 6,
}, },
[EngineName.Stockfish16NNUE]: { [EngineName.Stockfish16NNUE]: {
full: "Stockfish 16 (40MB)", full: "Stockfish 16 (40MB)",
small: "Stockfish 16", small: "Stockfish 16",
sizeMb: 40,
}, },
[EngineName.Stockfish16]: { [EngineName.Stockfish16]: {
full: "Stockfish 16 Lite (HCE)", full: "Stockfish 16 Lite (HCE)",
small: "Stockfish 16 Lite", small: "Stockfish 16 Lite",
sizeMb: 2,
}, },
[EngineName.Stockfish11]: { [EngineName.Stockfish11]: {
full: "Stockfish 11 (HCE)", full: "Stockfish 11 (HCE)",
small: "Stockfish 11", small: "Stockfish 11",
sizeMb: 2,
}, },
}; };

View File

@@ -12,12 +12,17 @@ export const isWasmSupported = () =>
export const isMultiThreadSupported = () => { export const isMultiThreadSupported = () => {
try { try {
return SharedArrayBuffer !== undefined; return SharedArrayBuffer !== undefined && !isIosDevice();
} catch { } catch {
return false; return false;
} }
}; };
export const isIosDevice = () => /iPhone|iPad|iPod/i.test(navigator.userAgent);
export const isMobileDevice = () =>
isIosDevice() || /Android|Opera Mini/i.test(navigator.userAgent);
export const isEngineSupported = (name: EngineName): boolean => { export const isEngineSupported = (name: EngineName): boolean => {
switch (name) { switch (name) {
case EngineName.Stockfish17: case EngineName.Stockfish17:

View File

@@ -49,7 +49,6 @@ export class UciEngine {
await engine.addNewWorker(); await engine.addNewWorker();
engine.isReady = true; engine.isReady = true;
console.log(`${engineName} initialized`);
return engine; return engine;
} }
@@ -138,11 +137,11 @@ export class UciEngine {
for (const worker of this.workers) { for (const worker of this.workers) {
this.terminateWorker(worker); this.terminateWorker(worker);
} }
this.workers = [];
console.log(`${this.name} shutdown`);
} }
private terminateWorker(worker: EngineWorker) { private terminateWorker(worker: EngineWorker) {
console.log(`Terminating worker from ${this.enginePath}`);
worker.uci("quit"); worker.uci("quit");
worker.terminate?.(); worker.terminate?.();
worker.isReady = false; worker.isReady = false;

View File

@@ -1,4 +1,5 @@
import { EngineWorker } from "@/types/engine"; import { EngineWorker } from "@/types/engine";
import { isIosDevice, isMobileDevice } from "./shared";
export const getEngineWorker = (enginePath: string): EngineWorker => { export const getEngineWorker = (enginePath: string): EngineWorker => {
console.log(`Creating worker from ${enginePath}`); console.log(`Creating worker from ${enginePath}`);
@@ -47,7 +48,7 @@ export const getRecommendedWorkersNb = (): number => {
const maxWorkersNbFromThreads = Math.max( const maxWorkersNbFromThreads = Math.max(
1, 1,
navigator.hardwareConcurrency - 4, navigator.hardwareConcurrency - 4,
Math.ceil((navigator.hardwareConcurrency * 2) / 3) Math.floor((navigator.hardwareConcurrency * 2) / 3)
); );
const maxWorkersNbFromMemory = const maxWorkersNbFromMemory =
@@ -55,5 +56,11 @@ export const getRecommendedWorkersNb = (): number => {
? navigator.deviceMemory ? navigator.deviceMemory
: 4; : 4;
return Math.min(maxWorkersNbFromThreads, maxWorkersNbFromMemory, 10); const maxWorkersNbFromDevice = isIosDevice() ? 2 : isMobileDevice() ? 4 : 10;
return Math.min(
maxWorkersNbFromThreads,
maxWorkersNbFromMemory,
maxWorkersNbFromDevice
);
}; };

View File

@@ -3,6 +3,7 @@ import {
engineDepthAtom, engineDepthAtom,
engineMultiPvAtom, engineMultiPvAtom,
engineNameAtom, engineNameAtom,
engineWorkersNbAtom,
evaluationProgressAtom, evaluationProgressAtom,
gameAtom, gameAtom,
gameEvalAtom, gameEvalAtom,
@@ -19,12 +20,12 @@ 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 { 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); useCurrentPosition(engine);
const engineWorkersNb = useAtomValue(engineWorkersNbAtom);
const [evaluationProgress, setEvaluationProgress] = useAtom( const [evaluationProgress, setEvaluationProgress] = useAtom(
evaluationProgressAtom evaluationProgressAtom
); );
@@ -58,7 +59,7 @@ export default function AnalyzeButton() {
white: white?.rating, white: white?.rating,
black: black?.rating, black: black?.rating,
}, },
workersNb: getRecommendedWorkersNb(), workersNb: engineWorkersNb,
}); });
setEval(newGameEval); setEval(newGameEval);
@@ -86,6 +87,7 @@ export default function AnalyzeButton() {
}, [ }, [
engine, engine,
engineName, engineName,
engineWorkersNb,
game, game,
engineDepth, engineDepth,
engineMultiPv, engineMultiPv,

View File

@@ -1,8 +1,10 @@
import { DEFAULT_ENGINE } from "@/constants"; import { DEFAULT_ENGINE } from "@/constants";
import { getRecommendedWorkersNb } from "@/lib/engine/worker";
import { EngineName } from "@/types/enums"; import { EngineName } from "@/types/enums";
import { CurrentPosition, GameEval, SavedEvals } from "@/types/eval"; import { CurrentPosition, GameEval, SavedEvals } from "@/types/eval";
import { Chess } from "chess.js"; import { Chess } from "chess.js";
import { atom } from "jotai"; import { atom } from "jotai";
import { atomWithStorage } from "jotai/utils";
export const gameEvalAtom = atom<GameEval | undefined>(undefined); export const gameEvalAtom = atom<GameEval | undefined>(undefined);
export const gameAtom = atom(new Chess()); export const gameAtom = atom(new Chess());
@@ -16,6 +18,10 @@ export const showPlayerMoveIconAtom = atom(true);
export const engineNameAtom = atom<EngineName>(DEFAULT_ENGINE); export const engineNameAtom = atom<EngineName>(DEFAULT_ENGINE);
export const engineDepthAtom = atom(14); export const engineDepthAtom = atom(14);
export const engineMultiPvAtom = atom(3); export const engineMultiPvAtom = atom(3);
export const engineWorkersNbAtom = atomWithStorage(
"engineWorkersNb",
getRecommendedWorkersNb()
);
export const evaluationProgressAtom = atom(0); export const evaluationProgressAtom = atom(0);
export const savedEvalsAtom = atom<SavedEvals>({}); export const savedEvalsAtom = atom<SavedEvals>({});

View File

@@ -20,6 +20,7 @@ import {
engineNameAtom, engineNameAtom,
engineDepthAtom, engineDepthAtom,
engineMultiPvAtom, engineMultiPvAtom,
engineWorkersNbAtom,
} from "../analysis/states"; } from "../analysis/states";
import ArrowOptions from "./arrowOptions"; import ArrowOptions from "./arrowOptions";
import { useAtomLocalStorage } from "@/hooks/useAtomLocalStorage"; import { useAtomLocalStorage } from "@/hooks/useAtomLocalStorage";
@@ -35,6 +36,7 @@ import {
PIECE_SETS, PIECE_SETS,
STRONGEST_ENGINE, STRONGEST_ENGINE,
} from "@/constants"; } from "@/constants";
import { getRecommendedWorkersNb } from "@/lib/engine/worker";
interface Props { interface Props {
open: boolean; open: boolean;
@@ -56,6 +58,7 @@ export default function EngineSettingsDialog({ open, onClose }: Props) {
); );
const [boardHue, setBoardHue] = useAtom(boardHueAtom); const [boardHue, setBoardHue] = useAtom(boardHueAtom);
const [pieceSet, setPieceSet] = useAtom(pieceSetAtom); const [pieceSet, setPieceSet] = useAtom(pieceSetAtom);
const [engineWorkersNb, setEngineWorkersNb] = useAtom(engineWorkersNbAtom);
const theme = useTheme(); const theme = useTheme();
const isDarkMode = theme.palette.mode === "dark"; const isDarkMode = theme.palette.mode === "dark";
@@ -72,12 +75,15 @@ export default function EngineSettingsDialog({ open, onClose }: Props) {
return ( return (
<Dialog open={open} onClose={onClose} maxWidth="md" fullWidth> <Dialog open={open} onClose={onClose} maxWidth="md" fullWidth>
<DialogTitle variant="h5">Settings</DialogTitle> <DialogTitle variant="h5" sx={{ paddingBottom: 1 }}>
Settings
</DialogTitle>
<DialogContent sx={{ paddingBottom: 0 }}> <DialogContent sx={{ paddingBottom: 0 }}>
<Grid <Grid
container container
justifyContent="center" justifyContent="center"
alignItems="center" alignItems="center"
paddingTop={1}
spacing={3} spacing={3}
size={12} size={12}
> >
@@ -86,12 +92,14 @@ export default function EngineSettingsDialog({ open, onClose }: Props) {
justifyContent="center" justifyContent="center"
size={{ xs: 12, sm: 7, md: 8 }} size={{ xs: 12, sm: 7, md: 8 }}
> >
<Typography> <Typography variant="body2">
{ENGINE_LABELS[DEFAULT_ENGINE].small} is the default engine if {ENGINE_LABELS[DEFAULT_ENGINE].small} is the default engine if
your device support its requirements. It offers the best balance your device support its requirements. It offers the best balance
between speed and strength.{" "} between speed and strength.{" "}
{ENGINE_LABELS[STRONGEST_ENGINE].small} is the strongest engine {ENGINE_LABELS[STRONGEST_ENGINE].small} is the strongest engine
available, note that it requires a one time download of 75MB. available, note that it requires a one time download of{" "}
{ENGINE_LABELS[STRONGEST_ENGINE].sizeMb}MB and is much more
compute intensive.
</Typography> </Typography>
</Grid> </Grid>
@@ -162,6 +170,7 @@ export default function EngineSettingsDialog({ open, onClose }: Props) {
<Grid <Grid
container container
justifyContent="center" justifyContent="center"
alignItems="center"
size={{ xs: 12, sm: 4, md: 3 }} size={{ xs: 12, sm: 4, md: 3 }}
> >
<FormControl variant="outlined"> <FormControl variant="outlined">
@@ -194,6 +203,26 @@ export default function EngineSettingsDialog({ open, onClose }: Props) {
</Select> </Select>
</FormControl> </FormControl>
</Grid> </Grid>
<Grid container justifyContent="center" size={{ xs: 12, sm: 7 }}>
<Slider
label="Number of threads"
value={engineWorkersNb}
setValue={setEngineWorkersNb}
min={1}
max={10}
marksFilter={1}
/>
</Grid>
<Grid container justifyContent="center" size={{ xs: 12, sm: 5 }}>
<Typography variant="body2">
More threads means quicker analysis but only if your device can
handle them, otherwise it may have the opposite effect. The
estimated best value for your device is{" "}
{getRecommendedWorkersNb()}.
</Typography>
</Grid>
</Grid> </Grid>
</DialogContent> </DialogContent>
<DialogActions sx={{ m: 1 }}> <DialogActions sx={{ m: 1 }}>

View File

@@ -2,8 +2,7 @@ export interface EngineWorker {
isReady: boolean; isReady: boolean;
uci(command: string): void; uci(command: string): void;
listen: (data: string) => void; listen: (data: string) => void;
terminate?: () => void; terminate: () => void;
setNnueBuffer?: (data: Uint8Array, index?: number) => void;
} }
export interface WorkerJob { export interface WorkerJob {