Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 48 additions & 16 deletions lib/src/model/analysis/retro_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -113,21 +113,6 @@ class RetroController extends AsyncNotifier<RetroState> with EngineEvaluationMix
_root = _game.makeTree();

if (_game.serverAnalysis == null) {
await serverAnalysisService.requestAnalysis(options.id);

_serverAnalysisCompleter.future.timeout(
kMaxWaitForServerAnalysis,
onTimeout: () {
_logger.warning(
'Server analysis did not finish within $kMaxWaitForServerAnalysis for game ${options.id}',
);
state = AsyncError(
Exception('Server analysis did not finish within $kMaxWaitForServerAnalysis'),
StackTrace.current,
);
},
);

final retroState = RetroState(
serverAnalysisAvailable: false,
mistakes: const IList.empty(),
Expand All @@ -148,9 +133,56 @@ class RetroController extends AsyncNotifier<RetroState> with EngineEvaluationMix

state = AsyncValue.data(retroState);

// Attach listener BEFORE possibly requesting analysis,
// so we don't miss the first progress event.
serverAnalysisService.lastAnalysisEvent.addListener(_listenToServerAnalysisEvents);

return retroState;
// Reuse an already available event immediately if it belongs to this game.
final existingEvent = serverAnalysisService.lastAnalysisEvent.value;
if (existingEvent != null && existingEvent.$1 == options.id) {
ServerAnalysisService.mergeOngoingAnalysis(_root, existingEvent.$2.tree);

final progress =
existingEvent.$2.evals.where((e) => e.hasEval).length / _root.mainline.length;

state = AsyncValue.data(state.requireValue.copyWith(serverAnalysisProgress: progress));

if (existingEvent.$2.isAnalysisComplete) {
if (!_serverAnalysisCompleter.isCompleted) {
_serverAnalysisCompleter.complete();
}

state = AsyncData(await _computeMistakes(options.initialSide));

socketClient.firstConnection.then((_) {
requestEval();
});

return state.requireValue;
}
}

// Only request analysis if this exact game is not already being analyzed.
if (serverAnalysisService.currentAnalysis.value != options.id) {
await serverAnalysisService.requestAnalysis(options.id);
}

unawaited(
_serverAnalysisCompleter.future.timeout(
kMaxWaitForServerAnalysis,
onTimeout: () {
_logger.warning(
'Server analysis did not finish within $kMaxWaitForServerAnalysis for game ${options.id}',
);
state = AsyncError(
Exception('Server analysis did not finish within $kMaxWaitForServerAnalysis'),
StackTrace.current,
);
},
),
);

return state.requireValue;
}

state = AsyncData(await _computeMistakes(options.initialSide));
Expand Down
7 changes: 7 additions & 0 deletions lib/src/model/analysis/server_analysis_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ class ServerAnalysisService {
/// This will return a future that completes when the server analysis is
/// launched (but not when it is finished).
Future<void> requestAnalysis(GameId id, [Side? side]) async {
// If we are already listening for analysis updates of this exact game,
// don't tear everything down and reconnect.
if (_currentAnalysis.value == id && _socketSubscription != null && _analysisCompleter != null) {
return;
}

_cancelAnalysis();

final uri = Uri(path: '/watch/$id/${side?.name ?? Side.white}/v6');
Expand Down Expand Up @@ -95,6 +101,7 @@ class ServerAnalysisService {
// of analyses is reached.
if (e.statusCode == 400) {
debugPrint('Analysis already requested for game $id');
_currentAnalysis.value = id.gameId;
} else {
debugPrint('ServerException requesting server analysis: $e');
_cancelAnalysis();
Expand Down