<commit_before>//! cargo_check provides the functionality needed to run `cargo check` or
//! another compatible command (f.x. clippy) in a background thread and provide
//! LSP diagnostics based on the output of the command.
use cargo_metadata::Message;
use crossbeam_channel::{never, select, unbounded, Receiver, RecvError, Sender};
use lsp_types::{
    Diagnostic, Url, WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressEnd,
    WorkDoneProgressReport,
};
use std::{
    collections::HashMap,
    io::BufReader,
    path::PathBuf,
    process::{Command, Stdio},
    sync::Arc,
    thread::JoinHandle,
    time::Instant,
};

mod conv;

use crate::conv::{map_rust_diagnostic_to_lsp, MappedRustDiagnostic, SuggestedFix};

pub use crate::conv::url_from_path_with_drive_lowercasing;

#[derive(Clone, Debug)]
pub struct CheckOptions {
    pub enable: bool,
    pub args: Vec<String>,
    pub command: String,
    pub all_targets: bool,
}

/// CheckWatcher wraps the shared state and communication machinery used for
/// running `cargo check` (or other compatible command) and providing
/// diagnostics based on the output.
/// The spawned thread is shut down when this struct is dropped.
#[derive(Debug)]
pub struct CheckWatcher {
    pub task_recv: Receiver<CheckTask>,
    pub state: Arc<CheckState>,
    cmd_send: Option<Sender<CheckCommand>>,
    handle: Option<JoinHandle<()>>,
}

impl CheckWatcher {
    pub fn new(options: &CheckOptions, workspace_root: PathBuf) -> CheckWatcher {
        let options = options.clone();
        let state = Arc::new(CheckState::new());

        let (task_send, task_recv) = unbounded::<CheckTask>();
        let (cmd_send, cmd_recv) = unbounded::<CheckCommand>();
        let handle = std::thread::spawn(move || {
            let mut check = CheckWatcherThread::new(options, workspace_root);
            check.run(&task_send, &cmd_recv);
        });
        CheckWatcher { task_recv, cmd_send: Some(cmd_send), handle: Some(handle), state }
    }

    /// Returns a CheckWatcher that doesn't actually do anything
    pub fn dummy() -> CheckWatcher {
        let state = Arc::new(CheckState::new());
        CheckWatcher { task_recv: never(), cmd_send: None, handle: None, state }
    }

    /// Schedule a re-start of the cargo check worker.
    pub fn update(&self) {
        if let Some(cmd_send) = &self.cmd_send {
            cmd_send.send(CheckCommand::Update).unwrap();
        }
    }
}

impl std::ops::Drop for CheckWatcher {
    fn drop(&mut self) {
        if let Some(handle) = self.handle.take() {
            // Take the sender out of the option
            let recv = self.cmd_send.take();

            // Dropping the sender finishes the thread loop
            drop(recv);

            // Join the thread, it should finish shortly. We don't really care
            // whether it panicked, so it is safe to ignore the result
            let _ = handle.join();
        }
    }
}

#[derive(Clone, Debug)]
pub struct CheckState {
    diagnostic_collection: HashMap<Url, Vec<Diagnostic>>,
    suggested_fix_collection: HashMap<Url, Vec<SuggestedFix>>,
}

impl CheckState {
    fn new() -> CheckState {
        CheckState {
            diagnostic_collection: HashMap::new(),
            suggested_fix_collection: HashMap::new(),
        }
    }

    /// Clear the cached diagnostics, and schedule updating diagnostics by the
    /// server, to clear stale results.
    pub fn clear(&mut self) -> Vec<Url> {
        let cleared_files: Vec<Url> = self.diagnostic_collection.keys().cloned().collect();
        self.diagnostic_collection.clear();
        self.suggested_fix_collection.clear();
        cleared_files
    }

    pub fn diagnostics_for(&self, uri: &Url) -> Option<&[Diagnostic]> {
        self.diagnostic_collection.get(uri).map(|d| d.as_slice())
    }

    pub fn fixes_for(&self, uri: &Url) -> Option<&[SuggestedFix]> {
        self.suggested_fix_collection.get(uri).map(|d| d.as_slice())
    }

    pub fn add_diagnostic_with_fixes(&mut self, file_uri: Url, diagnostic: DiagnosticWithFixes) {
        for fix in diagnostic.suggested_fixes {
            self.add_suggested_fix_for_diagnostic(fix, &diagnostic.diagnostic);
        }
        self.add_diagnostic(file_uri, diagnostic.diagnostic);
    }

    fn add_diagnostic(&mut self, file_uri: Url, diagnostic: Diagnostic) {
        let diagnostics = self.diagnostic_collection.entry(file_uri).or_default();

        // If we're building multiple targets it's possible we've already seen this diagnostic
        let is_duplicate = diagnostics.iter().any(|d| are_diagnostics_equal(d, &diagnostic));
        if is_duplicate {
            return;
        }

        diagnostics.push(diagnostic);
    }

    fn add_suggested_fix_for_diagnostic(
        &mut self,
        mut suggested_fix: SuggestedFix,
        diagnostic: &Diagnostic,
    ) {
        let file_uri = suggested_fix.location.uri.clone();
        let file_suggestions = self.suggested_fix_collection.entry(file_uri).or_default();

        let existing_suggestion: Option<&mut SuggestedFix> =
            file_suggestions.iter_mut().find(|s| s == &&suggested_fix);
        if let Some(existing_suggestion) = existing_suggestion {
            // The existing suggestion also applies to this new diagnostic
            existing_suggestion.diagnostics.push(diagnostic.clone());
        } else {
            // We haven't seen this suggestion before
            suggested_fix.diagnostics.push(diagnostic.clone());
            file_suggestions.push(suggested_fix);
        }
    }
}

#[derive(Debug)]
pub enum CheckTask {
    /// Request a clearing of all cached diagnostics from the check watcher
    ClearDiagnostics,

    /// Request adding a diagnostic with fixes included to a file
    AddDiagnostic(Url, DiagnosticWithFixes),

    /// Request check progress notification to client
    Status(WorkDoneProgress),
}

pub enum CheckCommand {
    /// Request re-start of check thread
    Update,
}

struct CheckWatcherThread {
    options: CheckOptions,
    workspace_root: PathBuf,
    watcher: WatchThread,
    last_update_req: Option<Instant>,
}

impl CheckWatcherThread {
    fn new(options: CheckOptions, workspace_root: PathBuf) -> CheckWatcherThread {
        CheckWatcherThread {
            options,
            workspace_root,
            watcher: WatchThread::dummy(),
            last_update_req: None,
        }
    }

    fn run(&mut self, task_send: &Sender<CheckTask>, cmd_recv: &Receiver<CheckCommand>) {
        loop {
            select! {
                recv(&cmd_recv) -> cmd => match cmd {
                    Ok(cmd) => self.handle_command(cmd),
                    Err(RecvError) => {
                        // Command channel has closed, so shut down
                        break;
                    },
                },
                recv(self.watcher.message_recv) -> msg => match msg {
                    Ok(msg) => self.handle_message(msg, task_send),
                    Err(RecvError) => {
                        // Watcher finished, replace it with a never channel to
                        // avoid busy-waiting.
                        std::mem::replace(&mut self.watcher.message_recv, never());
                    },
                }
            };

            if self.should_recheck() {
                self.last_update_req.take();
                task_send.send(CheckTask::ClearDiagnostics).unwrap();

                // By replacing the watcher, we drop the previous one which
                // causes it to shut down automatically.
                self.watcher = WatchThread::new(&self.options, &self.workspace_root);
            }
        }
    }

    fn should_recheck(&mut self) -> bool {
        if let Some(_last_update_req) = &self.last_update_req {
            // We currently only request an update on save, as we need up to
            // date source on disk for cargo check to do it's magic, so we
            // don't really need to debounce the requests at this point.
            return true;
        }
        false
    }

    fn handle_command(&mut self, cmd: CheckCommand) {
        match cmd {
            CheckCommand::Update => self.last_update_req = Some(Instant::now()),
        }
    }

    fn handle_message(&self, msg: CheckEvent, task_send: &Sender<CheckTask>) {
        match msg {
            CheckEvent::Begin => {
                task_send
                    .send(CheckTask::Status(WorkDoneProgress::Begin(WorkDoneProgressBegin {
                        title: "Running 'cargo check'".to_string(),
                        cancellable: Some(false),
                        message: None,
                        percentage: None,
                    })))
                    .unwrap();
            }

            CheckEvent::End => {
                task_send
                    .send(CheckTask::Status(WorkDoneProgress::End(WorkDoneProgressEnd {
                        message: None,
                    })))
                    .unwrap();
            }

            CheckEvent::Msg(Message::CompilerArtifact(msg)) => {
                task_send
                    .send(CheckTask::Status(WorkDoneProgress::Report(WorkDoneProgressReport {
                        cancellable: Some(false),
                        message: Some(msg.target.name),
                        percentage: None,
                    })))
                    .unwrap();
            }

            CheckEvent::Msg(Message::CompilerMessage(msg)) => {
                let map_result =
                    match map_rust_diagnostic_to_lsp(&msg.message, &self.workspace_root) {
                        Some(map_result) => map_result,
                        None => return,
                    };

                let MappedRustDiagnostic { location, diagnostic, suggested_fixes } = map_result;

                let diagnostic = DiagnosticWithFixes { diagnostic, suggested_fixes };
                task_send.send(CheckTask::AddDiagnostic(location.uri, diagnostic)).unwrap();
            }

            CheckEvent::Msg(Message::BuildScriptExecuted(_msg)) => {}
            CheckEvent::Msg(Message::Unknown) => {}
        }
    }
}

#[derive(Debug)]
pub struct DiagnosticWithFixes {
    diagnostic: Diagnostic,
    suggested_fixes: Vec<SuggestedFix>,
}

/// WatchThread exists to wrap around the communication needed to be able to
/// run `cargo check` without blocking. Currently the Rust standard library
/// doesn't provide a way to read sub-process output without blocking, so we
/// have to wrap sub-processes output handling in a thread and pass messages
/// back over a channel.
/// The correct way to dispose of the thread is to drop it, on which the
/// sub-process will be killed, and the thread will be joined.
struct WatchThread {
    handle: Option<JoinHandle<()>>,
    message_recv: Receiver<CheckEvent>,
}

enum CheckEvent {
    Begin,
    Msg(cargo_metadata::Message),
    End,
}

impl WatchThread {
    fn dummy() -> WatchThread {
        WatchThread { handle: None, message_recv: never() }
    }

    fn new(options: &CheckOptions, workspace_root: &PathBuf) -> WatchThread {
        let mut args: Vec<String> = vec![
            options.command.clone(),
            "--message-format=json".to_string(),
            "--manifest-path".to_string(),
            format!("{}/Cargo.toml", workspace_root.to_string_lossy()),
        ];
        if options.all_targets {
            args.push("--all-targets".to_string());
        }
        args.extend(options.args.iter().cloned());

        let (message_send, message_recv) = unbounded();
        let enabled = options.enable;
        let handle = std::thread::spawn(move || {
            if !enabled {
                return;
            }

            let mut command = Command::new("cargo")
                .args(&args)
                .stdout(Stdio::piped())
                .stderr(Stdio::null())
                .spawn()
                .expect("couldn't launch cargo");

            // If we trigger an error here, we will do so in the loop instead,
            // which will break out of the loop, and continue the shutdown
            let _ = message_send.send(CheckEvent::Begin);

            for message in
                cargo_metadata::parse_messages(BufReader::new(command.stdout.take().unwrap()))
            {
                let message = match message {
                    Ok(message) => message,
                    Err(err) => {
                        log::error!("Invalid json from cargo check, ignoring: {}", err);
                        continue;
                    }
                };

                match message_send.send(CheckEvent::Msg(message)) {
                    Ok(()) => {}
                    Err(_err) => {
                        // The send channel was closed, so we want to shutdown
                        break;
                    }
                }
            }

            // We can ignore any error here, as we are already in the progress
            // of shutting down.
            let _ = message_send.send(CheckEvent::End);

            // It is okay to ignore the result, as it only errors if the process is already dead
            let _ = command.kill();

            // Again, we don't care about the exit status so just ignore the result
            let _ = command.wait();
        });
        WatchThread { handle: Some(handle), message_recv }
    }
}

impl std::ops::Drop for WatchThread {
    fn drop(&mut self) {
        if let Some(handle) = self.handle.take() {
            // Replace our reciever with dummy one, so we can drop and close the
            // one actually communicating with the thread
            let recv = std::mem::replace(&mut self.message_recv, never());

            // Dropping the original reciever initiates thread sub-process shutdown
            drop(recv);

            // Join the thread, it should finish shortly. We don't really care
            // whether it panicked, so it is safe to ignore the result
            let _ = handle.join();
        }
    }
}

fn are_diagnostics_equal(left: &Diagnostic, right: &Diagnostic) -> bool {
    left.source == right.source
        && left.severity == right.severity
        && left.range == right.range
        && left.message == right.message
}
<commit_msg>Modify ordering of drops in check watcher to only ever have one cargo<commit_after>//! cargo_check provides the functionality needed to run `cargo check` or
//! another compatible command (f.x. clippy) in a background thread and provide
//! LSP diagnostics based on the output of the command.
use cargo_metadata::Message;
use crossbeam_channel::{never, select, unbounded, Receiver, RecvError, Sender};
use lsp_types::{
    Diagnostic, Url, WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressEnd,
    WorkDoneProgressReport,
};
use std::{
    collections::HashMap,
    io::BufReader,
    path::PathBuf,
    process::{Command, Stdio},
    sync::Arc,
    thread::JoinHandle,
    time::Instant,
};

mod conv;

use crate::conv::{map_rust_diagnostic_to_lsp, MappedRustDiagnostic, SuggestedFix};

pub use crate::conv::url_from_path_with_drive_lowercasing;

#[derive(Clone, Debug)]
pub struct CheckOptions {
    pub enable: bool,
    pub args: Vec<String>,
    pub command: String,
    pub all_targets: bool,
}

/// CheckWatcher wraps the shared state and communication machinery used for
/// running `cargo check` (or other compatible command) and providing
/// diagnostics based on the output.
/// The spawned thread is shut down when this struct is dropped.
#[derive(Debug)]
pub struct CheckWatcher {
    pub task_recv: Receiver<CheckTask>,
    pub state: Arc<CheckState>,
    cmd_send: Option<Sender<CheckCommand>>,
    handle: Option<JoinHandle<()>>,
}

impl CheckWatcher {
    pub fn new(options: &CheckOptions, workspace_root: PathBuf) -> CheckWatcher {
        let options = options.clone();
        let state = Arc::new(CheckState::new());

        let (task_send, task_recv) = unbounded::<CheckTask>();
        let (cmd_send, cmd_recv) = unbounded::<CheckCommand>();
        let handle = std::thread::spawn(move || {
            let mut check = CheckWatcherThread::new(options, workspace_root);
            check.run(&task_send, &cmd_recv);
        });
        CheckWatcher { task_recv, cmd_send: Some(cmd_send), handle: Some(handle), state }
    }

    /// Returns a CheckWatcher that doesn't actually do anything
    pub fn dummy() -> CheckWatcher {
        let state = Arc::new(CheckState::new());
        CheckWatcher { task_recv: never(), cmd_send: None, handle: None, state }
    }

    /// Schedule a re-start of the cargo check worker.
    pub fn update(&self) {
        if let Some(cmd_send) = &self.cmd_send {
            cmd_send.send(CheckCommand::Update).unwrap();
        }
    }
}

impl std::ops::Drop for CheckWatcher {
    fn drop(&mut self) {
        if let Some(handle) = self.handle.take() {
            // Take the sender out of the option
            let recv = self.cmd_send.take();

            // Dropping the sender finishes the thread loop
            drop(recv);

            // Join the thread, it should finish shortly. We don't really care
            // whether it panicked, so it is safe to ignore the result
            let _ = handle.join();
        }
    }
}

#[derive(Clone, Debug)]
pub struct CheckState {
    diagnostic_collection: HashMap<Url, Vec<Diagnostic>>,
    suggested_fix_collection: HashMap<Url, Vec<SuggestedFix>>,
}

impl CheckState {
    fn new() -> CheckState {
        CheckState {
            diagnostic_collection: HashMap::new(),
            suggested_fix_collection: HashMap::new(),
        }
    }

    /// Clear the cached diagnostics, and schedule updating diagnostics by the
    /// server, to clear stale results.
    pub fn clear(&mut self) -> Vec<Url> {
        let cleared_files: Vec<Url> = self.diagnostic_collection.keys().cloned().collect();
        self.diagnostic_collection.clear();
        self.suggested_fix_collection.clear();
        cleared_files
    }

    pub fn diagnostics_for(&self, uri: &Url) -> Option<&[Diagnostic]> {
        self.diagnostic_collection.get(uri).map(|d| d.as_slice())
    }

    pub fn fixes_for(&self, uri: &Url) -> Option<&[SuggestedFix]> {
        self.suggested_fix_collection.get(uri).map(|d| d.as_slice())
    }

    pub fn add_diagnostic_with_fixes(&mut self, file_uri: Url, diagnostic: DiagnosticWithFixes) {
        for fix in diagnostic.suggested_fixes {
            self.add_suggested_fix_for_diagnostic(fix, &diagnostic.diagnostic);
        }
        self.add_diagnostic(file_uri, diagnostic.diagnostic);
    }

    fn add_diagnostic(&mut self, file_uri: Url, diagnostic: Diagnostic) {
        let diagnostics = self.diagnostic_collection.entry(file_uri).or_default();

        // If we're building multiple targets it's possible we've already seen this diagnostic
        let is_duplicate = diagnostics.iter().any(|d| are_diagnostics_equal(d, &diagnostic));
        if is_duplicate {
            return;
        }

        diagnostics.push(diagnostic);
    }

    fn add_suggested_fix_for_diagnostic(
        &mut self,
        mut suggested_fix: SuggestedFix,
        diagnostic: &Diagnostic,
    ) {
        let file_uri = suggested_fix.location.uri.clone();
        let file_suggestions = self.suggested_fix_collection.entry(file_uri).or_default();

        let existing_suggestion: Option<&mut SuggestedFix> =
            file_suggestions.iter_mut().find(|s| s == &&suggested_fix);
        if let Some(existing_suggestion) = existing_suggestion {
            // The existing suggestion also applies to this new diagnostic
            existing_suggestion.diagnostics.push(diagnostic.clone());
        } else {
            // We haven't seen this suggestion before
            suggested_fix.diagnostics.push(diagnostic.clone());
            file_suggestions.push(suggested_fix);
        }
    }
}

#[derive(Debug)]
pub enum CheckTask {
    /// Request a clearing of all cached diagnostics from the check watcher
    ClearDiagnostics,

    /// Request adding a diagnostic with fixes included to a file
    AddDiagnostic(Url, DiagnosticWithFixes),

    /// Request check progress notification to client
    Status(WorkDoneProgress),
}

pub enum CheckCommand {
    /// Request re-start of check thread
    Update,
}

struct CheckWatcherThread {
    options: CheckOptions,
    workspace_root: PathBuf,
    watcher: WatchThread,
    last_update_req: Option<Instant>,
}

impl CheckWatcherThread {
    fn new(options: CheckOptions, workspace_root: PathBuf) -> CheckWatcherThread {
        CheckWatcherThread {
            options,
            workspace_root,
            watcher: WatchThread::dummy(),
            last_update_req: None,
        }
    }

    fn run(&mut self, task_send: &Sender<CheckTask>, cmd_recv: &Receiver<CheckCommand>) {
        loop {
            select! {
                recv(&cmd_recv) -> cmd => match cmd {
                    Ok(cmd) => self.handle_command(cmd),
                    Err(RecvError) => {
                        // Command channel has closed, so shut down
                        break;
                    },
                },
                recv(self.watcher.message_recv) -> msg => match msg {
                    Ok(msg) => self.handle_message(msg, task_send),
                    Err(RecvError) => {
                        // Watcher finished, replace it with a never channel to
                        // avoid busy-waiting.
                        std::mem::replace(&mut self.watcher.message_recv, never());
                    },
                }
            };

            if self.should_recheck() {
                self.last_update_req.take();
                task_send.send(CheckTask::ClearDiagnostics).unwrap();

                // Replace with a dummy watcher first so we drop the original and wait for completion
                std::mem::replace(&mut self.watcher, WatchThread::dummy());

                // Then create the actual new watcher
                self.watcher = WatchThread::new(&self.options, &self.workspace_root);
            }
        }
    }

    fn should_recheck(&mut self) -> bool {
        if let Some(_last_update_req) = &self.last_update_req {
            // We currently only request an update on save, as we need up to
            // date source on disk for cargo check to do it's magic, so we
            // don't really need to debounce the requests at this point.
            return true;
        }
        false
    }

    fn handle_command(&mut self, cmd: CheckCommand) {
        match cmd {
            CheckCommand::Update => self.last_update_req = Some(Instant::now()),
        }
    }

    fn handle_message(&self, msg: CheckEvent, task_send: &Sender<CheckTask>) {
        match msg {
            CheckEvent::Begin => {
                task_send
                    .send(CheckTask::Status(WorkDoneProgress::Begin(WorkDoneProgressBegin {
                        title: "Running 'cargo check'".to_string(),
                        cancellable: Some(false),
                        message: None,
                        percentage: None,
                    })))
                    .unwrap();
            }

            CheckEvent::End => {
                task_send
                    .send(CheckTask::Status(WorkDoneProgress::End(WorkDoneProgressEnd {
                        message: None,
                    })))
                    .unwrap();
            }

            CheckEvent::Msg(Message::CompilerArtifact(msg)) => {
                task_send
                    .send(CheckTask::Status(WorkDoneProgress::Report(WorkDoneProgressReport {
                        cancellable: Some(false),
                        message: Some(msg.target.name),
                        percentage: None,
                    })))
                    .unwrap();
            }

            CheckEvent::Msg(Message::CompilerMessage(msg)) => {
                let map_result =
                    match map_rust_diagnostic_to_lsp(&msg.message, &self.workspace_root) {
                        Some(map_result) => map_result,
                        None => return,
                    };

                let MappedRustDiagnostic { location, diagnostic, suggested_fixes } = map_result;

                let diagnostic = DiagnosticWithFixes { diagnostic, suggested_fixes };
                task_send.send(CheckTask::AddDiagnostic(location.uri, diagnostic)).unwrap();
            }

            CheckEvent::Msg(Message::BuildScriptExecuted(_msg)) => {}
            CheckEvent::Msg(Message::Unknown) => {}
        }
    }
}

#[derive(Debug)]
pub struct DiagnosticWithFixes {
    diagnostic: Diagnostic,
    suggested_fixes: Vec<SuggestedFix>,
}

/// WatchThread exists to wrap around the communication needed to be able to
/// run `cargo check` without blocking. Currently the Rust standard library
/// doesn't provide a way to read sub-process output without blocking, so we
/// have to wrap sub-processes output handling in a thread and pass messages
/// back over a channel.
/// The correct way to dispose of the thread is to drop it, on which the
/// sub-process will be killed, and the thread will be joined.
struct WatchThread {
    handle: Option<JoinHandle<()>>,
    message_recv: Receiver<CheckEvent>,
}

enum CheckEvent {
    Begin,
    Msg(cargo_metadata::Message),
    End,
}

impl WatchThread {
    fn dummy() -> WatchThread {
        WatchThread { handle: None, message_recv: never() }
    }

    fn new(options: &CheckOptions, workspace_root: &PathBuf) -> WatchThread {
        let mut args: Vec<String> = vec![
            options.command.clone(),
            "--message-format=json".to_string(),
            "--manifest-path".to_string(),
            format!("{}/Cargo.toml", workspace_root.to_string_lossy()),
        ];
        if options.all_targets {
            args.push("--all-targets".to_string());
        }
        args.extend(options.args.iter().cloned());

        let (message_send, message_recv) = unbounded();
        let enabled = options.enable;
        let handle = std::thread::spawn(move || {
            if !enabled {
                return;
            }

            let mut command = Command::new("cargo")
                .args(&args)
                .stdout(Stdio::piped())
                .stderr(Stdio::null())
                .spawn()
                .expect("couldn't launch cargo");

            // If we trigger an error here, we will do so in the loop instead,
            // which will break out of the loop, and continue the shutdown
            let _ = message_send.send(CheckEvent::Begin);

            for message in
                cargo_metadata::parse_messages(BufReader::new(command.stdout.take().unwrap()))
            {
                let message = match message {
                    Ok(message) => message,
                    Err(err) => {
                        log::error!("Invalid json from cargo check, ignoring: {}", err);
                        continue;
                    }
                };

                match message_send.send(CheckEvent::Msg(message)) {
                    Ok(()) => {}
                    Err(_err) => {
                        // The send channel was closed, so we want to shutdown
                        break;
                    }
                }
            }

            // We can ignore any error here, as we are already in the progress
            // of shutting down.
            let _ = message_send.send(CheckEvent::End);

            // It is okay to ignore the result, as it only errors if the process is already dead
            let _ = command.kill();

            // Again, we don't care about the exit status so just ignore the result
            let _ = command.wait();
        });
        WatchThread { handle: Some(handle), message_recv }
    }
}

impl std::ops::Drop for WatchThread {
    fn drop(&mut self) {
        if let Some(handle) = self.handle.take() {
            // Replace our reciever with dummy one, so we can drop and close the
            // one actually communicating with the thread
            let recv = std::mem::replace(&mut self.message_recv, never());

            // Dropping the original reciever initiates thread sub-process shutdown
            drop(recv);

            // Join the thread, it should finish shortly. We don't really care
            // whether it panicked, so it is safe to ignore the result
            let _ = handle.join();
        }
    }
}

fn are_diagnostics_equal(left: &Diagnostic, right: &Diagnostic) -> bool {
    left.source == right.source
        && left.severity == right.severity
        && left.range == right.range
        && left.message == right.message
}
<|endoftext|>


<commit_before>//!
//! Metapackage for renderer
//!

use std::char;

// `Console` is needed as Console is a trait that console::Root extends
use core::tcod::{Console, console};

use core::GlobalLog;

use core::world::World;

use core::world::dungeon::Dungeon;
// Used to expliclty reference constants
use core::world::dungeon::map::tile;
use core::world::dungeon::map::{Tile, TileType, ScentType};

use core::object::{Pos, Entity, RGB};

// Use camera privately
mod camera;
use self::camera::Camera;

///
/// Helper for rendering things to the screen
///
/// Tracks the player and automatically scrolls the screen around to match where they go.
/// This will never try to draw things outside of the given dimensions due to the way it handles
/// determining whether something should be drawn or not.
///
pub struct Renderer {
  // Camera object
  camera: Camera,
  screen: Pos,
  console_height: isize,
  panel_width: isize,
  pub sc_debug: bool,
  pub fov: bool,
  pub so_debug: bool
}

impl Renderer {

  ///
  /// Render for each monster as a visible colored entity
  ///
  pub fn debug_render_scent_map(&mut self, con: &mut console::Root, dungeon: &Dungeon) {

    for x in 0..dungeon.width {
      for y in 0..dungeon.height {
        // Pretty much just random, Player is red, bugs are green, cats are yellow and dogs are blue
        let mut color : (u8, u8, u8) = (
          dungeon.grid[x][y].scents[0].val + 50 + dungeon.grid[x][y].scents[3].val,
          dungeon.grid[x][y].scents[1].val + 25 + dungeon.grid[x][y].scents[3].val,
          dungeon.grid[x][y].scents[2].val + 50
        );
        // Iterate over scents, context of what scent it is isn't necessary
        for s in 0..ScentType::Num as usize {
          if dungeon.grid[x][y].scents[s].val > 0 {
            self.draw_entity(con, Pos::new(x as isize, y as isize), &Tile::new(
              "Debug Scent",
              ' ',
              (255, 255, 255),
              color,
              TileType::Debug
            ));
            break;
          }
        }
      }
    }

  }

  ///
  /// Render sound as a transparent blue entity
  ///
  pub fn debug_render_sound_map(&mut self, con: &mut console::Root, dungeon: &Dungeon) {

    for x in 0..dungeon.width {
      for y in 0..dungeon.height {
        // Color is weighted towards blue
        let mut color : (u8, u8, u8) = (
          dungeon.grid[x][y].get_bg().0,
          dungeon.grid[x][y].get_bg().1,
          dungeon.grid[x][y].sound
        );
        if dungeon.grid[x][y].sound > 0 {
          self.draw_entity(con, Pos::new(x as isize, y as isize), &Tile::new(
            "Debug Sound",
            ' ',
            (255, 255, 255),
            color,
            TileType::Debug
          ));
        }
      }
    }

  }

  ///
  /// Draw all.
  ///
  pub fn draw_all(&mut self, con: &mut console::Root, world: &mut World) {

    //
    // Console prep
    //

    // Clear console
    con.clear();

    // Move camera to player's position
    self.camera.move_to(world.player.fighter.pos);

    //
    // Draw tiles
    //

    // Draw seen tiles
    for x in 0..world.cur_dungeon.width {
      for y in 0..world.cur_dungeon.height {
        // If fov is on...
        if self.fov {
          // And it's in the FoV
          if world.tcod_map.is_in_fov(x as i32, y as i32) && self.fov {
            match world.cur_dungeon.grid[x][y].tiletype {
              _ => {
                // Draw a tile slightly more vibrant than it actually is
                self.draw_entity(con, Pos::new(x as isize, y as isize), &world.cur_dungeon.grid[x][y].yellowish());
              }
            }

            // Mark tile as seen if it's in the FoV
            world.cur_dungeon.grid[x][y].seen = true;

          }

          // And the tile has been seen...
          else if world.cur_dungeon.grid[x][y].seen {
            // Draw certain tiles depending on their types
            match world.cur_dungeon.grid[x][y].tiletype {
              _ => {
                // Draw a tile slightly darker than it actually is
                self.draw_entity(con, Pos::new(x as isize, y as isize), &world.cur_dungeon.grid[x][y].darken());
              }
            }
          }

        }

        // [Debug] Otherwise just draw all tiles normally
        else {
         match world.cur_dungeon.grid[x][y].tiletype {
            _ => {
              self.draw_entity(con, Pos::new(x as isize, y as isize), &world.cur_dungeon.grid[x][y]);
            }
          }
        }

      }
    }

    //
    // Debug options
    //

    // Debug scent
    if self.sc_debug {
      self.debug_render_scent_map(con, &world.cur_dungeon);
    }

    // Debug sound
    if self.so_debug {
      self.debug_render_sound_map(con, &world.cur_dungeon);
    }

    //
    // Draw creatures
    //

    for c in &world.creatures {
      // If fov is on...
      if self.fov {
        // And its in the fov...
        if world.tcod_map.is_in_fov(c.fighter.pos.x as i32, c.fighter.pos.y as i32) && self.fov {
          self.draw_creature(con, c.fighter.pos, &c.fighter, world);
        }
      } else {
        self.draw_creature(con, c.fighter.pos, &c.fighter, world);
      }
    }

    // Draw player. Player is always in the camera since
    // we move the camera over it.
    self.draw_creature(con, world.player.fighter.pos, &world.player.fighter, world);

    //
    // Draw log
    //

    self.draw_log(con);

    //
    // Draw UI
    //

    for x in 0..self.screen.x {
      con.put_char_ex(
        x as i32,
        (self.screen.y - self.console_height - 1) as i32,
        '-',
        RGB(255, 255, 255).to_tcod(),
        RGB(0, 0, 0).to_tcod()
      );
    }

    for y in 0..(self.screen.y - self.console_height - 1) {
      con.put_char_ex(
        (self.screen.x - self.panel_width - 1) as i32,
        y as i32,
        '|',
        RGB(255, 255, 255).to_tcod(),
        RGB(0, 0, 0).to_tcod()
      );
    }

    con.put_char_ex(
      (self.screen.x - self.panel_width - 1) as i32,
      (self.screen.y - self.console_height - 1) as i32,
      char::from_u32(193).unwrap(),
      RGB(255, 255, 255).to_tcod(),
      RGB(0, 0, 0).to_tcod()
    );

    //
    // Flush changes to root
    //

    con.flush();

  }

  ///
  /// Draw creatures with "transparent" backgrounds
  ///
  pub fn draw_creature(&self, con: &mut console::Root, pos: Pos, ce: &Entity, world: &World) {
    // Check if it's in the camera first
    if !self.camera.is_in_camera(pos) { return }

    // New pos with respect to camera
    let npos = pos + self.camera.pos;

    con.put_char_ex(
      npos.x as i32,
      npos.y as i32,
      ce.get_glyph(),
      ce.get_fg().to_tcod(),
      // Backgrounds are just inherited from the world.
      if self.fov {
        (world.get_bg_color_at(pos.x as usize, pos.y as usize) + tile::YELLOW_FAC).to_tcod()
      } else {
        (world.get_bg_color_at(pos.x as usize, pos.y as usize)).to_tcod()
      }
    );

  }

  ///
  /// Put an `Entity` on the console
  ///
  /// * `con` - Tcod `Console`
  /// * `entity` - `Entity`
  ///
  pub fn draw_entity(&self, con: &mut console::Root, pos: Pos, ce: &Entity) {

    // Check if it's in the camera first
    if !self.camera.is_in_camera(pos) { return }

    // New pos with respect to camera
    let pos = pos + self.camera.pos;

    if ce.get_glyph() == ' ' {
      con.set_char_background(
        pos.x as i32,
        pos.y as i32,
        ce.get_bg().to_tcod(),
        console::BackgroundFlag::Set
      );
    } else {
      con.put_char_ex(
        pos.x as i32,
        pos.y as i32,
        ce.get_glyph(),
        ce.get_fg().to_tcod(),
        ce.get_bg().to_tcod()
      );
    }

  }

  ///
  /// Draw the log
  ///
  /// Currently still testing
  ///
  pub fn draw_log(&self, con: &mut console::Root) {

    // Mutable reference to the mutex
    let mut log = GlobalLog.lock().unwrap();

    // Iterage over the latest range
    for i in log.get_latest_range(5) {
      // Draw to screen
      let y = self.screen.y - ((log.data.len() as isize) - i as isize);
      let color = log.data[i].1;
      let s = log.data[i].0;
      con.set_default_foreground(color.to_tcod());
      con.print_ex(0, y as i32, console::BackgroundFlag::Set, console::TextAlignment::Left, s);
    }

    // Test mutability
    log.push(("Update", RGB(255, 255, 255)));

  }

  ///
  /// Return a new `Renderer`
  ///
  /// * `map` - `Pos` that holds the map dimensions
  /// * `screen` - `Pos` that holds the screen dimensions
  ///
  #[inline]
  pub fn new(map: (isize, isize), screen: (isize, isize), console_height: isize, panel_width: isize) -> Renderer {
    Renderer {
      // Camera takes a modified screen value that compensates for the console_height
      // This way the render still knows that that area is "reserved" for the console
      camera: Camera::new(map, (screen.0 - panel_width - 1, screen.1 - console_height - 1)),
      console_height: console_height, panel_width: panel_width,
      screen: Pos::from_tup(screen),
      sc_debug: false, fov: true, so_debug: false
    }
  }

}
<commit_msg>print_ex -> print<commit_after>//!
//! Metapackage for renderer
//!

use std::char;

// `Console` is needed as Console is a trait that console::Root extends
use core::tcod::{Console, console};

use core::GlobalLog;

use core::world::World;

use core::world::dungeon::Dungeon;
// Used to expliclty reference constants
use core::world::dungeon::map::tile;
use core::world::dungeon::map::{Tile, TileType, ScentType};

use core::object::{Pos, Entity, RGB};

// Use camera privately
mod camera;
use self::camera::Camera;

///
/// Helper for rendering things to the screen
///
/// Tracks the player and automatically scrolls the screen around to match where they go.
/// This will never try to draw things outside of the given dimensions due to the way it handles
/// determining whether something should be drawn or not.
///
pub struct Renderer {
  // Camera object
  camera: Camera,
  screen: Pos,
  console_height: isize,
  panel_width: isize,
  pub sc_debug: bool,
  pub fov: bool,
  pub so_debug: bool
}

impl Renderer {

  ///
  /// Render for each monster as a visible colored entity
  ///
  pub fn debug_render_scent_map(&mut self, con: &mut console::Root, dungeon: &Dungeon) {

    for x in 0..dungeon.width {
      for y in 0..dungeon.height {
        // Pretty much just random, Player is red, bugs are green, cats are yellow and dogs are blue
        let mut color : (u8, u8, u8) = (
          dungeon.grid[x][y].scents[0].val + 50 + dungeon.grid[x][y].scents[3].val,
          dungeon.grid[x][y].scents[1].val + 25 + dungeon.grid[x][y].scents[3].val,
          dungeon.grid[x][y].scents[2].val + 50
        );
        // Iterate over scents, context of what scent it is isn't necessary
        for s in 0..ScentType::Num as usize {
          if dungeon.grid[x][y].scents[s].val > 0 {
            self.draw_entity(con, Pos::new(x as isize, y as isize), &Tile::new(
              "Debug Scent",
              ' ',
              (255, 255, 255),
              color,
              TileType::Debug
            ));
            break;
          }
        }
      }
    }

  }

  ///
  /// Render sound as a transparent blue entity
  ///
  pub fn debug_render_sound_map(&mut self, con: &mut console::Root, dungeon: &Dungeon) {

    for x in 0..dungeon.width {
      for y in 0..dungeon.height {
        // Color is weighted towards blue
        let mut color : (u8, u8, u8) = (
          dungeon.grid[x][y].get_bg().0,
          dungeon.grid[x][y].get_bg().1,
          dungeon.grid[x][y].sound
        );
        if dungeon.grid[x][y].sound > 0 {
          self.draw_entity(con, Pos::new(x as isize, y as isize), &Tile::new(
            "Debug Sound",
            ' ',
            (255, 255, 255),
            color,
            TileType::Debug
          ));
        }
      }
    }

  }

  ///
  /// Draw all.
  ///
  pub fn draw_all(&mut self, con: &mut console::Root, world: &mut World) {

    //
    // Console prep
    //

    // Clear console
    con.clear();

    // Move camera to player's position
    self.camera.move_to(world.player.fighter.pos);

    //
    // Draw tiles
    //

    // Draw seen tiles
    for x in 0..world.cur_dungeon.width {
      for y in 0..world.cur_dungeon.height {
        // If fov is on...
        if self.fov {
          // And it's in the FoV
          if world.tcod_map.is_in_fov(x as i32, y as i32) && self.fov {
            match world.cur_dungeon.grid[x][y].tiletype {
              _ => {
                // Draw a tile slightly more vibrant than it actually is
                self.draw_entity(con, Pos::new(x as isize, y as isize), &world.cur_dungeon.grid[x][y].yellowish());
              }
            }

            // Mark tile as seen if it's in the FoV
            world.cur_dungeon.grid[x][y].seen = true;

          }

          // And the tile has been seen...
          else if world.cur_dungeon.grid[x][y].seen {
            // Draw certain tiles depending on their types
            match world.cur_dungeon.grid[x][y].tiletype {
              _ => {
                // Draw a tile slightly darker than it actually is
                self.draw_entity(con, Pos::new(x as isize, y as isize), &world.cur_dungeon.grid[x][y].darken());
              }
            }
          }

        }

        // [Debug] Otherwise just draw all tiles normally
        else {
         match world.cur_dungeon.grid[x][y].tiletype {
            _ => {
              self.draw_entity(con, Pos::new(x as isize, y as isize), &world.cur_dungeon.grid[x][y]);
            }
          }
        }

      }
    }

    //
    // Debug options
    //

    // Debug scent
    if self.sc_debug {
      self.debug_render_scent_map(con, &world.cur_dungeon);
    }

    // Debug sound
    if self.so_debug {
      self.debug_render_sound_map(con, &world.cur_dungeon);
    }

    //
    // Draw creatures
    //

    for c in &world.creatures {
      // If fov is on...
      if self.fov {
        // And its in the fov...
        if world.tcod_map.is_in_fov(c.fighter.pos.x as i32, c.fighter.pos.y as i32) && self.fov {
          self.draw_creature(con, c.fighter.pos, &c.fighter, world);
        }
      } else {
        self.draw_creature(con, c.fighter.pos, &c.fighter, world);
      }
    }

    // Draw player. Player is always in the camera since
    // we move the camera over it.
    self.draw_creature(con, world.player.fighter.pos, &world.player.fighter, world);

    //
    // Draw log
    //

    self.draw_log(con);

    //
    // Draw UI
    //

    for x in 0..self.screen.x {
      con.put_char_ex(
        x as i32,
        (self.screen.y - self.console_height - 1) as i32,
        '-',
        RGB(255, 255, 255).to_tcod(),
        RGB(0, 0, 0).to_tcod()
      );
    }

    for y in 0..(self.screen.y - self.console_height - 1) {
      con.put_char_ex(
        (self.screen.x - self.panel_width - 1) as i32,
        y as i32,
        '|',
        RGB(255, 255, 255).to_tcod(),
        RGB(0, 0, 0).to_tcod()
      );
    }

    con.put_char_ex(
      (self.screen.x - self.panel_width - 1) as i32,
      (self.screen.y - self.console_height - 1) as i32,
      char::from_u32(193).unwrap(),
      RGB(255, 255, 255).to_tcod(),
      RGB(0, 0, 0).to_tcod()
    );

    con.print(
      (self.screen.x - self.panel_width + 1) as i32,
      0,
      "Edgequest"
    );

    con.print(
      (self.screen.x - self.panel_width + 1) as i32,
      1,
      "This is where we live"
    );

    //
    // Flush changes to root
    //

    con.flush();

  }

  ///
  /// Draw creatures with "transparent" backgrounds
  ///
  pub fn draw_creature(&self, con: &mut console::Root, pos: Pos, ce: &Entity, world: &World) {
    // Check if it's in the camera first
    if !self.camera.is_in_camera(pos) { return }

    // New pos with respect to camera
    let npos = pos + self.camera.pos;

    con.put_char_ex(
      npos.x as i32,
      npos.y as i32,
      ce.get_glyph(),
      ce.get_fg().to_tcod(),
      // Backgrounds are just inherited from the world.
      if self.fov {
        (world.get_bg_color_at(pos.x as usize, pos.y as usize) + tile::YELLOW_FAC).to_tcod()
      } else {
        (world.get_bg_color_at(pos.x as usize, pos.y as usize)).to_tcod()
      }
    );

  }

  ///
  /// Put an `Entity` on the console
  ///
  /// * `con` - Tcod `Console`
  /// * `entity` - `Entity`
  ///
  pub fn draw_entity(&self, con: &mut console::Root, pos: Pos, ce: &Entity) {

    // Check if it's in the camera first
    if !self.camera.is_in_camera(pos) { return }

    // New pos with respect to camera
    let pos = pos + self.camera.pos;

    if ce.get_glyph() == ' ' {
      con.set_char_background(
        pos.x as i32,
        pos.y as i32,
        ce.get_bg().to_tcod(),
        console::BackgroundFlag::Set
      );
    } else {
      con.put_char_ex(
        pos.x as i32,
        pos.y as i32,
        ce.get_glyph(),
        ce.get_fg().to_tcod(),
        ce.get_bg().to_tcod()
      );
    }

  }

  ///
  /// Draw the log
  ///
  /// Currently still testing
  ///
  pub fn draw_log(&self, con: &mut console::Root) {

    // Mutable reference to the mutex
    let mut log = GlobalLog.lock().unwrap();

    // Iterage over the latest range
    for i in log.get_latest_range(5) {
      // Draw to screen
      let y = self.screen.y - ((log.data.len() as isize) - i as isize);
      let color = log.data[i].1;
      let s = log.data[i].0;
      con.set_default_foreground(color.to_tcod());
      con.print(0, y as i32, s);
    }

    // Test mutability
    log.push(("Update", RGB(255, 255, 255)));

  }

  ///
  /// Return a new `Renderer`
  ///
  /// * `map` - `Pos` that holds the map dimensions
  /// * `screen` - `Pos` that holds the screen dimensions
  ///
  #[inline]
  pub fn new(map: (isize, isize), screen: (isize, isize), console_height: isize, panel_width: isize) -> Renderer {
    Renderer {
      // Camera takes a modified screen value that compensates for the console_height
      // This way the render still knows that that area is "reserved" for the console
      camera: Camera::new(map, (screen.0 - panel_width - 1, screen.1 - console_height - 1)),
      console_height: console_height, panel_width: panel_width,
      screen: Pos::from_tup(screen),
      sc_debug: false, fov: true, so_debug: false
    }
  }

}
<|endoftext|>


<commit_before>#![cfg(test)]

use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};

use Configuration;
use join;
use super::ThreadPool;
use unwind;

#[test]
#[should_panic(expected = "Hello, world!")]
fn panic_propagate() {
    let thread_pool = ThreadPool::new(Configuration::new()).unwrap();
    thread_pool.install(|| {
        panic!("Hello, world!");
    });
}

#[test]
fn workers_stop() {
    let registry;

    { // once we exit this block, thread-pool will be dropped
        let thread_pool = ThreadPool::new(Configuration::new().set_num_threads(22)).unwrap();
        registry = thread_pool.install(|| {
            // do some work on these threads
            join_a_lot(22);

            thread_pool.registry.clone()
        });
        assert_eq!(registry.num_threads(), 22);
    }

    // once thread-pool is dropped, registry should terminate, which
    // should lead to worker threads stopping
    registry.wait_until_stopped();
}

fn join_a_lot(n: usize) {
    if n > 0 {
        join(|| join_a_lot(n-1), || join_a_lot(n-1));
    }
}

#[test]
fn sleeper_stop() {
    use std::{thread, time};

    let registry;

    { // once we exit this block, thread-pool will be dropped
        let thread_pool = ThreadPool::new(Configuration::new().set_num_threads(22)).unwrap();
        registry = thread_pool.registry.clone();

        // Give time for at least some of the thread pool to fall asleep.
        thread::sleep(time::Duration::from_secs(1));
    }

    // once thread-pool is dropped, registry should terminate, which
    // should lead to worker threads stopping
    registry.wait_until_stopped();
}

/// Create a start/exit handler that increments an atomic counter.
fn count_handler() -> (Arc<AtomicUsize>, Box<::StartHandler>) {
    let count = Arc::new(AtomicUsize::new(0));
    (count.clone(), Box::new(move |_| { count.fetch_add(1, Ordering::SeqCst); }))
}

/// Immediately unwrap a counter, asserting that it's no longer shared.
fn unwrap_counter(counter: Arc<AtomicUsize>) -> usize {
    Arc::try_unwrap(counter)
        .expect("Counter is still shared!")
        .into_inner()
}

/// Wait until a counter is no longer shared, then return its value.
fn wait_for_counter(mut counter: Arc<AtomicUsize>) -> usize {
    use std::{thread, time};

    for _ in 0..60 {
        counter = match Arc::try_unwrap(counter) {
            Ok(counter) => return counter.into_inner(),
            Err(counter) => {
                thread::sleep(time::Duration::from_secs(1));
                counter
            },
        };
    }

    // That's too long -- do it now!
    unwrap_counter(counter)
}

#[test]
fn failed_thread_stack() {
    let (start_count, start_handler) = count_handler();
    let (exit_count, exit_handler) = count_handler();
    let config = Configuration::new()
        .set_stack_size(::std::usize::MAX)
        .set_start_handler(move |i| start_handler(i))
        .set_exit_handler(move |i| exit_handler(i));

    let pool = ThreadPool::new(config);
    assert!(pool.is_err(), "thread stack should have failed!");

    // With an impossible stack, we don't expect to have seen any threads,
    // nor should there be any remaining references to the counters.
    assert_eq!(0, unwrap_counter(start_count));
    assert_eq!(0, unwrap_counter(exit_count));
}

#[test]
fn panic_thread_name() {
    let (start_count, start_handler) = count_handler();
    let (exit_count, exit_handler) = count_handler();
    let config = Configuration::new()
        .set_num_threads(10)
        .set_start_handler(move |i| start_handler(i))
        .set_exit_handler(move |i| exit_handler(i))
        .set_thread_name(|i| {
                             if i >= 5 {
                                 panic!();
                             }
                             format!("panic_thread_name#{}", i)
                         });

    let pool = unwind::halt_unwinding(|| ThreadPool::new(config));
    assert!(pool.is_err(), "thread-name panic should propagate!");

    // Assuming they're created in order, threads 0 through 4 should have
    // been started already, and then terminated by the panic.
    assert_eq!(5, wait_for_counter(start_count));
    assert_eq!(5, wait_for_counter(exit_count));
}
<commit_msg>skip failed_thread_stack test<commit_after>#![cfg(test)]

use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};

use Configuration;
use join;
use super::ThreadPool;
use unwind;

#[test]
#[should_panic(expected = "Hello, world!")]
fn panic_propagate() {
    let thread_pool = ThreadPool::new(Configuration::new()).unwrap();
    thread_pool.install(|| {
        panic!("Hello, world!");
    });
}

#[test]
fn workers_stop() {
    let registry;

    { // once we exit this block, thread-pool will be dropped
        let thread_pool = ThreadPool::new(Configuration::new().set_num_threads(22)).unwrap();
        registry = thread_pool.install(|| {
            // do some work on these threads
            join_a_lot(22);

            thread_pool.registry.clone()
        });
        assert_eq!(registry.num_threads(), 22);
    }

    // once thread-pool is dropped, registry should terminate, which
    // should lead to worker threads stopping
    registry.wait_until_stopped();
}

fn join_a_lot(n: usize) {
    if n > 0 {
        join(|| join_a_lot(n-1), || join_a_lot(n-1));
    }
}

#[test]
fn sleeper_stop() {
    use std::{thread, time};

    let registry;

    { // once we exit this block, thread-pool will be dropped
        let thread_pool = ThreadPool::new(Configuration::new().set_num_threads(22)).unwrap();
        registry = thread_pool.registry.clone();

        // Give time for at least some of the thread pool to fall asleep.
        thread::sleep(time::Duration::from_secs(1));
    }

    // once thread-pool is dropped, registry should terminate, which
    // should lead to worker threads stopping
    registry.wait_until_stopped();
}

/// Create a start/exit handler that increments an atomic counter.
fn count_handler() -> (Arc<AtomicUsize>, Box<::StartHandler>) {
    let count = Arc::new(AtomicUsize::new(0));
    (count.clone(), Box::new(move |_| { count.fetch_add(1, Ordering::SeqCst); }))
}

/// Immediately unwrap a counter, asserting that it's no longer shared.
fn unwrap_counter(counter: Arc<AtomicUsize>) -> usize {
    Arc::try_unwrap(counter)
        .expect("Counter is still shared!")
        .into_inner()
}

/// Wait until a counter is no longer shared, then return its value.
fn wait_for_counter(mut counter: Arc<AtomicUsize>) -> usize {
    use std::{thread, time};

    for _ in 0..60 {
        counter = match Arc::try_unwrap(counter) {
            Ok(counter) => return counter.into_inner(),
            Err(counter) => {
                thread::sleep(time::Duration::from_secs(1));
                counter
            },
        };
    }

    // That's too long -- do it now!
    unwrap_counter(counter)
}

/// For some reason, macs do not error out here.
#[cfg_attr(target_os="macos", ignore)]
#[test]
fn failed_thread_stack() {
    let (start_count, start_handler) = count_handler();
    let (exit_count, exit_handler) = count_handler();
    let config = Configuration::new()
        .set_stack_size(::std::usize::MAX)
        .set_start_handler(move |i| start_handler(i))
        .set_exit_handler(move |i| exit_handler(i));

    let pool = ThreadPool::new(config);
    assert!(pool.is_err(), "thread stack should have failed!");

    // With an impossible stack, we don't expect to have seen any threads,
    // nor should there be any remaining references to the counters.
    assert_eq!(0, unwrap_counter(start_count));
    assert_eq!(0, unwrap_counter(exit_count));
}

#[test]
fn panic_thread_name() {
    let (start_count, start_handler) = count_handler();
    let (exit_count, exit_handler) = count_handler();
    let config = Configuration::new()
        .set_num_threads(10)
        .set_start_handler(move |i| start_handler(i))
        .set_exit_handler(move |i| exit_handler(i))
        .set_thread_name(|i| {
                             if i >= 5 {
                                 panic!();
                             }
                             format!("panic_thread_name#{}", i)
                         });

    let pool = unwind::halt_unwinding(|| ThreadPool::new(config));
    assert!(pool.is_err(), "thread-name panic should propagate!");

    // Assuming they're created in order, threads 0 through 4 should have
    // been started already, and then terminated by the panic.
    assert_eq!(5, wait_for_counter(start_count));
    assert_eq!(5, wait_for_counter(exit_count));
}
<|endoftext|>


<commit_before>//! Test/examples for the brainfuck macro.

#![feature(phase)]

#[phase(plugin)] extern crate brainfuck_macros;

#[cfg(bf_bf_interpreter)]
extern crate bf_bf_interpreter;

use std::{io, rand};
use std::rand::Rng;
use std::io::{BufReader, MemWriter};

/// Takes a compiled brainfuck program, feeds it `input` one byte at a
/// time, and compares the output against `expected_output`.
fn run(bf: fn(&mut Reader, &mut Writer) -> io::IoResult<Vec<u8>>,
       input: &str,
       expected_output: &str) {
    let mut input = BufReader::new(input.as_bytes());
    let mut out = MemWriter::new();

    assert!(bf(&mut input, &mut out).is_ok());

    assert_eq!(std::str::from_utf8(out.into_inner().as_slice()).ok().expect("non-UTF8 bf output"),
               expected_output)
}

#[test]
fn hello_world() {
    run(brainfuck!{
        ++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>
        ---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.
    }, "", "Hello World!\n");
}

#[test]
fn hello_world_foreign_tokens() {
    run(brainfuck!{
        +=
        +
        foo bar baz
        ++++++[
        =>
        ++++[>++>+++>+++>+
        <<=
        <
        $100
        <-
        ]>+>+>-
        >>=
        +[<]
        <-
        ]
        >>
        .>
        ---.+++++++
        ..
        +++.
        >>
        .
        <-.<.+++.
        -=
        -----.--------.
        >>
        +.>++.
    }, "", "Hello World!\n");
}

#[test]
fn hello_world_harder() {
    // "This is a slightly more complex variant that often triggers interpreter bugs "
    // http://esolangs.org/wiki/brainfuck
    run(brainfuck!{
        >++++++++[<+++++++++>-]<.>>+>+>++>[-]+<[>[->+<<++++>]<<]>.+++++++..+++.>
        >+++++++.<<<[[-]<[-]>]<+++++++++++++++.>>.+++.------.--------.>>+.>++++.
    }, "", "Hello World!\n");
}

#[test]
fn cat() {
    let bf = brainfuck!(,+[-.,+]);
    let mut rng = rand::task_rng();

    for _ in range(0u, 100) {
        let len = rng.gen::<uint>() % 200;
        let s = rng.gen_ascii_chars().take(len).collect::<String>();
        run(bf, s.as_slice(), s.as_slice())
    }
}
<commit_msg>Update to fc2ba1393 2015-01-03 05:35:17 +0000.<commit_after>//! Test/examples for the brainfuck macro.

#![feature(phase)]

#[phase(plugin)] extern crate brainfuck_macros;

#[cfg(bf_bf_interpreter)]
extern crate bf_bf_interpreter;

use std::{io, rand};
use std::rand::Rng;
use std::io::{BufReader, MemWriter};

/// Takes a compiled brainfuck program, feeds it `input` one byte at a
/// time, and compares the output against `expected_output`.
fn run(bf: fn(&mut Reader, &mut Writer) -> io::IoResult<Vec<u8>>,
       input: &str,
       expected_output: &str) {
    let mut input = BufReader::new(input.as_bytes());
    let mut out = MemWriter::new();

    assert!(bf(&mut input, &mut out).is_ok());

    assert_eq!(std::str::from_utf8(out.into_inner().as_slice()).ok().expect("non-UTF8 bf output"),
               expected_output)
}

#[test]
fn hello_world() {
    run(brainfuck!{
        ++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>
        ---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.
    }, "", "Hello World!\n");
}

#[test]
fn hello_world_foreign_tokens() {
    run(brainfuck!{
        +=
        +
        foo bar baz
        ++++++[
        =>
        ++++[>++>+++>+++>+
        <<=
        <
        $100
        <-
        ]>+>+>-
        >>=
        +[<]
        <-
        ]
        >>
        .>
        ---.+++++++
        ..
        +++.
        >>
        .
        <-.<.+++.
        -=
        -----.--------.
        >>
        +.>++.
    }, "", "Hello World!\n");
}

#[test]
fn hello_world_harder() {
    // "This is a slightly more complex variant that often triggers interpreter bugs "
    // http://esolangs.org/wiki/brainfuck
    run(brainfuck!{
        >++++++++[<+++++++++>-]<.>>+>+>++>[-]+<[>[->+<<++++>]<<]>.+++++++..+++.>
        >+++++++.<<<[[-]<[-]>]<+++++++++++++++.>>.+++.------.--------.>>+.>++++.
    }, "", "Hello World!\n");
}

#[test]
fn cat() {
    let bf = brainfuck!(,+[-.,+]);
    let mut rng = rand::thread_rng();

    for _ in range(0u, 100) {
        let len = rng.gen::<uint>() % 200;
        let s = rng.gen_ascii_chars().take(len).collect::<String>();
        run(bf, s.as_slice(), s.as_slice())
    }
}
<|endoftext|>


<commit_before>use ::NodeCount;

use std::sync::mpsc;
use uci;
use std::fmt;
use std::cmp;
use std::collections::HashMap;
use std::hash::Hash;
use std::cmp::Ordering;

use search_algorithms::board::GameResult;
use search_algorithms::board::EvalBoard;
use search_algorithms::game_move::Move;
use search_algorithms::board::Color::*;
use self::Score::*;
use std::thread;

extern crate time;
use std::sync::{Arc, Mutex};

/// Start a standard uci search, sending the results through a channel
pub fn start_uci_search<B> (board: B, time_limit: uci::TimeRestriction,
                            options: uci::EngineOptions, engine_comm: Arc<Mutex<uci::EngineComm>>,
                            move_list: Option<Vec<B::Move>>)
                            -> mpsc::Receiver<uci::UciInfo>
    where B: EvalBoard + fmt::Debug + Send + 'static + Hash + Eq, <B as EvalBoard>::Move: Sync + Send
{
    let (sender, receiver) = mpsc::channel();
    thread::spawn(move || uci_search(board, time_limit, options, sender, engine_comm, &move_list));
    receiver
}

pub fn uci_search<B>(board: B, time_limit: uci::TimeRestriction,
                     options: uci::EngineOptions, channel: mpsc::Sender<uci::UciInfo>,
                     engine_comm: Arc<Mutex<uci::EngineComm>>, move_list: &Option<Vec<B::Move>>)
    where B: EvalBoard + fmt::Debug + Send + Hash + Eq, <B as EvalBoard>::Move: Sync
{
    search_moves(board, engine_comm, time_limit, options, channel, move_list);
}

pub fn search_moves<B> (mut board: B, engine_comm: Arc<Mutex<uci::EngineComm>>,
                        time_restriction: uci::TimeRestriction,
                        options: uci::EngineOptions,
                        channel: mpsc::Sender<uci::UciInfo>,
                        move_list: &Option<Vec<B::Move>>)
                         -> (Score, Vec<B::Move>, NodeCount)
    where B: EvalBoard + fmt::Debug + Hash + Eq
{
    {
        engine_comm.lock().unwrap().engine_is_running = true;
    }

    let max_depth : u16 = match time_restriction {
        uci::TimeRestriction::Depth(d) => d,
        uci::TimeRestriction::Mate(d) => d as u16,
        _ => 128,
    };
    debug_assert!(max_depth > 1);

    let start_time = time::get_time();

    let (mut best_score, mut best_moves, mut best_node_count) = (None, None, None);

    let mut table = HashMap::new();

    for depth in 1..(max_depth + 1) {
        let mut total_node_count = NodeCount::new();
        let mut pvs = vec![];
        let mut pv_moves = vec![];
        for _ in 0..options.multipv {

            let mut moves_to_search = if move_list.is_some() {
                move_list.clone().unwrap()
            }
            else {
                board.all_legal_moves()
            };
            moves_to_search.retain(|mv| !pv_moves.iter().any(|mv2| mv == mv2));
            if moves_to_search.is_empty() {
                continue;
            }
            let (score, moves, node_count) =
            // TODO: If engine is told through uci to only search some moves, this will not work

                find_best_move_ab(&mut board, depth, &*engine_comm, time_restriction,
                                  Some(moves_to_search), &mut table);
            best_score = Some(score);
            best_moves = Some(moves.clone());
            best_node_count = Some(node_count.clone());

            if !moves.is_empty() {
                engine_comm.lock().unwrap().best_move = Some(moves[0].to_alg());
            }
            else {
                warn!("Find_best_move_ab didn't return any moves");
                engine_comm.lock().unwrap().best_move = None;
                continue;
            }
            let pv_str = moves.iter()
                .map(Move::to_alg)
                .collect::<Vec<_>>()
                .join(" ");
            pv_moves.push(moves[0].clone());
            pvs.push((score, pv_str));
            total_node_count = total_node_count + node_count;
        }
        let ms_taken = (time::get_time() - start_time).num_milliseconds();
        let uci_info = uci::UciInfo { depth: depth, seldepth: depth, time: ms_taken,
                                      nodes: total_node_count.total, hashfull: 0.0,
                                      pvs: pvs };
        channel.send(uci_info).unwrap();

        match time_restriction {
            uci::TimeRestriction::GameTime(info) => {
                if (board.to_move() == Black &&
                    ms_taken as u32 > info.black_inc / 5 + info.black_time / 100) ||
                    (board.to_move() == White &&
                     ms_taken as u32 > info.white_inc / 5 + info.white_time / 100)
                {
                    break;
                }

            },
            uci::TimeRestriction::MoveTime(time) => if ms_taken > time / 4 { break },

            _ => (),

        }
    }
    {
        // TODO: Makes this always get executed when the engine shuts down
        let mut engine_comm = engine_comm.lock().unwrap();
        engine_comm.engine_is_running = false;
    }
    (best_score.unwrap(), best_moves.unwrap(), best_node_count.unwrap())

}

type Table<B, M> = HashMap<B, HashEntry<M>>;

/// Returns a score, and a list of moves representing the best line it found
fn find_best_move_ab<B:> (board : &mut B, depth : u16, engine_comm : &Mutex<uci::EngineComm>,
                          time_restriction: uci::TimeRestriction,
                          move_list: Option<Vec<B::Move>>, table: &mut Table<B, B::Move>)
                          -> (Score, Vec<B::Move>, NodeCount)
    where B: EvalBoard + fmt::Debug + Hash + Eq
{

    fn find_best_move_ab_rec<B:> (board: &mut B, depth : u16,
                                  mut alpha: Score, mut beta : Score,
                                  engine_comm: &Mutex<uci::EngineComm>,
                                  time_restriction: uci::TimeRestriction,
                                  node_counter: &mut NodeCount,
                                  mut move_list: Option<Vec<B::Move>>,
                                  table: &mut Table<B, B::Move>)
                                  -> (Score, Vec<B::Move>)
        where B: EvalBoard + fmt::Debug + Hash + Eq
    {
        use uci::TimeRestriction::*;
        let first_candidate =
            if let Some(&HashEntry{ref best_move, score, depth: entry_depth }) = table.get(board) {
                if entry_depth >= depth {
                    if depth != entry_depth {
                        //println!("Position was already in cache stored at depth {}, found at depth  {}",
                        // entry_depth, depth);
                    }
                    return (score, match *best_move { Some(ref mv) => vec![mv.clone()], None => vec![]})
                }
                else {
                    best_move.clone()
                }
            }
        else {
            None
        };
        // Check if the thread should stop
        if node_counter.total % 8096 == 0 {
            let should_stop : bool;
            {
                let mut engine_comm = engine_comm.lock().unwrap();
                should_stop = engine_comm.engine_should_stop;
                if should_stop {
                    engine_comm.engine_is_running = false;
                }
            }
            if should_stop { panic!("Engine was told to stop") }
        }
        match board.game_result() {
            Some(GameResult::WhiteWin) => return (WhiteWin(0), vec![]),
            Some(GameResult::BlackWin) => return (BlackWin(0), vec![]),
            Some(GameResult::Draw) => return (Draw(0), vec![]),
            None => (),
        }
        if depth == 0 {
            node_counter.leaf += 1;
            node_counter.total += 1;
            let eval = board.eval_board();
            //let hash_entry = HashEntry::new_bottom_move(eval);
            //table.insert(board.clone(), hash_entry);
            return (Val(eval), vec![]);
        }
        else {
            node_counter.intern += 1;
            node_counter.total += 1;
        }

        match time_restriction {
            GameTime(_) => (),
            Depth(_) => (),
            Nodes(n) => if node_counter.total > n {
                engine_comm.lock().unwrap().engine_is_running = false;
                // TODO: Actually stop here
                //return (::score_board(&board), vec![]);
            },
            Mate(_) => (),
            MoveTime(_) => (), //TODO: Might want to check the clock here, and not just on every depth increase
            Infinite => (),
        };

        /*if board.half_move_clock > 50 {
            return (Draw(0), vec![]);
        }*/
        // TODO: Add this back, preferably by adding is_game_over() to the trait

        // Helpful alias
        let color = board.to_move();
        let mut best_move = None;
        let mut best_line = vec![];

        let mut legal_moves = if move_list.is_some() {
            move_list.take().unwrap()
        }
        else {
            board.all_legal_moves()
        };

        // If there is mate or stalemate on the board, we should already have returned
        assert!(!legal_moves.is_empty(), "Found 0 legal moves, but game result was {:?} on \n{:?}",
                board.game_result(), board);
        if let Some(mv) = first_candidate {
            let position = legal_moves.iter().position(|e| *e == mv).unwrap();
            legal_moves.swap(0, position);
        }

        for c_move in legal_moves {
            // Score is greater than the minimizer will ever allow OR
            // Score is lower than the maximizer will ever allow
            if (color == White && alpha >= beta) ||
                (color == Black && beta <= alpha) {
                    break;
                }
            else {
                let old_board = board.clone();
                let undo_move : <B as EvalBoard>::UndoMove = board.do_move(c_move.clone());
                let (tried_score, tried_line) =

                    find_best_move_ab_rec( board, depth - 1, alpha, beta, engine_comm,
                                           time_restriction, node_counter, None, table);
                board.undo_move(undo_move);
                debug_assert_eq!(board, &old_board,
                                 "Failed to restore board after move {:?}", c_move);

                if color == White && tried_score > alpha {
                    alpha = tried_score;
                    best_line = tried_line;
                    best_move = Some(c_move);
                }
                else if color == Black && tried_score < beta {
                    beta = tried_score;
                    best_line = tried_line;
                    best_move = Some(c_move);
                }
            }
        }
        if let Some(ref c_move) = best_move {
            best_line.push(c_move.clone());
        }
        let score = if color == White { alpha } else { beta };
        let final_score = match score {
            BlackWin(i) => BlackWin(i + 1),
            WhiteWin(i) => WhiteWin(i + 1),
            Draw(i) => Draw(i + 1),
            Val(n) => if depth == 1 {
                let n2 = board.eval_board();
                Val((n + n2) / 2.0)
            }
            else {
                Val(n)
            }, };
        if !table.contains_key(board) {
            // When doing multipv search, the positoin may already be in the hash
            // In that case, do not overwrite it
            table.insert(board.clone(),
                         HashEntry {best_move: best_move, score: final_score, depth: depth});
        }
         (final_score, best_line)

    };
    let mut node_counter = NodeCount { intern: 0, leaf: 0, total: 0 };
    let (score, mut moves) =
        find_best_move_ab_rec(board, depth, BlackWin(0), WhiteWin(0), engine_comm,
                              time_restriction, &mut node_counter, move_list, table);
    moves.reverse();
    (score, moves, node_counter)
}

struct HashEntry<M> {
    best_move: Option<M>, // The best reply. May be none if the position is game over, or if if child positions have not been evaluated yet
    score: Score,
    depth: u16,
}

/// The evaluation of a position. May be extact (A player wins in n moves) or an approximate evaluation.
#[derive(Clone, Copy, PartialEq)]
pub enum Score {
    Val(f32),
    Draw(u8),
    WhiteWin(u8),
    BlackWin(u8),
}

impl fmt::Display for Score {
    fn fmt(&self, fmt : &mut fmt::Formatter) -> Result<(), fmt::Error> {
        match *self {
            Score::Val(f) => write!(fmt, "cp {}", (100.0 * f) as i16),
            Score::WhiteWin(n) => write!(fmt, "mate {}", n as i16 / 2),
            Score::BlackWin(n) => write!(fmt, "mate {}", n as i16 / -2),
            Score::Draw(_) => write!(fmt, "0"),
        }
    }
}

impl PartialOrd for Score {
    fn partial_cmp (&self, other: &Score) -> Option<cmp::Ordering> {
        match (*self, *other) {
            (WhiteWin(n1), WhiteWin(n2)) => Some((&n2).cmp(&n1)),
            (WhiteWin(_), _) => Some(Ordering::Greater),

            (Val(_), WhiteWin(_)) => Some(Ordering::Less),
            (Val(_), BlackWin(_)) => Some(Ordering::Greater),
            (Val(n1), Val(n2)) => (&n1).partial_cmp(&n2),
            (Val(n1), Draw(_)) => (&n1).partial_cmp(&0.0),

            (Draw(_), Val(n1)) => (&0.0).partial_cmp(&n1),
            (Draw(_), Draw(_)) => Some(Ordering::Equal),
            (Draw(_), WhiteWin(_)) => Some(Ordering::Less),
            (Draw(_), BlackWin(_)) => Some(Ordering::Greater),

            (BlackWin(n1), BlackWin(n2)) => Some(n1.cmp(&n2)),
            (BlackWin(_), _) => Some(Ordering::Less),

        }
    }
}
<commit_msg>Fixed issue where selecting multipv > 1 would sometimes cause a crash<commit_after>use ::NodeCount;

use std::sync::mpsc;
use uci;
use std::fmt;
use std::cmp;
use std::collections::HashMap;
use std::hash::Hash;
use std::cmp::Ordering;

use search_algorithms::board::GameResult;
use search_algorithms::board::EvalBoard;
use search_algorithms::game_move::Move;
use search_algorithms::board::Color::*;
use self::Score::*;
use std::thread;

extern crate time;
use std::sync::{Arc, Mutex};

/// Start a standard uci search, sending the results through a channel
pub fn start_uci_search<B> (board: B, time_limit: uci::TimeRestriction,
                            options: uci::EngineOptions, engine_comm: Arc<Mutex<uci::EngineComm>>,
                            move_list: Option<Vec<B::Move>>)
                            -> mpsc::Receiver<uci::UciInfo>
    where B: EvalBoard + fmt::Debug + Send + 'static + Hash + Eq, <B as EvalBoard>::Move: Sync + Send
{
    let (sender, receiver) = mpsc::channel();
    thread::spawn(move || uci_search(board, time_limit, options, sender, engine_comm, &move_list));
    receiver
}

pub fn uci_search<B>(board: B, time_limit: uci::TimeRestriction,
                     options: uci::EngineOptions, channel: mpsc::Sender<uci::UciInfo>,
                     engine_comm: Arc<Mutex<uci::EngineComm>>, move_list: &Option<Vec<B::Move>>)
    where B: EvalBoard + fmt::Debug + Send + Hash + Eq, <B as EvalBoard>::Move: Sync
{
    search_moves(board, engine_comm, time_limit, options, channel, move_list);
}

pub fn search_moves<B> (mut board: B, engine_comm: Arc<Mutex<uci::EngineComm>>,
                        time_restriction: uci::TimeRestriction,
                        options: uci::EngineOptions,
                        channel: mpsc::Sender<uci::UciInfo>,
                        move_list: &Option<Vec<B::Move>>)
                         -> (Score, Vec<B::Move>, NodeCount)
    where B: EvalBoard + fmt::Debug + Hash + Eq
{
    {
        engine_comm.lock().unwrap().engine_is_running = true;
    }

    let max_depth : u16 = match time_restriction {
        uci::TimeRestriction::Depth(d) => d,
        uci::TimeRestriction::Mate(d) => d as u16,
        _ => 128,
    };
    debug_assert!(max_depth > 1);

    let start_time = time::get_time();
    let mut total_node_count = NodeCount::new();

    let (mut best_score, mut best_moves, mut best_node_count) = (None, None, None);

    let mut table = HashMap::new();

    for depth in 1..(max_depth + 1) {

        let mut pvs = vec![];
        let mut pv_moves = vec![];
        for _ in 0..options.multipv {

            let mut moves_to_search = if move_list.is_some() {
                move_list.clone().unwrap()
            }
            else {
                board.all_legal_moves()
            };
            moves_to_search.retain(|mv| !pv_moves.iter().any(|mv2| mv == mv2));
            if moves_to_search.is_empty() {
                continue;
            }
            let (score, moves, node_count) =
                find_best_move_ab(&mut board, depth, &*engine_comm, time_restriction,
                                  Some(moves_to_search), &mut table);
            best_score = Some(score);
            best_moves = Some(moves.clone());
            best_node_count = Some(node_count.clone());

            if !moves.is_empty() {
                engine_comm.lock().unwrap().best_move = Some(moves[0].to_alg());
            }
            else {
                warn!("Find_best_move_ab didn't return any moves");
                engine_comm.lock().unwrap().best_move = None;
                continue;
            }
            let pv_str = moves.iter()
                .map(Move::to_alg)
                .collect::<Vec<_>>()
                .join(" ");
            pv_moves.push(moves[0].clone());
            pvs.push((score, pv_str));
            total_node_count = total_node_count + node_count;
        }
        let ms_taken = (time::get_time() - start_time).num_milliseconds();
        let uci_info = uci::UciInfo { depth: depth, seldepth: depth, time: ms_taken,
                                      nodes: total_node_count.total, hashfull: 0.0,
                                      pvs: pvs };
        channel.send(uci_info).unwrap();

        match time_restriction {
            uci::TimeRestriction::GameTime(info) => {
                if (board.to_move() == Black &&
                    ms_taken as u32 > info.black_inc / 5 + info.black_time / 100) ||
                    (board.to_move() == White &&
                     ms_taken as u32 > info.white_inc / 5 + info.white_time / 100)
                {
                    break;
                }

            },
            uci::TimeRestriction::MoveTime(time) => if ms_taken > time / 4 { break },

            _ => (),

        }
    }
    {
        // TODO: Makes this always get executed when the engine shuts down
        let mut engine_comm = engine_comm.lock().unwrap();
        engine_comm.engine_is_running = false;
    }
    (best_score.unwrap(), best_moves.unwrap(), best_node_count.unwrap())

}

type Table<B, M> = HashMap<B, HashEntry<M>>;

/// Returns a score, and a list of moves representing the best line it found
fn find_best_move_ab<B:> (board : &mut B, depth : u16, engine_comm : &Mutex<uci::EngineComm>,
                          time_restriction: uci::TimeRestriction,
                          move_list: Option<Vec<B::Move>>, table: &mut Table<B, B::Move>)
                          -> (Score, Vec<B::Move>, NodeCount)
    where B: EvalBoard + fmt::Debug + Hash + Eq
{

    fn find_best_move_ab_rec<B:> (board: &mut B, depth : u16,
                                  mut alpha: Score, mut beta : Score,
                                  engine_comm: &Mutex<uci::EngineComm>,
                                  time_restriction: uci::TimeRestriction,
                                  node_counter: &mut NodeCount,
                                  mut move_list: Option<Vec<B::Move>>,
                                  table: &mut Table<B, B::Move>)
                                  -> (Score, Vec<B::Move>)
        where B: EvalBoard + fmt::Debug + Hash + Eq
    {
        use uci::TimeRestriction::*;
        let first_candidate =
            if let Some(&HashEntry{ref best_move, score, depth: entry_depth }) = table.get(board) {
                if entry_depth >= depth {
                    return (score, match *best_move {
                        Some(ref mv) => vec![mv.clone()],
                        None => vec![]
                    })
                }
                else {
                    best_move.clone()
                }
            }
        else {
            None
        };
        // Check if the thread should stop
        if node_counter.total % 8096 == 0 {
            let should_stop : bool;
            {
                let mut engine_comm = engine_comm.lock().unwrap();
                should_stop = engine_comm.engine_should_stop;
                if should_stop {
                    engine_comm.engine_is_running = false;
                }
            }
            if should_stop { panic!("Engine was told to stop") }
        }
        match board.game_result() {
            Some(GameResult::WhiteWin) => return (WhiteWin(0), vec![]),
            Some(GameResult::BlackWin) => return (BlackWin(0), vec![]),
            Some(GameResult::Draw) => return (Draw(0), vec![]),
            None => (),
        }
        if depth == 0 {
            node_counter.leaf += 1;
            node_counter.total += 1;
            let eval = board.eval_board();
            //let hash_entry = HashEntry::new_bottom_move(eval);
            //table.insert(board.clone(), hash_entry);
            return (Val(eval), vec![]);
        }
        else {
            node_counter.intern += 1;
            node_counter.total += 1;
        }

        match time_restriction {
            GameTime(_) => (),
            Depth(_) => (),
            Nodes(n) => if node_counter.total > n {
                engine_comm.lock().unwrap().engine_is_running = false;
                // TODO: Actually stop here
                //return (::score_board(&board), vec![]);
            },
            Mate(_) => (),
            MoveTime(_) => (), //TODO: Might want to check the clock here, and not just on every depth increase
            Infinite => (),
        };

        /*if board.half_move_clock > 50 {
            return (Draw(0), vec![]);
        }*/
        // TODO: Add this back, preferably by adding is_game_over() to the trait

        // Helpful alias
        let color = board.to_move();
        let mut best_move = None;
        let mut best_line = vec![];

        let mut legal_moves = if move_list.is_some() {
            move_list.take().unwrap()
        }
        else {
            board.all_legal_moves()
        };

        // If there is mate or stalemate on the board, we should already have returned
        assert!(!legal_moves.is_empty(), "Found 0 legal moves, but game result was {:?} on \n{:?}",
                board.game_result(), board);
        if let Some(mv) = first_candidate {
            // May fail if search is restricted to only certain moves
            if let Some(position) = legal_moves.iter().position(|e| *e == mv) {
                legal_moves.swap(0, position);
            }
        }

        for c_move in legal_moves {
            // Score is greater than the minimizer will ever allow OR
            // Score is lower than the maximizer will ever allow
            if (color == White && alpha >= beta) ||
                (color == Black && beta <= alpha) {
                    break;
                }
            else {
                let old_board = board.clone();
                let undo_move : <B as EvalBoard>::UndoMove = board.do_move(c_move.clone());
                let (tried_score, tried_line) =

                    find_best_move_ab_rec( board, depth - 1, alpha, beta, engine_comm,
                                           time_restriction, node_counter, None, table);
                board.undo_move(undo_move);
                debug_assert_eq!(board, &old_board,
                                 "Failed to restore board after move {:?}", c_move);

                if color == White && tried_score > alpha {
                    alpha = tried_score;
                    best_line = tried_line;
                    best_move = Some(c_move);
                }
                else if color == Black && tried_score < beta {
                    beta = tried_score;
                    best_line = tried_line;
                    best_move = Some(c_move);
                }
            }
        }
        if let Some(ref c_move) = best_move {
            best_line.push(c_move.clone());
        }
        let score = if color == White { alpha } else { beta };
        let final_score = match score {
            BlackWin(i) => BlackWin(i + 1),
            WhiteWin(i) => WhiteWin(i + 1),
            Draw(i) => Draw(i + 1),
            Val(n) => if depth == 1 {
                let n2 = board.eval_board();
                Val((n + n2) / 2.0)
            }
            else {
                Val(n)
            }, };
        if !table.contains_key(board) {
            // When doing multipv search, the positoin may already be in the hash
            // In that case, do not overwrite it
            table.insert(board.clone(),
                         HashEntry {best_move: best_move, score: final_score, depth: depth});
        }
         (final_score, best_line)

    };
    let mut node_counter = NodeCount { intern: 0, leaf: 0, total: 0 };
    let (score, mut moves) =
        find_best_move_ab_rec(board, depth, BlackWin(0), WhiteWin(0), engine_comm,
                              time_restriction, &mut node_counter, move_list, table);
    moves.reverse();
    (score, moves, node_counter)
}

struct HashEntry<M> {
    best_move: Option<M>, // The best reply. May be none if the position is game over, or if if child positions have not been evaluated yet
    score: Score,
    depth: u16,
}

/// The evaluation of a position. May be extact (A player wins in n moves) or an approximate evaluation.
#[derive(Clone, Copy, PartialEq)]
pub enum Score {
    Val(f32),
    Draw(u8),
    WhiteWin(u8),
    BlackWin(u8),
}

impl fmt::Display for Score {
    fn fmt(&self, fmt : &mut fmt::Formatter) -> Result<(), fmt::Error> {
        match *self {
            Score::Val(f) => write!(fmt, "cp {}", (100.0 * f) as i16),
            Score::WhiteWin(n) => write!(fmt, "mate {}", n as i16 / 2),
            Score::BlackWin(n) => write!(fmt, "mate {}", n as i16 / -2),
            Score::Draw(_) => write!(fmt, "0"),
        }
    }
}

impl PartialOrd for Score {
    fn partial_cmp (&self, other: &Score) -> Option<cmp::Ordering> {
        match (*self, *other) {
            (WhiteWin(n1), WhiteWin(n2)) => Some((&n2).cmp(&n1)),
            (WhiteWin(_), _) => Some(Ordering::Greater),

            (Val(_), WhiteWin(_)) => Some(Ordering::Less),
            (Val(_), BlackWin(_)) => Some(Ordering::Greater),
            (Val(n1), Val(n2)) => (&n1).partial_cmp(&n2),
            (Val(n1), Draw(_)) => (&n1).partial_cmp(&0.0),

            (Draw(_), Val(n1)) => (&0.0).partial_cmp(&n1),
            (Draw(_), Draw(_)) => Some(Ordering::Equal),
            (Draw(_), WhiteWin(_)) => Some(Ordering::Less),
            (Draw(_), BlackWin(_)) => Some(Ordering::Greater),

            (BlackWin(n1), BlackWin(n2)) => Some(n1.cmp(&n2)),
            (BlackWin(_), _) => Some(Ordering::Less),

        }
    }
}
<|endoftext|>


<commit_before>// http://rosettacode.org/wiki/Strip_comments_from_a_string

fn strip_comments(str: &str) -> &str {
    let markers = ['#', ';'];
    str.find(markers.as_slice()).map_or(str, |i| str.slice_to(i)).trim_right()
}

#[test]
fn test_strip_comments() {
    let inputs = ["apples, pears # and bananas",
                  "apples, pears ; and bananas",
                  "apples, pears "];
    let output = "apples, pears";

    for &input in inputs.iter() {
        assert_eq!(strip_comments(input), output)
    }
}

#[cfg(not(test))]
fn main() {
    let inputs = ["apples, pears # and bananas",
                  "apples, pears ; and bananas",
                  "apples, pears "];

    for &input in inputs.iter() {
        println!("Input: {}\nStripped: {}", input, strip_comments(input))
    }
}
<commit_msg>Trim the string from both sides, not just the right side.<commit_after>// http://rosettacode.org/wiki/Strip_comments_from_a_string

fn strip_comments(str: &str) -> &str {
    let markers = ['#', ';'];
    str.find(markers.as_slice()).map_or(str, |i| str.slice_to(i)).trim()
}

#[test]
fn test_strip_comments() {
    let inputs = ["apples, pears # and bananas",
                  "apples, pears ; and bananas",
                  "  apples, pears "];
    let output = "apples, pears";

    for &input in inputs.iter() {
        assert_eq!(strip_comments(input), output)
    }
}

#[cfg(not(test))]
fn main() {
    let inputs = ["apples, pears # and bananas",
                  "apples, pears ; and bananas",
                  "  apples, pears "];

    for &input in inputs.iter() {
        println!("Input: {}\nStripped: {}", input, strip_comments(input))
    }
}
<|endoftext|>