uci: threaded setup

Properly (I hope so) thread standard input and engine communications.
This commit is contained in:
dece 2020-06-03 09:55:41 +02:00
parent f6851f6ef7
commit ee06bfef0b
3 changed files with 69 additions and 62 deletions

View file

@ -23,17 +23,18 @@ pub enum Mode {
// No mode, sit here and do nothing. // No mode, sit here and do nothing.
No, No,
// UCI mode: listen to Cmds, send Uci::Cmd::Engine commands. // UCI mode: listen to Cmds, send Uci::Cmd::Engine commands.
Uci(mpsc::Receiver<Cmd>, mpsc::Sender<uci::Cmd>), Uci(mpsc::Sender<uci::Cmd>, mpsc::Receiver<Cmd>),
} }
#[derive(Debug)] #[derive(Debug)]
pub enum Cmd { pub enum Cmd {
// Commands that can be received by the engine. // 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<Cmd>), // Provide a sender to UCI to start receiving commands.
UciPosition(Vec<uci::PositionArgs>), // UCI "position" command. UciPosition(Vec<uci::PositionArgs>), // UCI "position" command.
// Commands that can be sent by the engine. // 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; pub const CASTLING_WH_K: u8 = 0b00000001;
@ -43,7 +44,7 @@ pub const CASTLING_BL_Q: u8 = 0b00001000;
pub const CASTLING_MASK: u8 = 0b00001111; pub const CASTLING_MASK: u8 = 0b00001111;
impl Engine { impl Engine {
pub fn new(mode: Mode) -> Engine { pub fn new() -> Engine {
Engine { Engine {
board: board::new_empty(), board: board::new_empty(),
color: board::SQ_WH, color: board::SQ_WH,
@ -51,20 +52,32 @@ impl Engine {
en_passant: None, en_passant: None,
halfmove: 0, halfmove: 0,
fullmove: 1, fullmove: 1,
mode, mode: Mode::No,
} }
} }
/// Setup engine for UCI communication.
pub fn setup_uci(&mut self, uci_s: mpsc::Sender<uci::Cmd>) {
// 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. /// Listen for incoming commands.
/// ///
/// In UCI mode, read incoming Cmds over the MPSC channel. /// In UCI mode, read incoming Cmds over the MPSC channel.
/// In no modes, stop listening immediately. /// In no modes, stop listening immediately.
pub fn listen(&mut self) { pub fn listen(&mut self) {
loop { loop {
match self.mode { match &self.mode {
Mode::No => break, Mode::No => break,
Mode::Uci(rx, tx) => { Mode::Uci(tx, rx) => {
self.recv_uci(rx, tx); 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(); let best_move = moves.iter().choose(&mut rng).unwrap();
*best_move *best_move
} }
/// Receive a command from Uci.
pub fn recv_uci(&mut self, rx: mpsc::Receiver<Cmd>, tx: mpsc::Sender<uci::Cmd>) {
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),
}
}
} }

View file

@ -20,9 +20,9 @@ fn main() {
.possible_values(&["w", "white", "b", "black"]))) .possible_values(&["w", "white", "b", "black"])))
.subcommand(SubCommand::with_name("uci") .subcommand(SubCommand::with_name("uci")
.about("Start engine in UCI mode") .about("Start engine in UCI mode")
.arg(Arg::with_name("output") .arg(Arg::with_name("log_file")
.help("Log file path") .help("Log file path (default is stderr)")
.short("o").long("output").takes_value(true).required(false))) .long("log-file").takes_value(true).required(false)))
.get_matches(); .get_matches();
process::exit(match matches.subcommand() { process::exit(match matches.subcommand() {

View file

@ -66,14 +66,15 @@ impl Uci {
/// Start a new UCI listening for standard input. /// Start a new UCI listening for standard input.
pub fn start(output: Option<&str>) { pub fn start(output: Option<&str>) {
// Create the UCI queue, both for standard IO and for engine communication. // Create the UCI queue, both for standard IO and for engine communication.
let (uci_tx, uci_rx): (mpsc::Sender<Cmd>, mpsc::Receiver<Cmd>) = mpsc::channel(); let (uci_s, uci_r): (mpsc::Sender<Cmd>, mpsc::Receiver<Cmd>) = mpsc::channel();
let stdin_tx = uci_s.clone();
thread::spawn(move || { thread::spawn(move || {
read_stdin(uci_tx); Uci::read_stdin(stdin_tx);
}); });
let mut uci = Uci { let mut uci = Uci {
state: State::Init, state: State::Init,
cmd_channel: (uci_tx, uci_rx), cmd_channel: (uci_s, uci_r),
engine_in: None, engine_in: None,
logfile: None, logfile: None,
}; };
@ -93,12 +94,13 @@ impl Uci {
loop { loop {
match self.cmd_channel.1.recv() { match self.cmd_channel.1.recv() {
Ok(Cmd::Stdin(cmd)) => { Ok(Cmd::Stdin(cmd)) => {
self.log(format!("UCI >>> {}", cmd));
if !self.handle_command(&parse_command(&cmd)) { if !self.handle_command(&parse_command(&cmd)) {
break break
} }
} }
Ok(Cmd::Engine(cmd)) => { Ok(Cmd::Engine(cmd)) => {
// TODO self.handle_engine_command(&cmd);
} }
Err(e) => self.log(format!("Can't read commands: {}", e)) Err(e) => self.log(format!("Can't read commands: {}", e))
} }
@ -118,12 +120,29 @@ impl Uci {
} }
} }
/// Read a command from the interface. /// Read lines over stdin, notifying over an MPSC channel.
fn receive(&mut self) -> Option<String> { ///
/// 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<Cmd>) {
let mut s = String::new(); let mut s = String::new();
match io::stdin().read_line(&mut s) { loop {
Ok(_) => { self.log(format!(">>> {}", s.trim_end())); Some(s.trim().to_string()) } match io::stdin().read_line(&mut s) {
Err(e) => { self.log(format!("Failed to read input: {:?}", e)); None } 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); 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 { fn handle_command(&mut self, cmd: &UciCmd) -> bool {
match cmd { match cmd {
UciCmd::Uci => if self.state == State::Init { 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::Go(g) => if self.state == State::Ready { self.go(g) }
UciCmd::Quit => return false, UciCmd::Quit => return false,
UciCmd::Unknown(c) => { self.log(format!("Unknown command: {}", c)); } UciCmd::Unknown(c) => { self.log(format!("Unknown command: {}", c)); }
_ => {}
} }
true 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. /// Send IDs to interface.
fn send_identities(&mut self) { fn send_identities(&mut self) {
self.send(&format!("id name {}", VATU_NAME)); self.send(&format!("id name {}", VATU_NAME));
@ -163,17 +194,11 @@ impl Uci {
} }
fn setup_engine(&mut self) { fn setup_engine(&mut self) {
// Create the channel to send commands to the engine. let uci_s = self.cmd_channel.0.clone();
let (uci_out, engine_in): (mpsc::Sender<engine::Cmd>, mpsc::Receiver<engine::Cmd>) =
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;
thread::spawn(move || { thread::spawn(move || {
let mut engine = engine::Engine::new(engine::Mode::Uci(engine_in, engine_out)); let mut engine = engine::Engine::new();
engine.listen(); engine.setup_uci(uci_s);
}); });
self.engine_in.as_ref().unwrap().send(engine::Cmd::Ping("test".to_string()));
self.state = State::Ready; 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<Cmd>) {
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. /// Parse an UCI command.
fn parse_command(s: &str) -> UciCmd { fn parse_command(s: &str) -> UciCmd {