From ee06bfef0ba69cd4528ec3c90d42ed43c4eb38a2 Mon Sep 17 00:00:00 2001 From: dece Date: Wed, 3 Jun 2020 09:55:41 +0200 Subject: [PATCH] uci: threaded setup Properly (I hope so) thread standard input and engine communications. --- src/engine.rs | 38 ++++++++++++---------- src/main.rs | 6 ++-- src/uci.rs | 87 ++++++++++++++++++++++++++------------------------- 3 files changed, 69 insertions(+), 62 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 02f7802..9017133 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -23,17 +23,18 @@ pub enum Mode { // No mode, sit here and do nothing. No, // UCI mode: listen to Cmds, send Uci::Cmd::Engine commands. - Uci(mpsc::Receiver, mpsc::Sender), + Uci(mpsc::Sender, mpsc::Receiver), } #[derive(Debug)] pub enum Cmd { // Commands that can be received by the engine. - Ping(String), // Test if the engine is responding. + Ping(String), // Test if the engine is responding. + UciChannel(mpsc::Sender), // Provide a sender to UCI to start receiving commands. UciPosition(Vec), // UCI "position" command. // Commands that can be sent by the engine. - Pong(String), // Answer a Ping command with the same payload. + } pub const CASTLING_WH_K: u8 = 0b00000001; @@ -43,7 +44,7 @@ pub const CASTLING_BL_Q: u8 = 0b00001000; pub const CASTLING_MASK: u8 = 0b00001111; impl Engine { - pub fn new(mode: Mode) -> Engine { + pub fn new() -> Engine { Engine { board: board::new_empty(), color: board::SQ_WH, @@ -51,20 +52,32 @@ impl Engine { en_passant: None, halfmove: 0, fullmove: 1, - mode, + mode: Mode::No, } } + /// Setup engine for UCI communication. + pub fn setup_uci(&mut self, uci_s: mpsc::Sender) { + // Create a channel to receive commands from Uci. + let (engine_s, engine_r) = mpsc::channel(); + uci_s.send(uci::Cmd::Engine(Cmd::UciChannel(engine_s))).unwrap(); + self.mode = Mode::Uci(uci_s, engine_r); + self.listen(); + } + /// Listen for incoming commands. /// /// In UCI mode, read incoming Cmds over the MPSC channel. /// In no modes, stop listening immediately. pub fn listen(&mut self) { loop { - match self.mode { + match &self.mode { Mode::No => break, - Mode::Uci(rx, tx) => { - self.recv_uci(rx, tx); + Mode::Uci(tx, rx) => { + match rx.recv() { + Ok(c) => eprintln!("Unhandled command: {:?}", c), + Err(e) => eprintln!("Engine recv failure: {}", e), + } } } } @@ -131,13 +144,4 @@ impl Engine { let best_move = moves.iter().choose(&mut rng).unwrap(); *best_move } - - /// Receive a command from Uci. - pub fn recv_uci(&mut self, rx: mpsc::Receiver, tx: mpsc::Sender) { - match rx.recv() { - Ok(Cmd::Ping(s)) => tx.send(uci::Cmd::Engine(Cmd::Pong(s))).unwrap(), - Ok(c) => eprintln!("Unhandled command: {:?}", c), - Err(e) => eprintln!("Engine recv failure: {}", e), - } - } } diff --git a/src/main.rs b/src/main.rs index 635d6ad..dd1d253 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,9 +20,9 @@ fn main() { .possible_values(&["w", "white", "b", "black"]))) .subcommand(SubCommand::with_name("uci") .about("Start engine in UCI mode") - .arg(Arg::with_name("output") - .help("Log file path") - .short("o").long("output").takes_value(true).required(false))) + .arg(Arg::with_name("log_file") + .help("Log file path (default is stderr)") + .long("log-file").takes_value(true).required(false))) .get_matches(); process::exit(match matches.subcommand() { diff --git a/src/uci.rs b/src/uci.rs index a367b41..633a772 100644 --- a/src/uci.rs +++ b/src/uci.rs @@ -66,14 +66,15 @@ impl Uci { /// Start a new UCI listening for standard input. pub fn start(output: Option<&str>) { // Create the UCI queue, both for standard IO and for engine communication. - let (uci_tx, uci_rx): (mpsc::Sender, mpsc::Receiver) = mpsc::channel(); + let (uci_s, uci_r): (mpsc::Sender, mpsc::Receiver) = mpsc::channel(); + let stdin_tx = uci_s.clone(); thread::spawn(move || { - read_stdin(uci_tx); + Uci::read_stdin(stdin_tx); }); let mut uci = Uci { state: State::Init, - cmd_channel: (uci_tx, uci_rx), + cmd_channel: (uci_s, uci_r), engine_in: None, logfile: None, }; @@ -93,12 +94,13 @@ impl Uci { loop { match self.cmd_channel.1.recv() { Ok(Cmd::Stdin(cmd)) => { + self.log(format!("UCI >>> {}", cmd)); if !self.handle_command(&parse_command(&cmd)) { break } } Ok(Cmd::Engine(cmd)) => { - // TODO + self.handle_engine_command(&cmd); } Err(e) => self.log(format!("Can't read commands: {}", e)) } @@ -118,12 +120,29 @@ impl Uci { } } - /// Read a command from the interface. - fn receive(&mut self) -> Option { + /// Read lines over stdin, notifying over an MPSC channel. + /// + /// As it is not trivial to add a timeout, or overly complicated + /// to break the loop with a second channel, simply stop listening + /// when the UCI "quit" command is received. + /// + /// This is not an Uci method as it does not need to act on the + /// instance itself. + pub fn read_stdin(tx: mpsc::Sender) { let mut s = String::new(); - match io::stdin().read_line(&mut s) { - Ok(_) => { self.log(format!(">>> {}", s.trim_end())); Some(s.trim().to_string()) } - Err(e) => { self.log(format!("Failed to read input: {:?}", e)); None } + loop { + match io::stdin().read_line(&mut s) { + Ok(_) => { + let s = s.trim(); + tx.send(Cmd::Stdin(s.to_string())).unwrap(); + if s == "quit" { + break; + } + } + Err(e) => { + eprintln!("Failed to read input: {:?}", e); + } + } } } @@ -133,7 +152,7 @@ impl Uci { println!("{}", s); } - /// Handle an UCI command, return false if engine should stop listening. + /// Handle an UCI command, return false if it should stop listening. fn handle_command(&mut self, cmd: &UciCmd) -> bool { match cmd { UciCmd::Uci => if self.state == State::Init { @@ -150,11 +169,23 @@ impl Uci { UciCmd::Go(g) => if self.state == State::Ready { self.go(g) } UciCmd::Quit => return false, UciCmd::Unknown(c) => { self.log(format!("Unknown command: {}", c)); } - _ => {} } true } + /// Handle an engine command. + fn handle_engine_command(&mut self, cmd: &engine::Cmd) { + match cmd { + engine::Cmd::UciChannel(s) => { + self.engine_in = Some(s.to_owned()); + // Send a ping to the engine to ensure communication. + let ping = engine::Cmd::Ping("test".to_string()); + self.engine_in.as_ref().unwrap().send(ping).unwrap(); + } + _ => {} + } + } + /// Send IDs to interface. fn send_identities(&mut self) { self.send(&format!("id name {}", VATU_NAME)); @@ -163,17 +194,11 @@ impl Uci { } fn setup_engine(&mut self) { - // Create the channel to send commands to the engine. - let (uci_out, engine_in): (mpsc::Sender, mpsc::Receiver) = - mpsc::channel(); - self.engine_in = Some(uci_out); - // Pass the general Uci receiver to the engine as well. - let engine_out = self.cmd_channel.0; + let uci_s = self.cmd_channel.0.clone(); thread::spawn(move || { - let mut engine = engine::Engine::new(engine::Mode::Uci(engine_in, engine_out)); - engine.listen(); + let mut engine = engine::Engine::new(); + engine.setup_uci(uci_s); }); - self.engine_in.as_ref().unwrap().send(engine::Cmd::Ping("test".to_string())); self.state = State::Ready; } @@ -226,28 +251,6 @@ impl Uci { } } -/// Read lines over stdin, notifying over an MPSC channel. -/// -/// As it is not trivial to add a timeout, or overly complicated to -/// break the loop with a second channel, simply stop listening when -/// the UCI "quit" command is received. -pub fn read_stdin(tx: mpsc::Sender) { - let mut s = String::new(); - loop { - match io::stdin().read_line(&mut s) { - Ok(_) => { - let s = s.trim(); - tx.send(Cmd::Stdin(s.to_string())); - if s == "quit" { - break; - } - } - Err(e) => { - eprintln!("Failed to read input: {:?}", e); - } - } - } -} /// Parse an UCI command. fn parse_command(s: &str) -> UciCmd {