From 91345848c6f23505e967958e75878a41be0dafb8 Mon Sep 17 00:00:00 2001 From: stefiosif Date: Sat, 1 Mar 2025 23:54:51 +0200 Subject: [PATCH] Spawn a separate thread to receive UCI messages via mpsc channel, simplify output to println --- src/interface/uci.rs | 74 ++++++++++--------------------- src/main.rs | 6 +-- src/search/iterative_deepening.rs | 52 ++++++---------------- 3 files changed, 38 insertions(+), 94 deletions(-) diff --git a/src/interface/uci.rs b/src/interface/uci.rs index 8b37fe5..34e296c 100644 --- a/src/interface/uci.rs +++ b/src/interface/uci.rs @@ -1,7 +1,9 @@ use std::{ collections::HashMap, - io::{BufRead, Write}, + io::{self, BufRead}, str::SplitWhitespace, + sync::mpsc, + thread, }; use anyhow::{anyhow, bail}; @@ -51,12 +53,6 @@ pub enum Response { Info(String), } -pub fn write_response(handle_out: &mut impl Write, response: &Response) -> anyhow::Result<()> { - writeln!(handle_out, "{response}").map_err(|e| anyhow!(e))?; - handle_out.flush().map_err(|e| anyhow!(e))?; - Ok(()) -} - use std::fmt; impl fmt::Display for Response { @@ -220,16 +216,24 @@ fn parse_next(iter: &mut SplitWhitespace, val: &str) -> an .and_then(|v| v.parse::().map_err(|_| anyhow!("Invalid {val}"))) } -pub fn uci_loop(input: R, mut output: W) -> anyhow::Result<()> { +pub fn uci_loop() -> anyhow::Result<()> { let mut params = UciParameters::new(); + let (uci_sender, uci_receiver) = mpsc::channel(); - for line in input.lines() { + thread::spawn(move || { + let input = io::stdin().lock(); + for line in input.lines() { + uci_sender.send(line).expect("Failed to send line"); + } + }); + + while let Ok(line) = uci_receiver.recv() { let line_str = line.unwrap_or_else(|_| "quit".to_string()); let mut parts = line_str.split_whitespace(); let command = parse_command(&mut parts)?; match command { - Command::Uci => write_response(&mut output, &Response::UciOk)?, - Command::IsReady => write_response(&mut output, &Response::ReadyOk)?, + Command::Uci => println!("{}", Response::UciOk), + Command::IsReady => println!("{}", Response::ReadyOk), Command::UciNewGame => { if let Some(game) = params.game.as_mut() { game.tt = TranspositionTable::new(); @@ -243,17 +247,16 @@ pub fn uci_loop(input: R, mut output: W) -> anyhow::Result Command::Go => { if let Some(game) = params.game.as_mut() { match uci_go(&mut parts, game) { - Ok(best_move) => write_response( - &mut output, - &Response::BestMove(best_move.parse_into_str()), - ), - Err(e) => write_response(&mut output, &Response::Info(e.to_string())), - }?; + Ok(best_move) => { + println!("{}", Response::BestMove(best_move.parse_into_str())) + } + Err(e) => println!("{}", Response::Info(e.to_string())), + } } else { - write_response( - &mut output, - &Response::Info("Failed to unwrap from UciParameter".to_string()), - )?; + println!( + "{}", + Response::Info("Failed to unwrap from UciParameter".to_string()) + ) }; } Command::Stop => break, @@ -267,8 +270,6 @@ pub fn uci_loop(input: R, mut output: W) -> anyhow::Result }; } }; - - output.flush().map_err(|e| anyhow!(e))? } Ok(()) @@ -276,8 +277,6 @@ pub fn uci_loop(input: R, mut output: W) -> anyhow::Result #[cfg(test)] mod tests { - use std::io::Cursor; - use crate::{ board::{fen::from_fen, square::Square}, interface::uci::{parse_command, Command}, @@ -285,7 +284,6 @@ mod tests { }; use super::uci_go; - use super::uci_loop; use super::uci_position; const FEN: [&str; 2] = [ @@ -326,28 +324,4 @@ mod tests { Ok(()) } - - #[test] - fn test_uci_loop() -> anyhow::Result<()> { - init_attacks(); - let commands = "uci\n\ - ucinewgame\n\ - position fen 8/8/8/8/8/4q1k1/8/5K2 b - - 0 1\n\ - go\n\ - quit"; - let input = Cursor::new(commands); - let mut output: Vec<_> = vec![]; - - uci_loop(input, &mut output)?; - - let expected_response = "id name zeal\n\ - id author stefiosif\n\ - uciok\n\ - bestmove e3f2\n"; - let actual_response = String::from_utf8(output).expect("Invalid UTF-8 in output"); - - assert_eq!(expected_response, actual_response); - - Ok(()) - } } diff --git a/src/main.rs b/src/main.rs index 97affc2..d925f1c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,3 @@ -use std::io; - mod board; mod evaluation; mod interface; @@ -28,9 +26,7 @@ fn main() { match Args::parse().mode { Some(Mode::Perft) => search::perft::perftree_script(), Some(Mode::Uci) | None => { - let input = io::stdin().lock(); - let output = io::stdout().lock(); - uci::uci_loop(input, output).unwrap_or_else(|e| println!("{e}")); + uci::uci_loop().unwrap_or_else(|e| println!("{e}")); } } } diff --git a/src/search/iterative_deepening.rs b/src/search/iterative_deepening.rs index 78601c5..4c90205 100644 --- a/src/search/iterative_deepening.rs +++ b/src/search/iterative_deepening.rs @@ -1,9 +1,7 @@ -use std::io; - use crate::{ board::game::Game, evaluation::{MAX_SCORE, MIN_SCORE}, - interface::uci::{write_response, Response}, + interface::uci::Response, movegen::r#move::Move, }; @@ -19,10 +17,7 @@ pub fn iterative_deepening( for depth in 1..=max_depth { if time.exceed_soft_limit() { - write_response( - &mut io::stdout(), - &Response::Info("Soft limit exceeded in negamax".to_string()), - )?; + println!("Soft limit exceeded in negamax"); return Ok(best_move); } @@ -44,7 +39,7 @@ pub fn iterative_deepening( let score = match score { Ok(score) => score, Err(ref e) => { - write_response(&mut io::stdout(), &Response::Info(format!("{e}")))?; + println!("Error: {}", Response::Info(e.to_string())); break_from_aw = false; break; } @@ -68,46 +63,25 @@ pub fn iterative_deepening( } if let Err(e) = score { - write_response(&mut io::stdout(), &Response::Info(format!("{e}")))?; + println!("Error: {}", Response::Info(e.to_string())); break; } best_move = game.tt.lookup(game.hash).and_then(|entry| entry.mv); - write_response( - &mut io::stdout(), - &log_depth_results( - depth, - time.instant.elapsed().as_secs(), - nodes, - time.nps(nodes), - score?, - best_move, - ), - )?; + println!( + "info depth {} ms {} nodes {} nps {} eval {} bestmove {}", + depth, + time.instant.elapsed().as_millis(), + nodes, + time.nps(nodes), + score?, + best_move.expect("No best move found") + ); } Ok(best_move) } -fn log_depth_results( - depth: u8, - seconds: u64, - nodes: u64, - nps: u64, - best_score: i16, - best_move: Option, -) -> Response { - Response::Info(format!( - "info depth {} seconds {} nodes {} nps {} eval {} bestmove {}", - depth, - seconds, - nodes, - nps, - best_score, - best_move.expect("No best move found") - )) -} - #[cfg(test)] mod tests { use crate::{