Skip to content

Commit 60ef3f9

Browse files
fix: background drill analysis cancelled on every move
Root cause: currentDrillGame state changes on every player/Maia move (updating moves array and playerMoveCount). The cancellation effect depended on currentDrillGame, so every move triggered cleanup which set backgroundCancelledRef=true, killing the loop immediately. Fix: track drill identity by ID instead of the full state object. Only cancel when a genuinely new drill starts. Also stop background analysis when post-drill analysis begins to avoid stockfish contention. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 09ed63f commit 60ef3f9

1 file changed

Lines changed: 61 additions & 9 deletions

File tree

src/hooks/useOpeningDrillController/useOpeningDrillController.ts

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -914,28 +914,60 @@ export const useOpeningDrillController = (
914914
const backgroundRunningRef = useRef(false)
915915
const backgroundCancelledRef = useRef(false)
916916

917-
// The long-lived loop that drains the queue
917+
// Refs to the latest versions of the analysis functions so the long-lived
918+
// loop always calls the current closure without needing to restart.
919+
const ensureMaiaRef = useRef(ensureMaiaForNode)
920+
const ensureStockfishRef = useRef(ensureStockfishForNode)
921+
useEffect(() => {
922+
ensureMaiaRef.current = ensureMaiaForNode
923+
}, [ensureMaiaForNode])
924+
useEffect(() => {
925+
ensureStockfishRef.current = ensureStockfishForNode
926+
}, [ensureStockfishForNode])
927+
928+
// The long-lived loop that drains the queue.
929+
// Uses refs for everything so it never needs to be recreated.
918930
const runBackgroundLoop = useCallback(async () => {
919931
if (backgroundRunningRef.current) return
920932
backgroundRunningRef.current = true
921933

934+
console.log('[bg-analysis] loop started')
922935
try {
923936
while (backgroundQueueRef.current.length > 0) {
924-
if (backgroundCancelledRef.current) break
937+
if (backgroundCancelledRef.current) {
938+
console.log('[bg-analysis] cancelled, stopping')
939+
break
940+
}
925941
const node = backgroundQueueRef.current[0]
942+
console.log(
943+
'[bg-analysis] processing node:',
944+
node.fen.split(' ').slice(0, 2).join(' '),
945+
)
926946

927-
await ensureMaiaForNode(node)
947+
await ensureMaiaRef.current(node)
928948
if (backgroundCancelledRef.current) break
929949

930-
await ensureStockfishForNode(node)
950+
console.log(
951+
'[bg-analysis] maia done, starting stockfish for:',
952+
node.fen.split(' ').slice(0, 2).join(' '),
953+
)
954+
await ensureStockfishRef.current(node)
931955

932956
// Only remove from queue after both analyses complete (or if cancelled)
933957
backgroundQueueRef.current.shift()
958+
console.log(
959+
'[bg-analysis] node complete, remaining:',
960+
backgroundQueueRef.current.length,
961+
)
934962
}
963+
} catch (error) {
964+
console.error('[bg-analysis] error:', error)
935965
} finally {
936966
backgroundRunningRef.current = false
967+
console.log('[bg-analysis] loop ended')
937968
}
938-
}, [ensureMaiaForNode, ensureStockfishForNode])
969+
// eslint-disable-next-line react-hooks/exhaustive-deps
970+
}, [])
939971

940972
// Enqueue new drill nodes whenever the tree grows
941973
useEffect(() => {
@@ -963,6 +995,12 @@ export const useOpeningDrillController = (
963995
})
964996

965997
if (newNodes.length > 0) {
998+
console.log(
999+
'[bg-analysis] enqueueing',
1000+
newNodes.length,
1001+
'nodes, loop running:',
1002+
backgroundRunningRef.current,
1003+
)
9661004
backgroundQueueRef.current.push(...newNodes)
9671005
runBackgroundLoop()
9681006
}
@@ -974,17 +1012,31 @@ export const useOpeningDrillController = (
9741012
treeController.currentNode,
9751013
])
9761014

977-
// Cancel background analysis when drill session resets
1015+
// Cancel background analysis when a new drill starts (not on every move).
1016+
// currentDrillGame changes on every move, so we track the drill ID instead.
1017+
const backgroundDrillIdRef = useRef<string | null>(null)
9781018
useEffect(() => {
979-
backgroundCancelledRef.current = false
980-
return () => {
1019+
const drillId = currentDrillGame?.id ?? null
1020+
if (drillId !== backgroundDrillIdRef.current) {
1021+
// New drill or drill cleared — cancel any running background work
9811022
backgroundCancelledRef.current = true
9821023
backgroundQueueRef.current = []
1024+
backgroundRunningRef.current = false
1025+
1026+
backgroundDrillIdRef.current = drillId
1027+
if (drillId) {
1028+
// Reset for the new drill
1029+
backgroundCancelledRef.current = false
1030+
}
9831031
}
984-
}, [currentDrillGame])
1032+
}, [currentDrillGame?.id])
9851033

9861034
const ensureDrillAnalysis = useCallback(
9871035
async (drillGame: OpeningDrillGame): Promise<boolean> => {
1036+
// Stop background analysis so it doesn't compete for stockfish
1037+
backgroundCancelledRef.current = true
1038+
backgroundQueueRef.current = []
1039+
9881040
const mainLine = drillGame.tree.getMainLine()
9891041
const startingNode = drillGame.openingEndNode || mainLine[0]
9901042
const startIndex = startingNode

0 commit comments

Comments
 (0)