diff --git a/src-tauri/src/chess.rs b/src-tauri/src/chess.rs index dd35e6bb..623ae3ef 100644 --- a/src-tauri/src/chess.rs +++ b/src-tauri/src/chess.rs @@ -40,6 +40,7 @@ pub struct EngineProcess { last_depth: u8, best_moves: Vec, fen: Fen, + multipv: u16, logs: Vec, } @@ -64,6 +65,7 @@ impl EngineProcess { best_moves: Vec::new(), fen: Fen::default(), logs: Vec::new(), + multipv: 1, }, BufReader::new(child.stdout.take().ok_or(Error::NoStdout)?).lines(), )) @@ -78,6 +80,12 @@ impl EngineProcess { } async fn set_options(&mut self, options: EngineOptions) -> Result<(), Error> { + let fen: Fen = options.fen.parse()?; + let pos: Chess = match fen.into_position(CastlingMode::Standard) { + Ok(p) => p, + Err(e) => e.ignore_too_much_material()?, + }; + self.multipv = options.multipv.min(pos.legal_moves().len() as u16); self.set_option("Threads", &options.threads.to_string()) .await?; self.set_option("MultiPV", &options.multipv.to_string()) @@ -190,12 +198,20 @@ fn parse_uci_attrs(attrs: Vec, fen: &Fen) -> Result { best_moves.multipv = multipv; } - UciInfoAttribute::Score { cp, mate, .. } => { + UciInfoAttribute::Score { + cp, + mate, + lower_bound, + upper_bound, + } => { if let Some(cp) = cp { best_moves.score = Score::Cp(cp); } else if let Some(mate) = mate { best_moves.score = Score::Mate(mate); } + if lower_bound.unwrap_or(false) || upper_bound.unwrap_or(false) { + return Err(Error::LowerOrUpperBound); + } } _ => (), } @@ -325,28 +341,23 @@ pub async fn get_best_moves( ) -> Result<(), Error> { let path = PathBuf::from(&engine); - let parsed_fen: Fen = options.fen.parse()?; - let pos: Chess = match parsed_fen.clone().into_position(CastlingMode::Standard) { - Ok(p) => p, - Err(e) => e.ignore_too_much_material()?, - }; - - let mut options = options.clone(); - options.multipv = options.multipv.min(pos.legal_moves().len() as u16); - let key = (tab.clone(), engine.clone()); if state.engine_processes.contains_key(&key) { { let process = state.engine_processes.get_mut(&key).unwrap(); let mut process = process.lock().await; - if process.stop().await.is_ok() { - process.set_options(options.clone()).await?; - process.go(&go_mode).await?; - return Ok(()); - } + process.stop().await?; } - state.engine_processes.remove(&key).unwrap(); + // give time for engine to stop and process previous lines + tokio::time::sleep(std::time::Duration::from_millis(50)).await; + { + let process = state.engine_processes.get_mut(&key).unwrap(); + let mut process = process.lock().await; + process.set_options(options.clone()).await?; + process.go(&go_mode).await?; + } + return Ok(()); } let (mut process, mut reader) = EngineProcess::new(path)?; @@ -355,36 +366,36 @@ pub async fn get_best_moves( let process = Arc::new(Mutex::new(process)); - state.engine_processes.insert(key, process.clone()); + state.engine_processes.insert(key.clone(), process.clone()); - loop { - let line_opt = reader.next_line().await?; + while let Some(line) = reader.next_line().await? { let mut proc = process.lock().await; - if let Some(line) = line_opt { - if let UciMessage::Info(attrs) = parse_one(&line) { - if let Ok(best_moves) = parse_uci_attrs(attrs, &proc.fen) { - let multipv = best_moves.multipv; - let cur_depth = best_moves.depth; - proc.best_moves.push(best_moves); - if multipv == options.multipv { - if proc.best_moves.iter().all(|x| x.depth == cur_depth) - && cur_depth >= proc.last_depth - { - BestMovesPayload { - best_lines: proc.best_moves.clone(), - engine: engine.clone(), - tab: tab.clone(), - } - .emit_all(&app)?; - proc.last_depth = cur_depth; + if let UciMessage::Info(attrs) = parse_one(&line) { + if let Ok(best_moves) = parse_uci_attrs(attrs, &proc.fen) { + let multipv = best_moves.multipv; + let cur_depth = best_moves.depth; + proc.best_moves.push(best_moves); + if multipv == proc.multipv { + if proc.best_moves.iter().all(|x| x.depth == cur_depth) + && cur_depth >= proc.last_depth + { + BestMovesPayload { + best_lines: proc.best_moves.clone(), + engine: engine.clone(), + tab: tab.clone(), } - proc.best_moves.clear(); + .emit_all(&app)?; + proc.last_depth = cur_depth; } + proc.best_moves.clear(); } } - proc.logs.push(EngineLog::Engine(line)); } + proc.logs.push(EngineLog::Engine(line)); } + info!("Engine process finished: tab: {}, engine: {}", tab, engine); + state.engine_processes.remove(&key).unwrap(); + Ok(()) } #[derive(Serialize, Debug, Default, Type)] diff --git a/src-tauri/src/error.rs b/src-tauri/src/error.rs index cc2b9cfb..607289d7 100644 --- a/src-tauri/src/error.rs +++ b/src-tauri/src/error.rs @@ -63,6 +63,9 @@ pub enum Error { #[error("No moves found")] NoMovesFound, + #[error("Lower or upper bound")] + LowerOrUpperBound, + #[error("Search stopped")] SearchStopped, diff --git a/src/components/panels/analysis/BestMoves.tsx b/src/components/panels/analysis/BestMoves.tsx index 92e23970..f18f93e6 100644 --- a/src/components/panels/analysis/BestMoves.tsx +++ b/src/components/panels/analysis/BestMoves.tsx @@ -109,9 +109,12 @@ export default function BestMovesComponent({ }; } waitForMove(); - }, []); + }, [activeTab, dispatch, engine.path, id, setArrows]); - const chess = new Chess(fen); + const isGameOver = useMemo(() => { + const chess = new Chess(fen); + return chess.isGameOver(); + }, [fen]); useThrottledEffect( () => { @@ -166,7 +169,7 @@ export default function BestMovesComponent({ onClick={() => { setSettings((prev) => ({ ...prev, enabled: !prev.enabled })); }} - disabled={chess.isGameOver()} + disabled={isGameOver} ml={12} > {settings.enabled ? ( @@ -295,15 +298,23 @@ export default function BestMovesComponent({ ), [ settings.enabled, - settings.numberLines, - settings.maxDepth, settings.cores, + settings.maxDepth, + settings.numberLines, + theme.primaryColor, + isGameOver, + engine.name, engineVariations, - threat, - settingsOn, progress, nps, + classes.subtitle, depth, + threat, + settingsOn, + setSettings, + toggleThreat, + toggleSettingsOn, + halfMoves, ] ); }