Skip to content

Commit d8040d9

Browse files
Add debug-only Stockfish loading phase messages
1 parent fd55c22 commit d8040d9

2 files changed

Lines changed: 110 additions & 12 deletions

File tree

src/contexts/StockfishEngineContext.tsx

Lines changed: 69 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,49 @@ import { StockfishStatus, StockfishEngine } from 'src/types'
1111
import Engine from 'src/lib/engine/stockfish'
1212

1313
const STOCKFISH_LOADING_TOAST_DELAY_MS = 800
14+
const STOCKFISH_DEBUG_LOADING_KEY = 'maia.stockfishDebugLoading'
15+
16+
const isTruthy = (value: string | null | undefined): boolean => {
17+
if (!value) return false
18+
return ['1', 'true', 'yes', 'on'].includes(value.toLowerCase())
19+
}
20+
21+
const isStockfishDebugLoadingEnabled = (): boolean => {
22+
if (typeof window !== 'undefined') {
23+
try {
24+
const localValue = window.localStorage.getItem(STOCKFISH_DEBUG_LOADING_KEY)
25+
if (localValue !== null) return isTruthy(localValue)
26+
} catch {
27+
// ignore localStorage access failures
28+
}
29+
}
30+
31+
return isTruthy(process.env.NEXT_PUBLIC_STOCKFISH_DEBUG_LOADING)
32+
}
33+
34+
const getStockfishLoadingLabel = (
35+
engine: Engine | null,
36+
debugLoadingEnabled: boolean,
37+
): string => {
38+
if (!debugLoadingEnabled) {
39+
return 'Loading Stockfish...'
40+
}
41+
42+
if (!engine) return 'Loading Stockfish...'
43+
44+
switch (engine.initializationPhase) {
45+
case 'checking-cache':
46+
return 'Checking local Stockfish cache...'
47+
case 'downloading-nnue':
48+
return 'Downloading Stockfish model weights...'
49+
case 'loading-nnue':
50+
return 'Loading Stockfish from local cache...'
51+
case 'loading-module':
52+
return 'Starting Stockfish engine...'
53+
default:
54+
return 'Loading Stockfish...'
55+
}
56+
}
1457

1558
let sharedClientStockfishEngine: Engine | null = null
1659

@@ -63,6 +106,12 @@ export const StockfishEngineContextProvider: React.FC<{
63106
const [error, setError] = useState<string | null>(
64107
() => engineRef.current?.initializationError ?? null,
65108
)
109+
const [debugLoadingEnabled] = useState<boolean>(() =>
110+
isStockfishDebugLoadingEnabled(),
111+
)
112+
const [loadingLabel, setLoadingLabel] = useState<string>(() =>
113+
getStockfishLoadingLabel(engineRef.current, debugLoadingEnabled),
114+
)
66115
const toastId = useRef<string | null>(null)
67116
const loadingToastTimerRef = useRef<number | null>(null)
68117

@@ -90,6 +139,11 @@ export const StockfishEngineContextProvider: React.FC<{
90139
const engine = engineRef.current
91140
if (!engine) return
92141

142+
setLoadingLabel((prev) => {
143+
const next = getStockfishLoadingLabel(engine, debugLoadingEnabled)
144+
return prev === next ? prev : next
145+
})
146+
93147
if (engine.initializationError) {
94148
setStatus('error')
95149
setError(engine.initializationError)
@@ -105,7 +159,7 @@ export const StockfishEngineContextProvider: React.FC<{
105159
const interval = setInterval(checkEngineStatus, 100)
106160

107161
return () => clearInterval(interval)
108-
}, [])
162+
}, [debugLoadingEnabled])
109163

110164
// Toast notifications for Stockfish engine status
111165
useEffect(() => {
@@ -123,18 +177,25 @@ export const StockfishEngineContextProvider: React.FC<{
123177

124178
useEffect(() => {
125179
if (status === 'loading') {
126-
if (
127-
toastId.current ||
128-
loadingToastTimerRef.current !== null ||
129-
typeof window === 'undefined'
130-
) {
180+
if (typeof window === 'undefined') {
181+
return
182+
}
183+
184+
if (toastId.current) {
185+
toastId.current = toast.loading(loadingLabel, { id: toastId.current })
186+
return
187+
}
188+
189+
if (loadingToastTimerRef.current !== null) {
131190
return
132191
}
133192

134193
loadingToastTimerRef.current = window.setTimeout(() => {
135194
loadingToastTimerRef.current = null
136195
if (!toastId.current && engineRef.current && !engineRef.current.ready) {
137-
toastId.current = toast.loading('Loading Stockfish Engine...')
196+
toastId.current = toast.loading(
197+
getStockfishLoadingLabel(engineRef.current, debugLoadingEnabled),
198+
)
138199
}
139200
}, STOCKFISH_LOADING_TOAST_DELAY_MS)
140201
return
@@ -166,7 +227,7 @@ export const StockfishEngineContextProvider: React.FC<{
166227
toast.error(message)
167228
}
168229
}
169-
}, [status, error])
230+
}, [status, error, loadingLabel, debugLoadingEnabled])
170231

171232
const contextValue = useMemo(
172233
() => ({

src/lib/engine/stockfish.ts

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@ import { StockfishEvaluation } from 'src/types'
55
import { StockfishModelStorage } from './stockfishStorage'
66

77
const DEFAULT_NNUE_FETCH_TIMEOUT_MS = 30000
8+
type StockfishInitPhase =
9+
| 'idle'
10+
| 'loading-module'
11+
| 'checking-cache'
12+
| 'downloading-nnue'
13+
| 'loading-nnue'
14+
| 'ready'
15+
| 'error'
816

917
class Engine {
1018
private fen: string
@@ -25,6 +33,7 @@ class Engine {
2533
private evaluationGenerator: AsyncGenerator<StockfishEvaluation> | null
2634
private initError: string | null
2735
private initInFlight: boolean
36+
private initPhase: StockfishInitPhase
2837

2938
constructor() {
3039
this.fen = ''
@@ -39,6 +48,7 @@ class Engine {
3948
this.evaluationGenerator = null
4049
this.initError = null
4150
this.initInFlight = false
51+
this.initPhase = 'idle'
4252

4353
this.onMessage = this.onMessage.bind(this)
4454

@@ -48,7 +58,10 @@ class Engine {
4858
}
4959

5060
this.initInFlight = true
51-
setupStockfish()
61+
this.initPhase = 'loading-module'
62+
setupStockfish((phase) => {
63+
this.initPhase = phase
64+
})
5265
.then((stockfish: StockfishWeb) => {
5366
this.stockfish = stockfish
5467
stockfish.uci('uci')
@@ -59,13 +72,15 @@ class Engine {
5972
this.initError = null
6073
this.isReady = true
6174
this.nnueLoaded = true
75+
this.initPhase = 'ready'
6276
})
6377
.catch((error) => {
6478
console.error('Failed to initialize Stockfish:', error)
6579
this.initError =
6680
error instanceof Error ? error.message : 'Unknown initialization error'
6781
this.isReady = false
6882
this.nnueLoaded = false
83+
this.initPhase = 'error'
6984
})
7085
.finally(() => {
7186
this.initInFlight = false
@@ -84,6 +99,10 @@ class Engine {
8499
return this.initInFlight
85100
}
86101

102+
get initializationPhase(): StockfishInitPhase {
103+
return this.initPhase
104+
}
105+
87106
async *streamEvaluations(
88107
fen: string,
89108
legalMoveCount: number,
@@ -401,12 +420,14 @@ const loadNnueModel = async (
401420
modelUrl: string,
402421
storage: StockfishModelStorage,
403422
timeoutMs: number,
423+
onNetworkFetchStart?: () => void,
404424
): Promise<ArrayBuffer> => {
405425
const cachedModel = await storage.getModel(modelUrl)
406426
if (cachedModel) {
407427
return cachedModel
408428
}
409429

430+
onNetworkFetchStart?.()
410431
const response = await fetchWithTimeout(modelUrl, timeoutMs)
411432
if (!response.ok) {
412433
throw new Error(
@@ -419,7 +440,10 @@ const loadNnueModel = async (
419440
return buffer
420441
}
421442

422-
const setupStockfish = async (): Promise<StockfishWeb> => {
443+
const setupStockfish = async (
444+
onPhaseChange?: (phase: StockfishInitPhase) => void,
445+
): Promise<StockfishWeb> => {
446+
onPhaseChange?.('loading-module')
423447
// eslint-disable-next-line @typescript-eslint/no-explicit-any
424448
const makeModule: any = await import('lila-stockfish-web/sf17-79.js')
425449
const instance: StockfishWeb = await makeModule.default({
@@ -438,12 +462,25 @@ const setupStockfish = async (): Promise<StockfishWeb> => {
438462
const nnue0Url = `${nnueBaseUrl}/${instance.getRecommendedNnue(0)}`
439463
const nnue1Url = `${nnueBaseUrl}/${instance.getRecommendedNnue(1)}`
440464
const timeoutMs = getNnueFetchTimeoutMs()
465+
let downloadStarted = false
441466

442467
try {
468+
onPhaseChange?.('checking-cache')
443469
const buffers = await Promise.all([
444-
loadNnueModel(nnue0Url, storage, timeoutMs),
445-
loadNnueModel(nnue1Url, storage, timeoutMs),
470+
loadNnueModel(nnue0Url, storage, timeoutMs, () => {
471+
if (!downloadStarted) {
472+
downloadStarted = true
473+
onPhaseChange?.('downloading-nnue')
474+
}
475+
}),
476+
loadNnueModel(nnue1Url, storage, timeoutMs, () => {
477+
if (!downloadStarted) {
478+
downloadStarted = true
479+
onPhaseChange?.('downloading-nnue')
480+
}
481+
}),
446482
])
483+
onPhaseChange?.('loading-nnue')
447484
instance.setNnueBuffer(new Uint8Array(buffers[0]), 0)
448485
instance.setNnueBuffer(new Uint8Array(buffers[1]), 1)
449486
return instance

0 commit comments

Comments
 (0)