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,
|
||||
// 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)]
|
||||
pub enum Cmd {
|
||||
// Commands that can be received by the engine.
|
||||
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.
|
||||
|
||||
// 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<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.
|
||||
///
|
||||
/// 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<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"])))
|
||||
.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() {
|
||||
|
|
85
src/uci.rs
85
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<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 || {
|
||||
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<String> {
|
||||
/// 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<Cmd>) {
|
||||
let mut s = String::new();
|
||||
loop {
|
||||
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 }
|
||||
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<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;
|
||||
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<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.
|
||||
fn parse_command(s: &str) -> UciCmd {
|
||||
|
|
Reference in a new issue