Skip to content

Commit

Permalink
Merge pull request #1275 from lichess-org/fix_start_engine
Browse files Browse the repository at this point in the history
Fix engine evaluation started from new analysis screens
  • Loading branch information
veloce authored Dec 19, 2024
2 parents 2ea2b5e + 47fba32 commit ed56971
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 175 deletions.
94 changes: 33 additions & 61 deletions lib/src/model/analysis/analysis_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -191,20 +191,11 @@ class AnalysisController extends _$AnalysisController implements PgnTreeNotifier
);

if (analysisState.isEngineAvailable) {
evaluationService
.initEngine(
_evaluationContext,
options: EvaluationOptions(
multiPv: prefs.numEvalLines,
cores: prefs.numEngineCores,
searchTime: prefs.engineSearchTime,
),
)
.then((_) {
_startEngineEvalTimer = Timer(const Duration(milliseconds: 250), () {
_startEngineEval();
});
});
evaluationService.initEngine(_evaluationContext, options: _evaluationOptions).then((_) {
_startEngineEvalTimer = Timer(const Duration(milliseconds: 250), () {
_startEngineEval();
});
});
}

return analysisState;
Expand All @@ -213,6 +204,9 @@ class AnalysisController extends _$AnalysisController implements PgnTreeNotifier
EvaluationContext get _evaluationContext =>
EvaluationContext(variant: _variant, initialPosition: _root.position);

EvaluationOptions get _evaluationOptions =>
ref.read(analysisPreferencesProvider).evaluationOptions;

void onUserMove(NormalMove move, {bool shouldReplace = false}) {
if (!state.requireValue.position.isLegal(move)) return;

Expand Down Expand Up @@ -360,17 +354,9 @@ class AnalysisController extends _$AnalysisController implements PgnTreeNotifier
);

if (state.requireValue.isEngineAvailable) {
final prefs = ref.read(analysisPreferencesProvider);
await ref
.read(evaluationServiceProvider)
.initEngine(
_evaluationContext,
options: EvaluationOptions(
multiPv: prefs.numEvalLines,
cores: prefs.numEngineCores,
searchTime: prefs.engineSearchTime,
),
);
.initEngine(_evaluationContext, options: _evaluationOptions);
_startEngineEval();
} else {
_stopEngineEval();
Expand All @@ -381,15 +367,7 @@ class AnalysisController extends _$AnalysisController implements PgnTreeNotifier
void setNumEvalLines(int numEvalLines) {
ref.read(analysisPreferencesProvider.notifier).setNumEvalLines(numEvalLines);

ref
.read(evaluationServiceProvider)
.setOptions(
EvaluationOptions(
multiPv: numEvalLines,
cores: ref.read(analysisPreferencesProvider).numEngineCores,
searchTime: ref.read(analysisPreferencesProvider).engineSearchTime,
),
);
ref.read(evaluationServiceProvider).setOptions(_evaluationOptions);

_root.updateAll((node) => node.eval = null);

Expand All @@ -406,31 +384,15 @@ class AnalysisController extends _$AnalysisController implements PgnTreeNotifier
void setEngineCores(int numEngineCores) {
ref.read(analysisPreferencesProvider.notifier).setEngineCores(numEngineCores);

ref
.read(evaluationServiceProvider)
.setOptions(
EvaluationOptions(
multiPv: ref.read(analysisPreferencesProvider).numEvalLines,
cores: numEngineCores,
searchTime: ref.read(analysisPreferencesProvider).engineSearchTime,
),
);
ref.read(evaluationServiceProvider).setOptions(_evaluationOptions);

_startEngineEval();
}

void setEngineSearchTime(Duration searchTime) {
ref.read(analysisPreferencesProvider.notifier).setEngineSearchTime(searchTime);

ref
.read(evaluationServiceProvider)
.setOptions(
EvaluationOptions(
multiPv: ref.read(analysisPreferencesProvider).numEvalLines,
cores: ref.read(analysisPreferencesProvider).numEngineCores,
searchTime: searchTime,
),
);
ref.read(evaluationServiceProvider).setOptions(_evaluationOptions);

_startEngineEval();
}
Expand Down Expand Up @@ -553,6 +515,14 @@ class AnalysisController extends _$AnalysisController implements PgnTreeNotifier
}
}

void _refreshCurrentNode() {
state = AsyncData(
state.requireValue.copyWith(
currentNode: AnalysisCurrentNode.fromNode(_root.nodeAt(state.requireValue.currentPath)),
),
);
}

Future<(UciPath, FullOpening)?> _fetchOpening(Node fromNode, UciPath path) async {
if (!kOpeningAllowedVariants.contains(_variant)) return null;

Expand All @@ -572,15 +542,16 @@ class AnalysisController extends _$AnalysisController implements PgnTreeNotifier

final curState = state.requireValue;
if (curState.currentPath == path) {
state = AsyncData(
curState.copyWith(currentNode: AnalysisCurrentNode.fromNode(_root.nodeAt(path))),
);
_refreshCurrentNode();
}
}

void _startEngineEval() {
Future<void> _startEngineEval() async {
final curState = state.requireValue;
if (!curState.isEngineAvailable) return;
await ref
.read(evaluationServiceProvider)
.ensureEngineInitialized(_evaluationContext, options: _evaluationOptions);
ref
.read(evaluationServiceProvider)
.start(
Expand All @@ -589,7 +560,13 @@ class AnalysisController extends _$AnalysisController implements PgnTreeNotifier
initialPositionEval: _root.eval,
shouldEmit: (work) => work.path == state.valueOrNull?.currentPath,
)
?.forEach((t) => _root.updateAt(t.$1.path, (node) => node.eval = t.$2));
?.forEach((t) {
final (work, eval) = t;
_root.updateAt(work.path, (node) => node.eval = eval);
if (work.path == curState.currentPath && eval.searchTime >= work.searchTime) {
_refreshCurrentNode();
}
});
}

void _debouncedStartEngineEval() {
Expand All @@ -601,12 +578,7 @@ class AnalysisController extends _$AnalysisController implements PgnTreeNotifier
void _stopEngineEval() {
ref.read(evaluationServiceProvider).stop();
// update the current node with last cached eval
final curState = state.requireValue;
state = AsyncData(
curState.copyWith(
currentNode: AnalysisCurrentNode.fromNode(_root.nodeAt(curState.currentPath)),
),
);
_refreshCurrentNode();
}

void _listenToServerAnalysisEvents() {
Expand Down
3 changes: 3 additions & 0 deletions lib/src/model/analysis/analysis_preferences.dart
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ class AnalysisPrefs with _$AnalysisPrefs implements Serializable {
factory AnalysisPrefs.fromJson(Map<String, dynamic> json) {
return _$AnalysisPrefsFromJson(json);
}

EvaluationOptions get evaluationOptions =>
EvaluationOptions(multiPv: numEvalLines, cores: numEngineCores, searchTime: engineSearchTime);
}

Duration _searchTimeDefault() {
Expand Down
89 changes: 32 additions & 57 deletions lib/src/model/broadcast/broadcast_game_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -120,20 +120,11 @@ class BroadcastGameController extends _$BroadcastGameController implements PgnTr
);

if (broadcastState.isLocalEvaluationEnabled) {
evaluationService
.initEngine(
_evaluationContext,
options: EvaluationOptions(
multiPv: prefs.numEvalLines,
cores: prefs.numEngineCores,
searchTime: ref.read(analysisPreferencesProvider).engineSearchTime,
),
)
.then((_) {
_startEngineEvalTimer = Timer(const Duration(milliseconds: 250), () {
_startEngineEval();
});
});
evaluationService.initEngine(_evaluationContext, options: _evaluationOptions).then((_) {
_startEngineEvalTimer = Timer(const Duration(milliseconds: 250), () {
_startEngineEval();
});
});
}

return broadcastState;
Expand Down Expand Up @@ -251,6 +242,9 @@ class BroadcastGameController extends _$BroadcastGameController implements PgnTr
EvaluationContext get _evaluationContext =>
EvaluationContext(variant: Variant.standard, initialPosition: _root.position);

EvaluationOptions get _evaluationOptions =>
ref.read(analysisPreferencesProvider).evaluationOptions;

void onUserMove(NormalMove move) {
if (!state.hasValue) return;

Expand Down Expand Up @@ -388,17 +382,9 @@ class BroadcastGameController extends _$BroadcastGameController implements PgnTr
);

if (state.requireValue.isLocalEvaluationEnabled) {
final prefs = ref.read(analysisPreferencesProvider);
await ref
.read(evaluationServiceProvider)
.initEngine(
_evaluationContext,
options: EvaluationOptions(
multiPv: prefs.numEvalLines,
cores: prefs.numEngineCores,
searchTime: ref.read(analysisPreferencesProvider).engineSearchTime,
),
);
.initEngine(_evaluationContext, options: _evaluationOptions);
_startEngineEval();
} else {
_stopEngineEval();
Expand All @@ -411,15 +397,7 @@ class BroadcastGameController extends _$BroadcastGameController implements PgnTr

ref.read(analysisPreferencesProvider.notifier).setNumEvalLines(numEvalLines);

ref
.read(evaluationServiceProvider)
.setOptions(
EvaluationOptions(
multiPv: numEvalLines,
cores: ref.read(analysisPreferencesProvider).numEngineCores,
searchTime: ref.read(analysisPreferencesProvider).engineSearchTime,
),
);
ref.read(evaluationServiceProvider).setOptions(_evaluationOptions);

_root.updateAll((node) => node.eval = null);

Expand All @@ -435,31 +413,15 @@ class BroadcastGameController extends _$BroadcastGameController implements PgnTr
void setEngineCores(int numEngineCores) {
ref.read(analysisPreferencesProvider.notifier).setEngineCores(numEngineCores);

ref
.read(evaluationServiceProvider)
.setOptions(
EvaluationOptions(
multiPv: ref.read(analysisPreferencesProvider).numEvalLines,
cores: numEngineCores,
searchTime: ref.read(analysisPreferencesProvider).engineSearchTime,
),
);
ref.read(evaluationServiceProvider).setOptions(_evaluationOptions);

_startEngineEval();
}

void setEngineSearchTime(Duration searchTime) {
ref.read(analysisPreferencesProvider.notifier).setEngineSearchTime(searchTime);

ref
.read(evaluationServiceProvider)
.setOptions(
EvaluationOptions(
multiPv: ref.read(analysisPreferencesProvider).numEvalLines,
cores: ref.read(analysisPreferencesProvider).numEngineCores,
searchTime: searchTime,
),
);
ref.read(evaluationServiceProvider).setOptions(_evaluationOptions);

_startEngineEval();
}
Expand Down Expand Up @@ -540,10 +502,13 @@ class BroadcastGameController extends _$BroadcastGameController implements PgnTr
}
}

void _startEngineEval() {
Future<void> _startEngineEval() async {
if (!state.hasValue) return;

if (!state.requireValue.isLocalEvaluationEnabled) return;
await ref
.read(evaluationServiceProvider)
.ensureEngineInitialized(_evaluationContext, options: _evaluationOptions);
ref
.read(evaluationServiceProvider)
.start(
Expand All @@ -552,7 +517,21 @@ class BroadcastGameController extends _$BroadcastGameController implements PgnTr
initialPositionEval: _root.eval,
shouldEmit: (work) => work.path == state.valueOrNull?.currentPath,
)
?.forEach((t) => _root.updateAt(t.$1.path, (node) => node.eval = t.$2));
?.forEach((t) {
final (work, eval) = t;
_root.updateAt(work.path, (node) => node.eval = eval);
if (work.path == state.requireValue.currentPath && eval.searchTime >= work.searchTime) {
_refreshCurrentNode();
}
});
}

void _refreshCurrentNode() {
state = AsyncData(
state.requireValue.copyWith(
currentNode: AnalysisCurrentNode.fromNode(_root.nodeAt(state.requireValue.currentPath)),
),
);
}

void _debouncedStartEngineEval() {
Expand All @@ -566,11 +545,7 @@ class BroadcastGameController extends _$BroadcastGameController implements PgnTr

ref.read(evaluationServiceProvider).stop();
// update the current node with last cached eval
state = AsyncData(
state.requireValue.copyWith(
currentNode: AnalysisCurrentNode.fromNode(_root.nodeAt(state.requireValue.currentPath)),
),
);
_refreshCurrentNode();
}

({Duration? parentClock, Duration? clock}) _getClocks(UciPath path) {
Expand Down
15 changes: 14 additions & 1 deletion lib/src/model/engine/evaluation_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ class EvaluationService {

/// Initialize the engine with the given context and options.
///
/// If the engine is already initialized, it is disposed first.
///
/// An optional [engineFactory] can be provided, it defaults to Stockfish.
///
/// If [options] is not provided, the default options are used.
Expand All @@ -59,7 +61,7 @@ class EvaluationService {
Engine Function() engineFactory = StockfishEngine.new,
EvaluationOptions? options,
}) async {
if (context != _context) {
if (_context != null || _engine != null) {
await disposeEngine();
}
_context = context;
Expand All @@ -81,6 +83,17 @@ class EvaluationService {
});
}

/// Ensure the engine is initialized with the given context and options.
Future<void> ensureEngineInitialized(
EvaluationContext context, {
Engine Function() engineFactory = StockfishEngine.new,
EvaluationOptions? options,
}) async {
if (_engine == null || _context != context) {
await initEngine(context, engineFactory: engineFactory, options: options);
}
}

void setOptions(EvaluationOptions options) {
stop();
_options = options;
Expand Down
6 changes: 5 additions & 1 deletion lib/src/model/puzzle/puzzle_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -447,8 +447,9 @@ class PuzzleController extends _$PuzzleController {
return pgn;
}

void _startEngineEval() {
Future<void> _startEngineEval() async {
if (!state.isEngineEnabled) return;
await ref.read(evaluationServiceProvider).ensureEngineInitialized(state.evaluationContext);
_engineEvalDebounce(
() => ref
.read(evaluationServiceProvider)
Expand All @@ -463,6 +464,9 @@ class PuzzleController extends _$PuzzleController {
_gameTree.updateAt(work.path, (node) {
node.eval = eval;
});
if (work.path == state.currentPath && eval.searchTime >= work.searchTime) {
state = state.copyWith(node: _gameTree.branchAt(state.currentPath).view);
}
}),
);
}
Expand Down
Loading

0 comments on commit ed56971

Please sign in to comment.