use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{self, Sender};
use std::sync::Arc;
use std::thread::JoinHandle;
use crate::error::{self, ErrorKind};
use crate::position::{Game, Position};
use crate::search::{self, SearchResult};
use crate::timeman::Mode;
use crate::TranspositionTable;
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct EngineBuilder {
game: Game,
transpositions_mb: usize,
num_threads: usize,
debug: bool,
}
impl EngineBuilder {
pub fn new() -> Self {
Self {
game: Game::start_position(),
transpositions_mb: 1,
num_threads: 1,
debug: true,
}
}
pub fn build(&self) -> Engine {
let tt = Arc::new(TranspositionTable::with_mb(self.transpositions_mb));
let stopper = Arc::new(AtomicBool::new(false));
Engine {
game: self.game.clone(),
tt,
stopper,
debug: self.debug,
search_handle: None,
}
}
pub fn game(mut self, game: Game) -> Self {
self.game = game;
self
}
pub fn position(mut self, position: Position) -> Self {
self.game = Game::from(position);
self
}
pub fn threads(mut self, num_threads: usize) -> Self {
self.num_threads = num_threads;
self
}
pub fn transpositions_mb(mut self, transpositions_mb: usize) -> Self {
self.transpositions_mb = transpositions_mb;
self
}
pub fn debug(mut self, debug: bool) -> Self {
self.debug = debug;
self
}
}
pub struct Engine {
game: Game,
tt: Arc<TranspositionTable>,
stopper: Arc<AtomicBool>,
debug: bool,
search_handle: Option<JoinHandle<()>>,
}
impl Engine {
pub fn new() -> Self {
Self {
game: Game::from(Position::start_position()),
tt: Arc::new(TranspositionTable::new()),
stopper: Arc::new(AtomicBool::new(false)),
debug: true,
search_handle: None,
}
}
pub fn game(&self) -> &Game {
&self.game
}
pub fn debug(&self) -> &bool {
&self.debug
}
pub fn transposition_table(&self) -> &TranspositionTable {
&self.tt
}
pub fn set_game<T: Into<Game>>(&mut self, game: T) {
self.game = game.into();
}
pub fn set_debug(&mut self, new_debug: bool) {
self.debug = new_debug;
}
pub fn new_game(&mut self) -> error::Result<()> {
self.try_clear_transpositions()
}
pub fn try_set_transpositions_mb(&mut self, new_mb: usize) -> error::Result<usize> {
Arc::get_mut(&mut self.tt)
.map(|inner_tt| inner_tt.set_mb(new_mb))
.ok_or(ErrorKind::EngineTranspositionTableInUse.into())
}
pub fn try_clear_transpositions(&mut self) -> error::Result<()> {
Arc::get_mut(&mut self.tt)
.map(|inner_tt| inner_tt.clear())
.ok_or(ErrorKind::EngineTranspositionTableInUse.into())
}
pub fn search_sync(&mut self, mode: Mode) -> SearchResult {
self.stop();
self.wait();
self.unstop();
let (sender, receiver) = mpsc::channel();
self.search(mode, sender).unwrap();
self.wait();
receiver.recv().unwrap()
}
pub fn search<T>(&mut self, mode: Mode, sender: Sender<T>) -> error::Result<()>
where
T: From<SearchResult> + Send + 'static,
{
if self.search_handle.is_none() {
self.unstop();
let handle = search::search_nonblocking(
self.game.clone(),
mode,
Arc::clone(&self.tt),
Arc::clone(&self.stopper),
self.debug,
sender,
);
self.search_handle = Some(handle);
Ok(())
} else {
Err((ErrorKind::EngineAlreadySearching, "failed to begin search").into())
}
}
pub fn ponder(&self) {
todo!()
}
pub fn stop(&self) {
self.stopper.store(true, Ordering::Relaxed);
}
pub fn unstop(&self) {
self.stopper.store(false, Ordering::Relaxed);
}
pub fn wait(&mut self) {
let handle_opt = self.search_handle.take();
if let Some(handle) = handle_opt {
handle.join().unwrap();
}
}
pub fn ready(&self) -> bool {
self.search_handle.is_none()
}
pub fn shutdown(self) {}
}
impl Default for Engine {
fn default() -> Self {
Self::new()
}
}
impl Drop for Engine {
fn drop(&mut self) {
self.stop();
self.wait();
}
}