uci: threaded setup
Properly (I hope so) thread standard input and engine communications.
This commit is contained in:
parent
f6851f6ef7
commit
ee06bfef0b
|
@ -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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
85
src/uci.rs
85
src/uci.rs
|
@ -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();
|
||||||
|
loop {
|
||||||
match io::stdin().read_line(&mut s) {
|
match io::stdin().read_line(&mut s) {
|
||||||
Ok(_) => { self.log(format!(">>> {}", s.trim_end())); Some(s.trim().to_string()) }
|
Ok(_) => {
|
||||||
Err(e) => { self.log(format!("Failed to read input: {:?}", e)); None }
|
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 {
|
||||||
|
|
Reference in a new issue