@@ -113,21 +113,6 @@ class RetroController extends AsyncNotifier<RetroState> with EngineEvaluationMix
113113 _root = _game.makeTree ();
114114
115115 if (_game.serverAnalysis == null ) {
116- await serverAnalysisService.requestAnalysis (options.id);
117-
118- _serverAnalysisCompleter.future.timeout (
119- kMaxWaitForServerAnalysis,
120- onTimeout: () {
121- _logger.warning (
122- 'Server analysis did not finish within $kMaxWaitForServerAnalysis for game ${options .id }' ,
123- );
124- state = AsyncError (
125- Exception ('Server analysis did not finish within $kMaxWaitForServerAnalysis ' ),
126- StackTrace .current,
127- );
128- },
129- );
130-
131116 final retroState = RetroState (
132117 serverAnalysisAvailable: false ,
133118 mistakes: const IList .empty (),
@@ -148,9 +133,58 @@ class RetroController extends AsyncNotifier<RetroState> with EngineEvaluationMix
148133
149134 state = AsyncValue .data (retroState);
150135
136+ // Attach listener BEFORE possibly requesting analysis,
137+ // so we don't miss the first progress event.
151138 serverAnalysisService.lastAnalysisEvent.addListener (_listenToServerAnalysisEvents);
152139
153- return retroState;
140+ // Reuse an already available event immediately if it belongs to this game.
141+ final existingEvent = serverAnalysisService.lastAnalysisEvent.value;
142+ if (existingEvent != null && existingEvent.$1 == options.id) {
143+ ServerAnalysisService .mergeOngoingAnalysis (_root, existingEvent.$2.tree);
144+
145+ final progress =
146+ existingEvent.$2.evals.where ((e) => e.hasEval).length / _root.mainline.length;
147+
148+ state = AsyncValue .data (
149+ state.requireValue.copyWith (serverAnalysisProgress: progress),
150+ );
151+
152+ if (existingEvent.$2.isAnalysisComplete) {
153+ if (! _serverAnalysisCompleter.isCompleted) {
154+ _serverAnalysisCompleter.complete ();
155+ }
156+
157+ state = AsyncData (await _computeMistakes (options.initialSide));
158+
159+ socketClient.firstConnection.then ((_) {
160+ requestEval ();
161+ });
162+
163+ return state.requireValue;
164+ }
165+ }
166+
167+ // Only request analysis if this exact game is not already being analyzed.
168+ if (serverAnalysisService.currentAnalysis.value != options.id) {
169+ await serverAnalysisService.requestAnalysis (options.id);
170+ }
171+
172+ unawaited (
173+ _serverAnalysisCompleter.future.timeout (
174+ kMaxWaitForServerAnalysis,
175+ onTimeout: () {
176+ _logger.warning (
177+ 'Server analysis did not finish within $kMaxWaitForServerAnalysis for game ${options .id }' ,
178+ );
179+ state = AsyncError (
180+ Exception ('Server analysis did not finish within $kMaxWaitForServerAnalysis ' ),
181+ StackTrace .current,
182+ );
183+ },
184+ ),
185+ );
186+
187+ return state.requireValue;
154188 }
155189
156190 state = AsyncData (await _computeMistakes (options.initialSide));
0 commit comments