Skip to content
Closed
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
20 changes: 18 additions & 2 deletions lib/src/model/analysis/analysis_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'dart:async';
import 'package:collection/collection.dart';
import 'package:dartchess/dartchess.dart';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:intl/intl.dart';
import 'package:lichess_mobile/src/model/analysis/analysis_preferences.dart';
Expand Down Expand Up @@ -799,7 +800,6 @@ class AnalysisState with _$AnalysisState {
orientation: pov,
isLocalEngineAvailable: isEngineAvailable,
position: position,
savedEval: currentNode.eval ?? currentNode.serverEval,
);
}

Expand All @@ -813,7 +813,7 @@ class AnalysisCurrentNode with _$AnalysisCurrentNode {
required bool isRoot,
SanMove? sanMove,
Opening? opening,
LocalEval? eval,
ClientEval? eval,
IList<PgnComment>? lichessAnalysisComments,
IList<PgnComment>? startingComments,
IList<PgnComment>? comments,
Expand Down Expand Up @@ -845,6 +845,22 @@ class AnalysisCurrentNode with _$AnalysisCurrentNode {
}
}

Eval? currentEval(WidgetRef ref) {
return switch (eval) {
CloudEval() => eval,
LocalEval() => ref.watch(engineEvaluationProvider).eval ?? eval,
null => ref.watch(engineEvaluationProvider).eval ?? serverEval,
};
}

ClientEval? currentClientEval(WidgetRef ref) {
final eval = currentEval(ref);
return switch (eval) {
ExternalEval() || null => null,
ClientEval() => eval,
};
}

/// The evaluation from the PGN comments.
///
/// For now we only trust the eval coming from lichess analysis.
Expand Down
57 changes: 49 additions & 8 deletions lib/src/model/broadcast/broadcast_analysis_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'package:lichess_mobile/src/model/analysis/analysis_controller.dart';
import 'package:lichess_mobile/src/model/analysis/analysis_preferences.dart';
import 'package:lichess_mobile/src/model/broadcast/broadcast_repository.dart';
import 'package:lichess_mobile/src/model/common/chess.dart';
import 'package:lichess_mobile/src/model/common/eval.dart';
import 'package:lichess_mobile/src/model/common/id.dart';
import 'package:lichess_mobile/src/model/common/node.dart';
import 'package:lichess_mobile/src/model/common/service/move_feedback.dart';
Expand Down Expand Up @@ -43,8 +44,6 @@ class BroadcastAnalysisController extends _$BroadcastAnalysisController implemen
final _engineEvalDebounce = Debouncer(const Duration(milliseconds: 150));
final _syncDebouncer = Debouncer(const Duration(milliseconds: 150));

Timer? _startEngineEvalTimer;

Object? _key = Object();

@override
Expand Down Expand Up @@ -81,7 +80,6 @@ class BroadcastAnalysisController extends _$BroadcastAnalysisController implemen
_key = null;
_subscription?.cancel();
_socketOpenSubscription?.cancel();
_startEngineEvalTimer?.cancel();
_engineEvalDebounce.dispose();
evaluationService.disposeEngine();
_appLifecycleListener?.dispose();
Expand Down Expand Up @@ -119,11 +117,12 @@ class BroadcastAnalysisController extends _$BroadcastAnalysisController implemen
clocks: _getClocks(currentPath),
);

state = AsyncData(broadcastState);

if (broadcastState.isLocalEvaluationEnabled) {
_sendEvalGetEvent();
evaluationService.initEngine(_evaluationContext, options: _evaluationOptions).then((_) {
_startEngineEvalTimer = Timer(const Duration(milliseconds: 250), () {
_startEngineEval();
});
_debouncedStartEngineEval();
});
}

Expand Down Expand Up @@ -180,6 +179,9 @@ class BroadcastAnalysisController extends _$BroadcastAnalysisController implemen
// Sent when a pgn tag changes
case 'setTags':
_handleSetTagsEvent(event);
// Sent when a new eval is received
case 'evalHit':
_handleEvalHitEvent(event);
}
}

Expand Down Expand Up @@ -239,6 +241,43 @@ class BroadcastAnalysisController extends _$BroadcastAnalysisController implemen
state = AsyncData(state.requireValue.copyWith(pgnHeaders: pgnHeaders));
}

void _handleEvalHitEvent(SocketEvent event) {
final path = pick(event.data, 'path').asUciPathOrThrow();

if (state.requireValue.currentPath != path) return;

final depth = pick(event.data, 'depth').asIntOrThrow();
final pvs =
pick(event.data, 'pvs')
.asListOrThrow(
(pv) => PvData(
moves: pv('moves').asStringOrThrow().split(' ').toIList(),
cp: pv('cp').asIntOrNull(),
mate: pv('mate').asIntOrNull(),
),
)
.toIList();

state = AsyncData(
state.requireValue.copyWith(
currentNode: state.requireValue.currentNode.copyWith(
eval: CloudEval(depth: depth, position: state.requireValue.position, pvs: pvs),
),
),
);
}

void _sendEvalGetEvent() {
final numEvalLines = ref.read(analysisPreferencesProvider).numEvalLines;

_socketClient.send('evalGet', {
'fen': state.requireValue.currentNode.position.fen,
'path': state.requireValue.currentPath.value,
'mpv': numEvalLines,
'up': true,
});
}

EvaluationContext get _evaluationContext =>
EvaluationContext(variant: Variant.standard, initialPosition: _root.position);

Expand Down Expand Up @@ -498,6 +537,7 @@ class BroadcastAnalysisController extends _$BroadcastAnalysisController implemen
}

if (pathChange && state.requireValue.isLocalEvaluationEnabled) {
_sendEvalGetEvent();
_debouncedStartEngineEval();
}
}
Expand Down Expand Up @@ -536,7 +576,9 @@ class BroadcastAnalysisController extends _$BroadcastAnalysisController implemen

void _debouncedStartEngineEval() {
_engineEvalDebounce(() {
_startEngineEval();
if (state.requireValue.currentNode.eval is! CloudEval) {
_startEngineEval();
}
});
}

Expand Down Expand Up @@ -626,6 +668,5 @@ class BroadcastAnalysisState with _$BroadcastAnalysisState {
orientation: pov,
isLocalEngineAvailable: isLocalEvaluationEnabled,
position: position,
savedEval: currentNode.eval ?? currentNode.serverEval,
);
}
15 changes: 15 additions & 0 deletions lib/src/model/common/eval.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,13 @@ sealed class Eval {

/// The eval from the client side, either from the cloud or the local engine.
sealed class ClientEval extends Eval {
int get depth;

Position get position;

IList<PvData> get pvs;

IList<MoveWithWinningChances> get bestMoves;
}

/// The eval coming from other Lichess clients, served from the network.
Expand All @@ -47,6 +51,16 @@ class CloudEval with _$CloudEval implements ClientEval {
int? get cp => pvs[0].cp;

int? get mate => pvs[0].mate;

@override
IList<MoveWithWinningChances> get bestMoves {
return pvs
.where((e) => e.moves.isNotEmpty)
.map((e) => e._firstMoveWithWinningChances(position.turn))
.nonNulls
.sorted((a, b) => b.winningChances.compareTo(a.winningChances))
.toIList();
}
}

/// The eval from the local engine.
Expand All @@ -73,6 +87,7 @@ class LocalEval with _$LocalEval implements ClientEval {
return Move.parse(uci);
}

@override
IList<MoveWithWinningChances> get bestMoves {
return pvs
.where((e) => e.moves.isNotEmpty)
Expand Down
3 changes: 3 additions & 0 deletions lib/src/model/common/node.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ abstract class Node {
/// The local evaluation of the position.
LocalEval? eval;

/// The cloud evaluation of the position
CloudEval? cloudEval;

/// The opening associated with this node.
Opening? opening;

Expand Down
6 changes: 2 additions & 4 deletions lib/src/model/engine/evaluation_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,7 @@ const engineSupportedVariants = {Variant.standard, Variant.chess960, Variant.fro

/// A service to evaluate chess positions using an engine.
class EvaluationService {
EvaluationService(this.ref, {required this.maxMemory});

final Ref ref;
EvaluationService({required this.maxMemory});

final int maxMemory;

Expand Down Expand Up @@ -179,7 +177,7 @@ class EvaluationService {
EvaluationService evaluationService(Ref ref) {
final maxMemory = ref.read(preloadedDataProvider).requireValue.engineMaxMemoryInMb;

final service = EvaluationService(ref, maxMemory: maxMemory);
final service = EvaluationService(maxMemory: maxMemory);
ref.onDispose(() {
service.disposeEngine();
});
Expand Down
7 changes: 1 addition & 6 deletions lib/src/model/study/study_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -588,12 +588,7 @@ class StudyState with _$StudyState {

EngineGaugeParams? get engineGaugeParams =>
isEngineAvailable
? (
orientation: pov,
isLocalEngineAvailable: isEngineAvailable,
position: position!,
savedEval: currentNode.eval,
)
? (orientation: pov, isLocalEngineAvailable: isEngineAvailable, position: position!)
: null;

Position? get position => currentNode.position;
Expand Down
8 changes: 6 additions & 2 deletions lib/src/view/analysis/analysis_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ class _AnalysisScreenState extends ConsumerState<_AnalysisScreen>

final appBarActions = [
if (prefs.enableComputerAnalysis)
EngineDepth(defaultEval: asyncState.valueOrNull?.currentNode.eval),
EngineDepth(eval: asyncState.valueOrNull?.currentNode.currentClientEval(ref)),
AppBarAnalysisTabIndicator(tabs: tabs, controller: _tabController),
AppBarIconButton(
onPressed: () {
Expand Down Expand Up @@ -182,6 +182,8 @@ class _Body extends ConsumerWidget {
final currentNode = analysisState.currentNode;
final pov = analysisState.pov;

final eval = currentNode.currentEval(ref);

return AnalysisLayout(
tabController: controller,
pov: pov,
Expand All @@ -199,13 +201,15 @@ class _Body extends ConsumerWidget {
? EngineGauge(
displayMode: EngineGaugeDisplayMode.horizontal,
params: analysisState.engineGaugeParams,
eval: eval,
)
: Container(
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(borderRadius: BorderRadius.circular(4.0)),
child: EngineGauge(
displayMode: EngineGaugeDisplayMode.vertical,
params: analysisState.engineGaugeParams,
eval: eval,
),
);
}
Expand All @@ -214,7 +218,7 @@ class _Body extends ConsumerWidget {
isEngineAvailable && numEvalLines > 0
? EngineLines(
onTapMove: ref.read(ctrlProvider.notifier).onUserMove,
localEval: currentNode.eval,
eval: currentNode.eval,
isGameOver: currentNode.position.isGameOver,
)
: null,
Expand Down
20 changes: 15 additions & 5 deletions lib/src/view/broadcast/broadcast_game_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import 'package:lichess_mobile/src/model/broadcast/broadcast_repository.dart';
import 'package:lichess_mobile/src/model/common/chess.dart';
import 'package:lichess_mobile/src/model/common/eval.dart';
import 'package:lichess_mobile/src/model/common/id.dart';
import 'package:lichess_mobile/src/model/engine/evaluation_service.dart';
import 'package:lichess_mobile/src/model/game/game_share_service.dart';
import 'package:lichess_mobile/src/model/settings/board_preferences.dart';
import 'package:lichess_mobile/src/network/http.dart';
Expand All @@ -24,6 +23,7 @@ import 'package:lichess_mobile/src/view/broadcast/broadcast_game_screen_provider
import 'package:lichess_mobile/src/view/broadcast/broadcast_game_settings.dart';
import 'package:lichess_mobile/src/view/broadcast/broadcast_player_results_screen.dart';
import 'package:lichess_mobile/src/view/broadcast/broadcast_player_widget.dart';
import 'package:lichess_mobile/src/view/engine/engine_depth.dart';
import 'package:lichess_mobile/src/view/engine/engine_gauge.dart';
import 'package:lichess_mobile/src/view/engine/engine_lines.dart';
import 'package:lichess_mobile/src/view/opening_explorer/opening_explorer_view.dart';
Expand Down Expand Up @@ -117,6 +117,14 @@ class _BroadcastGameScreenState extends ConsumerState<BroadcastGameScreen>
enableBackgroundFilterBlur: false,
appBarTitle: title,
appBarActions: [
EngineDepth(
eval: ref
.watch(broadcastAnalysisControllerProvider(widget.roundId, widget.gameId))
.value
?.currentNode
.currentClientEval(ref),
),

AppBarAnalysisTabIndicator(tabs: tabs, controller: _tabController),
AppBarIconButton(
onPressed: () {
Expand Down Expand Up @@ -174,6 +182,8 @@ class _Body extends ConsumerWidget {
final currentNode = state.currentNode;
final pov = state.pov;

final eval = currentNode.currentEval(ref);

return AnalysisLayout(
pov: pov,
tabController: tabController,
Expand All @@ -199,21 +209,23 @@ class _Body extends ConsumerWidget {
? EngineGauge(
displayMode: EngineGaugeDisplayMode.horizontal,
params: engineGaugeParams,
eval: eval,
)
: Container(
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(borderRadius: BorderRadius.circular(4.0)),
child: EngineGauge(
displayMode: EngineGaugeDisplayMode.vertical,
params: engineGaugeParams,
eval: eval,
),
);
}
: null,
engineLines:
isLocalEvaluationEnabled && numEvalLines > 0
? EngineLines(
localEval: currentNode.eval,
eval: currentNode.currentClientEval(ref),
isGameOver: currentNode.position.isGameOver,
onTapMove:
ref
Expand Down Expand Up @@ -303,12 +315,10 @@ class _BroadcastBoardState extends ConsumerState<_BroadcastBoard> {
final boardPrefs = ref.watch(boardPreferencesProvider);
final analysisPrefs = ref.watch(analysisPreferencesProvider);

final evalBestMoves = ref.watch(engineEvaluationProvider.select((s) => s.eval?.bestMoves));

final currentNode = broadcastAnalysisState.currentNode;
final annotation = makeAnnotation(currentNode.nags);

final bestMoves = evalBestMoves ?? currentNode.eval?.bestMoves;
final bestMoves = currentNode.currentClientEval(ref)?.bestMoves;

final sanMove = currentNode.sanMove;

Expand Down
Loading