<commit_before>extern crate serde_derive;

use std::collections::{BTreeMap, HashSet};
use std::error::Error;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::{Path, PathBuf};

use chrono::{DateTime, Utc};
use clap::{App, Arg, ArgMatches, SubCommand};
use glob::glob;
use serde_derive::{Deserialize, Serialize};

#[derive(Deserialize, Debug, Clone)]
struct RepoConfig {
    path: String,
    #[serde(default = "default_false")]
    commit: bool,
    #[serde(default = "default_false")]
    sync_updates: bool,
}

fn default_false() -> bool {
    false
}

#[derive(Deserialize, Debug)]
struct Supfile {
    repo: RepoConfig,
    editor: Option<String>,
}

impl Supfile {
    pub fn from(str: &str) -> Result<Self, toml::de::Error> {
        toml::from_str(&str)
    }

    pub fn find() -> PathBuf {
        // TODO: pull in XDG crate for paths
        let default_dir = "~/.config/sup/";

        ["./", default_dir]
            .iter()
            .map(|dir| Path::new(dir).join("Supfile"))
            .find(|p| p.exists())
            .unwrap_or_else(|| {
                // TODO: offer to create path somehow
                panic!("can't find supfile")
            })
    }
}

#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
enum TaskState {
    Todo,
    InProgress,
    Complete,
    Canceled,
    Deferred { until: DateTime<Utc> },
}

#[derive(Deserialize, Serialize)]
enum Action {
    Create {
        task: String,
        notes: Option<String>,
        tags: Vec<String>,
    },
    EditTags {
        add: Vec<String>,
        remove: Vec<String>,
    },
    EditTask(String),
    EditNotes(Option<String>),
    EditState(TaskState),
}

#[derive(Deserialize, Serialize)]
struct TaskUpdate {
    id: String,
    timestamp: DateTime<Utc>,
    action: Action,
}

impl TaskUpdate {
    fn from_str(line: &str) -> serde_json::Result<Self> {
        serde_json::from_str(line).map_err(|e| {
            println!("nuts: {:?}", e);
            e
        })
    }
}

#[derive(Clone, Debug)]
struct Task {
    id: String,
    created_at: DateTime<Utc>,
    completed_at: Option<DateTime<Utc>>,
    state: TaskState,
    task: String,
    notes: Option<String>,
    tags: HashSet<String>,
}

impl Task {
    fn apply(&mut self, update: &TaskUpdate) {
        match &update.action {
            Action::Create { task, notes, tags } => {
                self.task = task.clone();
                self.notes = notes.clone();
                for tag in tags.iter() {
                    self.tags.insert(tag.clone());
                }
            }
            Action::EditTags { add, remove } => {
                for tag in remove.iter() {
                    self.tags.remove(tag);
                }

                for tag in add.iter() {
                    self.tags.insert(tag.clone());
                }
            }
            Action::EditTask(task) => self.task = task.clone(),
            Action::EditNotes(notes) => self.notes = notes.clone(),
            Action::EditState(state) => {
                self.state = state.clone();
                if state == &TaskState::Complete {
                    self.completed_at = Some(update.timestamp);
                }
            }
        }
    }
}

impl From<&TaskUpdate> for Task {
    fn from(update: &TaskUpdate) -> Self {
        let mut task = Task {
            id: update.id.clone(),
            created_at: update.timestamp.clone(),
            completed_at: None,
            state: TaskState::Todo,
            task: "untitled".to_string(),
            notes: None,
            tags: HashSet::new(),
        };

        task.apply(update);
        return task;
    }
}

#[derive(Debug)]
struct SupdateLog {
    name: String,
    file: BufReader<File>,
}

impl SupdateLog {
    fn new(path: &Path) -> Self {
        let name = path
            .file_name()
            .expect("need file, not directory")
            .to_string_lossy()
            .into();

        let file = File::open(path).expect("could not open file");
        // TODO: Support versioning in line

        SupdateLog {
            name,
            file: BufReader::new(file),
        }
    }
}

impl Iterator for SupdateLog {
    type Item = TaskUpdate;

    fn next(&mut self) -> Option<TaskUpdate> {
        let mut line = String::new();
        return match self.file.read_line(&mut line) {
            // End of file
            Ok(0) => None,

            Ok(_) => {
                println!("line = {:?}", line);
                // TODO: alert when we don't succeed at parsing
                TaskUpdate::from_str(&line).ok()
            }

            Err(e) => {
                println!("Failed to read line: {:?}", e);
                None
            }
        };
    }
}

/**
 * A Repository represents a directory containing SupdateLogs.
 *
 * The directory structure is something like this:
 *
 * repo/
 *   2019/
 *     08/
 *       20190801_235959.sup
 *       20190802_003030.sup
 *       20190802_123030.sup
 *
 * TODO: Support concept of `rollup` files to put an upper limit on
 * how far we need to look back to fully reconstruct the state of
 * everything.
 */

struct Repository {
    config: RepoConfig,
}

impl Repository {
    fn new(cfg: RepoConfig) -> Self {
        // TODO: Handle error, return Result
        if !Path::new(&cfg.path).exists() {
            panic!("given repository does not exist")
        }

        Repository { config: cfg }
    }

    fn open_journal(&self) -> Journal {
        let updates = self.read_updates();
        self.build_journal(updates)
    }

    fn build_journal(&self, updates: Vec<SupdateLog>) -> Journal {
        let mut journal = Journal::new();

        // Reverse ordering so that we consume oldest elements first.
        for log in updates.into_iter().rev() {
            println!("reading: {:?}", log);

            for ref update in log {
                journal.add(update);
            }
        }

        journal
    }

    fn read_updates(&self) -> Vec<SupdateLog> {
        let path = Path::new(&self.config.path).join("**/*.sup");

        // TODO: This is really wasteful. Make this an iterator
        // Get list of all files in the repo
        let mut updates = glob(path.to_str().unwrap())
            .expect("glob failed")
            .filter_map(|f| f.map(|p| SupdateLog::new(&p)).ok())
            .collect::<Vec<SupdateLog>>();

        // We want sorted (chronological, desc) order, based on file name.
        updates.sort_by(|a, b| b.name.cmp(&a.name));
        return updates;
    }
}

/**
 * A collection of Updates.
 *
 * Represents the cumulative, "rolled up" set of task updates.
 */
#[derive(Debug)]
struct Journal {
    /// id -> task
    entries: BTreeMap<String, Task>,
}

impl Journal {
    fn new() -> Self {
        return Self {
            entries: BTreeMap::new(),
        };
    }

    fn add(&mut self, update: &TaskUpdate) {
        let task = self.entries.get_mut(&update.id);

        match task {
            Some(t) => t.apply(update),
            None => {
                self.entries.insert(update.id.clone(), Task::from(update));
            }
        }
    }
}

struct SupApp {
    matches: ArgMatches<'static>,
}

impl SupApp {
    fn new() -> Self {
        Self {
            matches: Self::parse_args(),
        }
    }

    fn parse_args<'a>() -> ArgMatches<'a> {
        App::new("sup")
            .version("0.0.0")
            .about("Keep track of what's up.")
            .arg(
                Arg::with_name("config")
                    .short("c")
                    .long("config")
                    .value_name("FILE")
                    .takes_value(true)
                    .help("Set custom location for Supfile"),
            )
            .subcommand(
                SubCommand::with_name("show")
                    .about("asdf")
                    .arg(Arg::with_name("DATE").takes_value(true).help("asdf")),
            )
            .subcommand(
                SubCommand::with_name("serve")
                    .about("launch web interface")
                    .arg(
                        Arg::with_name("port")
                            .short("p")
                            .long("port")
                            .takes_value(true),
                    ),
            )
            .get_matches()
    }

    fn load_config(&self) -> Result<Supfile, Box<dyn Error>> {
        let path = self
            .matches
            .value_of("config")
            .map(|p| Path::new(p).to_path_buf())
            .unwrap_or_else(Supfile::find);

        let bytes = std::fs::read_to_string(path.as_os_str())?;
        Ok(Supfile::from(&bytes)?)
    }

    fn run(&self) -> Result<(), Box<dyn Error>> {
        let cfg = self.load_config()?;

        match self.matches.subcommand() {
            ("show", Some(m)) => println!("TODO: show {:?}", m),
            ("serve", Some(m)) => println!("TODO: serve {:?}", m),
            _ => {
                println!("add update");

                run_add_update(&cfg)?;
            }
        };

        Ok(())
    }
}

fn run_add_update(cfg: &Supfile) -> Result<(), Box<dyn Error>> {
    let repo = Repository::new(cfg.repo.clone());
    let journal = repo.open_journal();

    println!("journal: {:?}", journal);

    Ok(())
}

// TODO: Investiage this instead of Box<Error>
// https://github.com/rust-lang-nursery/failure
fn main() {
    let app = SupApp::new();
    match app.run() {
        Err(e) => println!("oh no! {:?}", e),
        Ok(_) => (),
    };
}
<commit_msg>Use snake case for enum representations<commit_after>extern crate serde_derive;

use std::collections::{BTreeMap, HashSet};
use std::error::Error;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::{Path, PathBuf};

use chrono::{DateTime, Utc};
use clap::{App, Arg, ArgMatches, SubCommand};
use glob::glob;
use serde_derive::{Deserialize, Serialize};

#[derive(Deserialize, Debug, Clone)]
struct RepoConfig {
    path: String,
    #[serde(default = "default_false")]
    commit: bool,
    #[serde(default = "default_false")]
    sync_updates: bool,
}

fn default_false() -> bool {
    false
}

#[derive(Deserialize, Debug)]
struct Supfile {
    repo: RepoConfig,
    editor: Option<String>,
}

impl Supfile {
    pub fn from(str: &str) -> Result<Self, toml::de::Error> {
        toml::from_str(&str)
    }

    pub fn find() -> PathBuf {
        // TODO: pull in XDG crate for paths
        let default_dir = "~/.config/sup/";

        ["./", default_dir]
            .iter()
            .map(|dir| Path::new(dir).join("Supfile"))
            .find(|p| p.exists())
            .unwrap_or_else(|| {
                // TODO: offer to create path somehow
                panic!("can't find supfile")
            })
    }
}

#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
enum TaskState {
    Todo,
    InProgress,
    Complete,
    Canceled,
    Deferred { until: DateTime<Utc> },
}

#[derive(Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
enum Action {
    Create {
        task: String,
        #[serde(default)]
        notes: Option<String>,
        #[serde(default)]
        tags: Vec<String>,
    },
    EditTags {
        add: Vec<String>,
        remove: Vec<String>,
    },
    EditTask(String),
    EditNotes(Option<String>),
    EditState(TaskState),
}

#[derive(Deserialize, Serialize)]
struct TaskUpdate {
    id: String,
    timestamp: DateTime<Utc>,
    action: Action,
}

impl TaskUpdate {
    fn from_str(line: &str) -> serde_json::Result<Self> {
        serde_json::from_str(line).map_err(|e| {
            println!("nuts: {:?}", e);
            e
        })
    }
}

#[derive(Clone, Debug)]
struct Task {
    id: String,
    created_at: DateTime<Utc>,
    completed_at: Option<DateTime<Utc>>,
    state: TaskState,
    task: String,
    notes: Option<String>,
    tags: HashSet<String>,
}

impl Task {
    fn apply(&mut self, update: &TaskUpdate) {
        match &update.action {
            Action::Create { task, notes, tags } => {
                self.task = task.clone();
                self.notes = notes.clone();
                for tag in tags.iter() {
                    self.tags.insert(tag.clone());
                }
            }
            Action::EditTags { add, remove } => {
                for tag in remove.iter() {
                    self.tags.remove(tag);
                }

                for tag in add.iter() {
                    self.tags.insert(tag.clone());
                }
            }
            Action::EditTask(task) => self.task = task.clone(),
            Action::EditNotes(notes) => self.notes = notes.clone(),
            Action::EditState(state) => {
                self.state = state.clone();
                if state == &TaskState::Complete {
                    self.completed_at = Some(update.timestamp);
                }
            }
        }
    }
}

impl From<&TaskUpdate> for Task {
    fn from(update: &TaskUpdate) -> Self {
        let mut task = Task {
            id: update.id.clone(),
            created_at: update.timestamp.clone(),
            completed_at: None,
            state: TaskState::Todo,
            task: "untitled".to_string(),
            notes: None,
            tags: HashSet::new(),
        };

        task.apply(update);
        return task;
    }
}

#[derive(Debug)]
struct SupdateLog {
    name: String,
    file: BufReader<File>,
}

impl SupdateLog {
    fn new(path: &Path) -> Self {
        let name = path
            .file_name()
            .expect("need file, not directory")
            .to_string_lossy()
            .into();

        let file = File::open(path).expect("could not open file");
        // TODO: Support versioning in line

        SupdateLog {
            name,
            file: BufReader::new(file),
        }
    }
}

impl Iterator for SupdateLog {
    type Item = TaskUpdate;

    fn next(&mut self) -> Option<TaskUpdate> {
        let mut line = String::new();
        return match self.file.read_line(&mut line) {
            // End of file
            Ok(0) => None,

            Ok(_) => {
                println!("line = {:?}", line);
                // TODO: alert when we don't succeed at parsing
                TaskUpdate::from_str(&line).ok()
            }

            Err(e) => {
                println!("Failed to read line: {:?}", e);
                None
            }
        };
    }
}

/**
 * A Repository represents a directory containing SupdateLogs.
 *
 * The directory structure is something like this:
 *
 * repo/
 *   2019/
 *     08/
 *       20190801_235959.sup
 *       20190802_003030.sup
 *       20190802_123030.sup
 *
 * TODO: Support concept of `rollup` files to put an upper limit on
 * how far we need to look back to fully reconstruct the state of
 * everything.
 */

struct Repository {
    config: RepoConfig,
}

impl Repository {
    fn new(cfg: RepoConfig) -> Self {
        // TODO: Handle error, return Result
        if !Path::new(&cfg.path).exists() {
            panic!("given repository does not exist")
        }

        Repository { config: cfg }
    }

    fn open_journal(&self) -> Journal {
        let updates = self.read_updates();
        self.build_journal(updates)
    }

    fn build_journal(&self, updates: Vec<SupdateLog>) -> Journal {
        let mut journal = Journal::new();

        // Reverse ordering so that we consume oldest elements first.
        for log in updates.into_iter().rev() {
            println!("reading: {:?}", log);

            for ref update in log {
                journal.add(update);
            }
        }

        journal
    }

    fn read_updates(&self) -> Vec<SupdateLog> {
        let path = Path::new(&self.config.path).join("**/*.sup");

        // TODO: This is really wasteful. Make this an iterator
        // Get list of all files in the repo
        let mut updates = glob(path.to_str().unwrap())
            .expect("glob failed")
            .filter_map(|f| f.map(|p| SupdateLog::new(&p)).ok())
            .collect::<Vec<SupdateLog>>();

        // We want sorted (chronological, desc) order, based on file name.
        updates.sort_by(|a, b| b.name.cmp(&a.name));
        return updates;
    }
}

/**
 * A collection of Updates.
 *
 * Represents the cumulative, "rolled up" set of task updates.
 */
#[derive(Debug)]
struct Journal {
    /// id -> task
    entries: BTreeMap<String, Task>,
}

impl Journal {
    fn new() -> Self {
        return Self {
            entries: BTreeMap::new(),
        };
    }

    fn add(&mut self, update: &TaskUpdate) {
        let task = self.entries.get_mut(&update.id);

        match task {
            Some(t) => t.apply(update),
            None => {
                self.entries.insert(update.id.clone(), Task::from(update));
            }
        }
    }
}

struct SupApp {
    matches: ArgMatches<'static>,
}

impl SupApp {
    fn new() -> Self {
        Self {
            matches: Self::parse_args(),
        }
    }

    fn parse_args<'a>() -> ArgMatches<'a> {
        App::new("sup")
            .version("0.0.0")
            .about("Keep track of what's up.")
            .arg(
                Arg::with_name("config")
                    .short("c")
                    .long("config")
                    .value_name("FILE")
                    .takes_value(true)
                    .help("Set custom location for Supfile"),
            )
            .subcommand(
                SubCommand::with_name("show")
                    .about("asdf")
                    .arg(Arg::with_name("DATE").takes_value(true).help("asdf")),
            )
            .subcommand(
                SubCommand::with_name("serve")
                    .about("launch web interface")
                    .arg(
                        Arg::with_name("port")
                            .short("p")
                            .long("port")
                            .takes_value(true),
                    ),
            )
            .get_matches()
    }

    fn load_config(&self) -> Result<Supfile, Box<dyn Error>> {
        let path = self
            .matches
            .value_of("config")
            .map(|p| Path::new(p).to_path_buf())
            .unwrap_or_else(Supfile::find);

        let bytes = std::fs::read_to_string(path.as_os_str())?;
        Ok(Supfile::from(&bytes)?)
    }

    fn run(&self) -> Result<(), Box<dyn Error>> {
        let cfg = self.load_config()?;

        match self.matches.subcommand() {
            ("show", Some(m)) => println!("TODO: show {:?}", m),
            ("serve", Some(m)) => println!("TODO: serve {:?}", m),
            _ => {
                println!("add update");

                run_add_update(&cfg)?;
            }
        };

        Ok(())
    }
}

fn run_add_update(cfg: &Supfile) -> Result<(), Box<dyn Error>> {
    let repo = Repository::new(cfg.repo.clone());
    let journal = repo.open_journal();

    println!("journal: {:?}", journal);

    Ok(())
}

// TODO: Investiage this instead of Box<Error>
// https://github.com/rust-lang-nursery/failure
fn main() {
    let app = SupApp::new();
    match app.run() {
        Err(e) => println!("oh no! {:?}", e),
        Ok(_) => (),
    };
}
<|endoftext|>


<commit_before>// ignore-windows: File handling is not implemented yet
// compile-flags: -Zmiri-disable-isolation

#![feature(rustc_private)]

use std::fs::{
    File, create_dir, OpenOptions, read_dir, remove_dir, remove_dir_all, remove_file, rename,
};
use std::ffi::CString;
use std::io::{Read, Write, Error, ErrorKind, Result, Seek, SeekFrom};
use std::path::{PathBuf, Path};

extern crate libc;

fn main() {
    test_file();
    test_file_clone();
    test_file_create_new();
    test_seek();
    test_metadata();
    test_file_set_len();
    test_file_sync();
    test_symlink();
    test_errors();
    test_rename();
    test_directory();
    test_dup_stdout_stderr();
}

fn tmp() -> PathBuf {
    std::env::var("MIRI_TEMP").map(PathBuf::from).unwrap_or_else(|_| std::env::temp_dir())
}

/// Prepare: compute filename and make sure the file does not exist.
fn prepare(filename: &str) -> PathBuf {
    let path = tmp().join(filename);
    // Clean the paths for robustness.
    remove_file(&path).ok();
    path
}

/// Prepare directory: compute directory name and make sure it does not exist.
fn prepare_dir(dirname: &str) -> PathBuf {
    let path = tmp().join(&dirname);
    // Clean the directory for robustness.
    remove_dir_all(&path).ok();
    path
}

/// Prepare like above, and also write some initial content to the file.
fn prepare_with_content(filename: &str, content: &[u8]) -> PathBuf {
    let path = prepare(filename);
    let mut file = File::create(&path).unwrap();
    file.write(content).unwrap();
    path
}

fn test_file() {
    let bytes = b"Hello, World!\n";
    let path = prepare("miri_test_fs_file.txt");

    // Test creating, writing and closing a file (closing is tested when `file` is dropped).
    let mut file = File::create(&path).unwrap();
    // Writing 0 bytes should not change the file contents.
    file.write(&mut []).unwrap();
    assert_eq!(file.metadata().unwrap().len(), 0);

    file.write(bytes).unwrap();
    assert_eq!(file.metadata().unwrap().len(), bytes.len() as u64);
    // Test opening, reading and closing a file.
    let mut file = File::open(&path).unwrap();
    let mut contents = Vec::new();
    // Reading 0 bytes should not move the file pointer.
    file.read(&mut []).unwrap();
    // Reading until EOF should get the whole text.
    file.read_to_end(&mut contents).unwrap();
    assert_eq!(bytes, contents.as_slice());

    // Removing file should succeed.
    remove_file(&path).unwrap();
}

fn test_file_clone() {
    let bytes = b"Hello, World!\n";
    let path = prepare_with_content("miri_test_fs_file_clone.txt", bytes);

    // Cloning a file should be successful.
    let file = File::open(&path).unwrap();
    let mut cloned = file.try_clone().unwrap();
    // Reading from a cloned file should get the same text.
    let mut contents = Vec::new();
    cloned.read_to_end(&mut contents).unwrap();
    assert_eq!(bytes, contents.as_slice());

    // Removing file should succeed.
    remove_file(&path).unwrap();
}

fn test_file_create_new() {
    let path = prepare("miri_test_fs_file_create_new.txt");

    // Creating a new file that doesn't yet exist should succeed.
    OpenOptions::new().write(true).create_new(true).open(&path).unwrap();
    // Creating a new file that already exists should fail.
    assert_eq!(ErrorKind::AlreadyExists, OpenOptions::new().write(true).create_new(true).open(&path).unwrap_err().kind());
    // Optionally creating a new file that already exists should succeed.
    OpenOptions::new().write(true).create(true).open(&path).unwrap();

    // Clean up
    remove_file(&path).unwrap();
}

fn test_seek() {
    let bytes = b"Hello, entire World!\n";
    let path = prepare_with_content("miri_test_fs_seek.txt", bytes);

    let mut file = File::open(&path).unwrap();
    let mut contents = Vec::new();
    file.read_to_end(&mut contents).unwrap();
    assert_eq!(bytes, contents.as_slice());
    // Test that seeking to the beginning and reading until EOF gets the text again.
    file.seek(SeekFrom::Start(0)).unwrap();
    let mut contents = Vec::new();
    file.read_to_end(&mut contents).unwrap();
    assert_eq!(bytes, contents.as_slice());
    // Test seeking relative to the end of the file.
    file.seek(SeekFrom::End(-1)).unwrap();
    let mut contents = Vec::new();
    file.read_to_end(&mut contents).unwrap();
    assert_eq!(&bytes[bytes.len() - 1..], contents.as_slice());
    // Test seeking relative to the current position.
    file.seek(SeekFrom::Start(5)).unwrap();
    file.seek(SeekFrom::Current(-3)).unwrap();
    let mut contents = Vec::new();
    file.read_to_end(&mut contents).unwrap();
    assert_eq!(&bytes[2..], contents.as_slice());

    // Removing file should succeed.
    remove_file(&path).unwrap();
}

fn check_metadata(bytes: &[u8], path: &Path) -> Result<()> {
    // Test that the file metadata is correct.
    let metadata = path.metadata()?;
    // `path` should point to a file.
    assert!(metadata.is_file());
    // The size of the file must be equal to the number of written bytes.
    assert_eq!(bytes.len() as u64, metadata.len());
    Ok(())
}

fn test_metadata() {
    let bytes = b"Hello, meta-World!\n";
    let path = prepare_with_content("miri_test_fs_metadata.txt", bytes);

    // Test that metadata of an absolute path is correct.
    check_metadata(bytes, &path).unwrap();
    // Test that metadata of a relative path is correct.
    std::env::set_current_dir(path.parent().unwrap()).unwrap();
    check_metadata(bytes, Path::new(path.file_name().unwrap())).unwrap();

    // Removing file should succeed.
    remove_file(&path).unwrap();
}

fn test_file_set_len() {
    let bytes = b"Hello, World!\n";
    let path = prepare_with_content("miri_test_fs_set_len.txt", bytes);

    // Test extending the file
    let mut file = OpenOptions::new().read(true).write(true).open(&path).unwrap();
    let bytes_extended = b"Hello, World!\n\x00\x00\x00\x00\x00\x00";
    file.set_len(20).unwrap();
    let mut contents = Vec::new();
    file.read_to_end(&mut contents).unwrap();
    assert_eq!(bytes_extended, contents.as_slice());

    // Test truncating the file
    file.seek(SeekFrom::Start(0)).unwrap();
    file.set_len(10).unwrap();
    let mut contents = Vec::new();
    file.read_to_end(&mut contents).unwrap();
    assert_eq!(&bytes[..10], contents.as_slice());

    // Can't use set_len on a file not opened for writing
    let file = OpenOptions::new().read(true).open(&path).unwrap();
    assert_eq!(ErrorKind::InvalidInput, file.set_len(14).unwrap_err().kind());

    remove_file(&path).unwrap();
}

fn test_file_sync() {
    let bytes = b"Hello, World!\n";
    let path = prepare_with_content("miri_test_fs_sync.txt", bytes);

    // Test that we can call sync_data and sync_all (can't readily test effects of this operation)
    let file = OpenOptions::new().write(true).open(&path).unwrap();
    file.sync_data().unwrap();
    file.sync_all().unwrap();

    // Test that we can call sync_data and sync_all on a file opened for reading.
    let file = File::open(&path).unwrap();
    file.sync_data().unwrap();
    file.sync_all().unwrap();

    remove_file(&path).unwrap();
}

fn test_symlink() {
    let bytes = b"Hello, World!\n";
    let path = prepare_with_content("miri_test_fs_link_target.txt", bytes);
    let symlink_path = prepare("miri_test_fs_symlink.txt");

    // Creating a symbolic link should succeed.
    #[cfg(unix)]
    std::os::unix::fs::symlink(&path, &symlink_path).unwrap();
    #[cfg(windows)]
    std::os::windows::fs::symlink_file(&path, &symlink_path).unwrap();
    // Test that the symbolic link has the same contents as the file.
    let mut symlink_file = File::open(&symlink_path).unwrap();
    let mut contents = Vec::new();
    symlink_file.read_to_end(&mut contents).unwrap();
    assert_eq!(bytes, contents.as_slice());

    #[cfg(unix)]
    {
        use std::os::unix::ffi::OsStrExt;

        let expected_path = path.as_os_str().as_bytes();

        // Test that the expected string gets written to a buffer of proper
        // length, and that a trailing null byte is not written.
        let symlink_c_str = CString::new(symlink_path.as_os_str().as_bytes()).unwrap();
        let symlink_c_ptr = symlink_c_str.as_ptr();

        // Make the buf one byte larger than it needs to be,
        // and check that the last byte is not overwritten.
        let mut large_buf = vec![0xFF; expected_path.len() + 1];
        let res = unsafe { libc::readlink(symlink_c_ptr, large_buf.as_mut_ptr().cast(), large_buf.len()) };
        assert_eq!(res, large_buf.len() as isize - 1);
        // Check that the resovled path was properly written into the buf.
        assert_eq!(&large_buf[..(large_buf.len() - 1)], expected_path);
        assert_eq!(large_buf.last(), Some(&0xFF));

        // Test that the resolved path is truncated if the provided buffer
        // is too small.
        let mut small_buf = [0u8; 2];
        let res = unsafe { libc::readlink(symlink_c_ptr, small_buf.as_mut_ptr().cast(), small_buf.len()) };
        assert_eq!(res, small_buf.len() as isize);
        assert_eq!(small_buf, &expected_path[..small_buf.len()]);

        // Test that we report a proper error for a missing path.
        let bad_path = CString::new("MIRI_MISSING_FILE_NAME").unwrap();
        let res = unsafe { libc::readlink(bad_path.as_ptr(), small_buf.as_mut_ptr().cast(), small_buf.len()) };
        assert_eq!(res, -1);
        assert_eq!(Error::last_os_error().kind(), ErrorKind::NotFound);
    }

    // Test that metadata of a symbolic link is correct.
    check_metadata(bytes, &symlink_path).unwrap();
    // Test that the metadata of a symbolic link is correct when not following it.
    assert!(symlink_path.symlink_metadata().unwrap().file_type().is_symlink());
    // Removing symbolic link should succeed.
    remove_file(&symlink_path).unwrap();

    // Removing file should succeed.
    remove_file(&path).unwrap();
}

fn test_errors() {
    let bytes = b"Hello, World!\n";
    let path = prepare("miri_test_fs_errors.txt");

    // The following tests also check that the `__errno_location()` shim is working properly.
    // Opening a non-existing file should fail with a "not found" error.
    assert_eq!(ErrorKind::NotFound, File::open(&path).unwrap_err().kind());
    // Removing a non-existing file should fail with a "not found" error.
    assert_eq!(ErrorKind::NotFound, remove_file(&path).unwrap_err().kind());
    // Reading the metadata of a non-existing file should fail with a "not found" error.
    assert_eq!(ErrorKind::NotFound, check_metadata(bytes, &path).unwrap_err().kind());
}

fn test_rename() {
    // Renaming a file should succeed.
    let path1 = prepare("miri_test_fs_rename_source.txt");
    let path2 = prepare("miri_test_fs_rename_destination.txt");

    let file = File::create(&path1).unwrap();
    drop(file);

    // Renaming should succeed
    rename(&path1, &path2).unwrap();
    // Check that the old file path isn't present
    assert_eq!(ErrorKind::NotFound, path1.metadata().unwrap_err().kind());
    // Check that the file has moved successfully
    assert!(path2.metadata().unwrap().is_file());

    // Renaming a nonexistent file should fail
    assert_eq!(ErrorKind::NotFound, rename(&path1, &path2).unwrap_err().kind());

    remove_file(&path2).unwrap();
}

fn test_directory() {
    let dir_path = prepare_dir("miri_test_fs_dir");
    // Creating a directory should succeed.
    create_dir(&dir_path).unwrap();
    // Test that the metadata of a directory is correct.
    assert!(dir_path.metadata().unwrap().is_dir());
    // Creating a directory when it already exists should fail.
    assert_eq!(ErrorKind::AlreadyExists, create_dir(&dir_path).unwrap_err().kind());

    // Create some files inside the directory
    let path_1 = dir_path.join("test_file_1");
    drop(File::create(&path_1).unwrap());
    let path_2 = dir_path.join("test_file_2");
    drop(File::create(&path_2).unwrap());
    // Test that the files are present inside the directory
    let dir_iter = read_dir(&dir_path).unwrap();
    let mut file_names = dir_iter.map(|e| e.unwrap().file_name()).collect::<Vec<_>>();
    file_names.sort_unstable();
    assert_eq!(file_names, vec!["test_file_1", "test_file_2"]);
    // Clean up the files in the directory
    remove_file(&path_1).unwrap();
    remove_file(&path_2).unwrap();
    // Now there should be nothing left in the directory.
    let dir_iter = read_dir(&dir_path).unwrap();
    let file_names = dir_iter.map(|e| e.unwrap().file_name()).collect::<Vec<_>>();
    assert!(file_names.is_empty());

    // Deleting the directory should succeed.
    remove_dir(&dir_path).unwrap();
    // Reading the metadata of a non-existent directory should fail with a "not found" error.
    assert_eq!(ErrorKind::NotFound, check_metadata(&[], &dir_path).unwrap_err().kind());
}

fn test_dup_stdout_stderr() {
    let bytes = b"hello dup fd\n";
    unsafe {
        let new_stdout = libc::fcntl(1, libc::F_DUPFD, 0);
        let new_stderr = libc::fcntl(2, libc::F_DUPFD, 0);
        libc::write(new_stdout, bytes.as_ptr() as *const libc::c_void, bytes.len());
        libc::write(new_stderr, bytes.as_ptr() as *const libc::c_void, bytes.len());
    }
}
<commit_msg>Swap order of assertions for easier debugging<commit_after>// ignore-windows: File handling is not implemented yet
// compile-flags: -Zmiri-disable-isolation

#![feature(rustc_private)]

use std::fs::{
    File, create_dir, OpenOptions, read_dir, remove_dir, remove_dir_all, remove_file, rename,
};
use std::ffi::CString;
use std::io::{Read, Write, Error, ErrorKind, Result, Seek, SeekFrom};
use std::path::{PathBuf, Path};

extern crate libc;

fn main() {
    test_file();
    test_file_clone();
    test_file_create_new();
    test_seek();
    test_metadata();
    test_file_set_len();
    test_file_sync();
    test_symlink();
    test_errors();
    test_rename();
    test_directory();
    test_dup_stdout_stderr();
}

fn tmp() -> PathBuf {
    std::env::var("MIRI_TEMP").map(PathBuf::from).unwrap_or_else(|_| std::env::temp_dir())
}

/// Prepare: compute filename and make sure the file does not exist.
fn prepare(filename: &str) -> PathBuf {
    let path = tmp().join(filename);
    // Clean the paths for robustness.
    remove_file(&path).ok();
    path
}

/// Prepare directory: compute directory name and make sure it does not exist.
fn prepare_dir(dirname: &str) -> PathBuf {
    let path = tmp().join(&dirname);
    // Clean the directory for robustness.
    remove_dir_all(&path).ok();
    path
}

/// Prepare like above, and also write some initial content to the file.
fn prepare_with_content(filename: &str, content: &[u8]) -> PathBuf {
    let path = prepare(filename);
    let mut file = File::create(&path).unwrap();
    file.write(content).unwrap();
    path
}

fn test_file() {
    let bytes = b"Hello, World!\n";
    let path = prepare("miri_test_fs_file.txt");

    // Test creating, writing and closing a file (closing is tested when `file` is dropped).
    let mut file = File::create(&path).unwrap();
    // Writing 0 bytes should not change the file contents.
    file.write(&mut []).unwrap();
    assert_eq!(file.metadata().unwrap().len(), 0);

    file.write(bytes).unwrap();
    assert_eq!(file.metadata().unwrap().len(), bytes.len() as u64);
    // Test opening, reading and closing a file.
    let mut file = File::open(&path).unwrap();
    let mut contents = Vec::new();
    // Reading 0 bytes should not move the file pointer.
    file.read(&mut []).unwrap();
    // Reading until EOF should get the whole text.
    file.read_to_end(&mut contents).unwrap();
    assert_eq!(bytes, contents.as_slice());

    // Removing file should succeed.
    remove_file(&path).unwrap();
}

fn test_file_clone() {
    let bytes = b"Hello, World!\n";
    let path = prepare_with_content("miri_test_fs_file_clone.txt", bytes);

    // Cloning a file should be successful.
    let file = File::open(&path).unwrap();
    let mut cloned = file.try_clone().unwrap();
    // Reading from a cloned file should get the same text.
    let mut contents = Vec::new();
    cloned.read_to_end(&mut contents).unwrap();
    assert_eq!(bytes, contents.as_slice());

    // Removing file should succeed.
    remove_file(&path).unwrap();
}

fn test_file_create_new() {
    let path = prepare("miri_test_fs_file_create_new.txt");

    // Creating a new file that doesn't yet exist should succeed.
    OpenOptions::new().write(true).create_new(true).open(&path).unwrap();
    // Creating a new file that already exists should fail.
    assert_eq!(ErrorKind::AlreadyExists, OpenOptions::new().write(true).create_new(true).open(&path).unwrap_err().kind());
    // Optionally creating a new file that already exists should succeed.
    OpenOptions::new().write(true).create(true).open(&path).unwrap();

    // Clean up
    remove_file(&path).unwrap();
}

fn test_seek() {
    let bytes = b"Hello, entire World!\n";
    let path = prepare_with_content("miri_test_fs_seek.txt", bytes);

    let mut file = File::open(&path).unwrap();
    let mut contents = Vec::new();
    file.read_to_end(&mut contents).unwrap();
    assert_eq!(bytes, contents.as_slice());
    // Test that seeking to the beginning and reading until EOF gets the text again.
    file.seek(SeekFrom::Start(0)).unwrap();
    let mut contents = Vec::new();
    file.read_to_end(&mut contents).unwrap();
    assert_eq!(bytes, contents.as_slice());
    // Test seeking relative to the end of the file.
    file.seek(SeekFrom::End(-1)).unwrap();
    let mut contents = Vec::new();
    file.read_to_end(&mut contents).unwrap();
    assert_eq!(&bytes[bytes.len() - 1..], contents.as_slice());
    // Test seeking relative to the current position.
    file.seek(SeekFrom::Start(5)).unwrap();
    file.seek(SeekFrom::Current(-3)).unwrap();
    let mut contents = Vec::new();
    file.read_to_end(&mut contents).unwrap();
    assert_eq!(&bytes[2..], contents.as_slice());

    // Removing file should succeed.
    remove_file(&path).unwrap();
}

fn check_metadata(bytes: &[u8], path: &Path) -> Result<()> {
    // Test that the file metadata is correct.
    let metadata = path.metadata()?;
    // `path` should point to a file.
    assert!(metadata.is_file());
    // The size of the file must be equal to the number of written bytes.
    assert_eq!(bytes.len() as u64, metadata.len());
    Ok(())
}

fn test_metadata() {
    let bytes = b"Hello, meta-World!\n";
    let path = prepare_with_content("miri_test_fs_metadata.txt", bytes);

    // Test that metadata of an absolute path is correct.
    check_metadata(bytes, &path).unwrap();
    // Test that metadata of a relative path is correct.
    std::env::set_current_dir(path.parent().unwrap()).unwrap();
    check_metadata(bytes, Path::new(path.file_name().unwrap())).unwrap();

    // Removing file should succeed.
    remove_file(&path).unwrap();
}

fn test_file_set_len() {
    let bytes = b"Hello, World!\n";
    let path = prepare_with_content("miri_test_fs_set_len.txt", bytes);

    // Test extending the file
    let mut file = OpenOptions::new().read(true).write(true).open(&path).unwrap();
    let bytes_extended = b"Hello, World!\n\x00\x00\x00\x00\x00\x00";
    file.set_len(20).unwrap();
    let mut contents = Vec::new();
    file.read_to_end(&mut contents).unwrap();
    assert_eq!(bytes_extended, contents.as_slice());

    // Test truncating the file
    file.seek(SeekFrom::Start(0)).unwrap();
    file.set_len(10).unwrap();
    let mut contents = Vec::new();
    file.read_to_end(&mut contents).unwrap();
    assert_eq!(&bytes[..10], contents.as_slice());

    // Can't use set_len on a file not opened for writing
    let file = OpenOptions::new().read(true).open(&path).unwrap();
    assert_eq!(ErrorKind::InvalidInput, file.set_len(14).unwrap_err().kind());

    remove_file(&path).unwrap();
}

fn test_file_sync() {
    let bytes = b"Hello, World!\n";
    let path = prepare_with_content("miri_test_fs_sync.txt", bytes);

    // Test that we can call sync_data and sync_all (can't readily test effects of this operation)
    let file = OpenOptions::new().write(true).open(&path).unwrap();
    file.sync_data().unwrap();
    file.sync_all().unwrap();

    // Test that we can call sync_data and sync_all on a file opened for reading.
    let file = File::open(&path).unwrap();
    file.sync_data().unwrap();
    file.sync_all().unwrap();

    remove_file(&path).unwrap();
}

fn test_symlink() {
    let bytes = b"Hello, World!\n";
    let path = prepare_with_content("miri_test_fs_link_target.txt", bytes);
    let symlink_path = prepare("miri_test_fs_symlink.txt");

    // Creating a symbolic link should succeed.
    #[cfg(unix)]
    std::os::unix::fs::symlink(&path, &symlink_path).unwrap();
    #[cfg(windows)]
    std::os::windows::fs::symlink_file(&path, &symlink_path).unwrap();
    // Test that the symbolic link has the same contents as the file.
    let mut symlink_file = File::open(&symlink_path).unwrap();
    let mut contents = Vec::new();
    symlink_file.read_to_end(&mut contents).unwrap();
    assert_eq!(bytes, contents.as_slice());

    #[cfg(unix)]
    {
        use std::os::unix::ffi::OsStrExt;

        let expected_path = path.as_os_str().as_bytes();

        // Test that the expected string gets written to a buffer of proper
        // length, and that a trailing null byte is not written.
        let symlink_c_str = CString::new(symlink_path.as_os_str().as_bytes()).unwrap();
        let symlink_c_ptr = symlink_c_str.as_ptr();

        // Make the buf one byte larger than it needs to be,
        // and check that the last byte is not overwritten.
        let mut large_buf = vec![0xFF; expected_path.len() + 1];
        let res = unsafe { libc::readlink(symlink_c_ptr, large_buf.as_mut_ptr().cast(), large_buf.len()) };
        // Check that the resovled path was properly written into the buf.
        assert_eq!(&large_buf[..(large_buf.len() - 1)], expected_path);
        assert_eq!(large_buf.last(), Some(&0xFF));
        assert_eq!(res, large_buf.len() as isize - 1);

        // Test that the resolved path is truncated if the provided buffer
        // is too small.
        let mut small_buf = [0u8; 2];
        let res = unsafe { libc::readlink(symlink_c_ptr, small_buf.as_mut_ptr().cast(), small_buf.len()) };
        assert_eq!(small_buf, &expected_path[..small_buf.len()]);
        assert_eq!(res, small_buf.len() as isize);

        // Test that we report a proper error for a missing path.
        let bad_path = CString::new("MIRI_MISSING_FILE_NAME").unwrap();
        let res = unsafe { libc::readlink(bad_path.as_ptr(), small_buf.as_mut_ptr().cast(), small_buf.len()) };
        assert_eq!(res, -1);
        assert_eq!(Error::last_os_error().kind(), ErrorKind::NotFound);
    }

    // Test that metadata of a symbolic link is correct.
    check_metadata(bytes, &symlink_path).unwrap();
    // Test that the metadata of a symbolic link is correct when not following it.
    assert!(symlink_path.symlink_metadata().unwrap().file_type().is_symlink());
    // Removing symbolic link should succeed.
    remove_file(&symlink_path).unwrap();

    // Removing file should succeed.
    remove_file(&path).unwrap();
}

fn test_errors() {
    let bytes = b"Hello, World!\n";
    let path = prepare("miri_test_fs_errors.txt");

    // The following tests also check that the `__errno_location()` shim is working properly.
    // Opening a non-existing file should fail with a "not found" error.
    assert_eq!(ErrorKind::NotFound, File::open(&path).unwrap_err().kind());
    // Removing a non-existing file should fail with a "not found" error.
    assert_eq!(ErrorKind::NotFound, remove_file(&path).unwrap_err().kind());
    // Reading the metadata of a non-existing file should fail with a "not found" error.
    assert_eq!(ErrorKind::NotFound, check_metadata(bytes, &path).unwrap_err().kind());
}

fn test_rename() {
    // Renaming a file should succeed.
    let path1 = prepare("miri_test_fs_rename_source.txt");
    let path2 = prepare("miri_test_fs_rename_destination.txt");

    let file = File::create(&path1).unwrap();
    drop(file);

    // Renaming should succeed
    rename(&path1, &path2).unwrap();
    // Check that the old file path isn't present
    assert_eq!(ErrorKind::NotFound, path1.metadata().unwrap_err().kind());
    // Check that the file has moved successfully
    assert!(path2.metadata().unwrap().is_file());

    // Renaming a nonexistent file should fail
    assert_eq!(ErrorKind::NotFound, rename(&path1, &path2).unwrap_err().kind());

    remove_file(&path2).unwrap();
}

fn test_directory() {
    let dir_path = prepare_dir("miri_test_fs_dir");
    // Creating a directory should succeed.
    create_dir(&dir_path).unwrap();
    // Test that the metadata of a directory is correct.
    assert!(dir_path.metadata().unwrap().is_dir());
    // Creating a directory when it already exists should fail.
    assert_eq!(ErrorKind::AlreadyExists, create_dir(&dir_path).unwrap_err().kind());

    // Create some files inside the directory
    let path_1 = dir_path.join("test_file_1");
    drop(File::create(&path_1).unwrap());
    let path_2 = dir_path.join("test_file_2");
    drop(File::create(&path_2).unwrap());
    // Test that the files are present inside the directory
    let dir_iter = read_dir(&dir_path).unwrap();
    let mut file_names = dir_iter.map(|e| e.unwrap().file_name()).collect::<Vec<_>>();
    file_names.sort_unstable();
    assert_eq!(file_names, vec!["test_file_1", "test_file_2"]);
    // Clean up the files in the directory
    remove_file(&path_1).unwrap();
    remove_file(&path_2).unwrap();
    // Now there should be nothing left in the directory.
    let dir_iter = read_dir(&dir_path).unwrap();
    let file_names = dir_iter.map(|e| e.unwrap().file_name()).collect::<Vec<_>>();
    assert!(file_names.is_empty());

    // Deleting the directory should succeed.
    remove_dir(&dir_path).unwrap();
    // Reading the metadata of a non-existent directory should fail with a "not found" error.
    assert_eq!(ErrorKind::NotFound, check_metadata(&[], &dir_path).unwrap_err().kind());
}

fn test_dup_stdout_stderr() {
    let bytes = b"hello dup fd\n";
    unsafe {
        let new_stdout = libc::fcntl(1, libc::F_DUPFD, 0);
        let new_stderr = libc::fcntl(2, libc::F_DUPFD, 0);
        libc::write(new_stdout, bytes.as_ptr() as *const libc::c_void, bytes.len());
        libc::write(new_stderr, bytes.as_ptr() as *const libc::c_void, bytes.len());
    }
}
<|endoftext|>


<commit_before>use gfx;
use core::color;

pub type Rgba = color::Rgba<f32>;
pub type Float4 = [f32; 4];
pub type Float = f32;
pub type RenderColorChannels = gfx::format::R16_G16_B16_A16;
pub type RenderColorFormat = (RenderColorChannels, gfx::format::Float);
pub type RenderDepthFormat = gfx::format::Depth;

pub type ScreenColorChannels = gfx::format::R8_G8_B8_A8; // Srgba8 broken on Linux
pub type ScreenColorFormat = (ScreenColorChannels, gfx::format::Unorm); // Srgba8 broken on Linux
pub type ScreenDepthFormat = gfx::format::Depth;

pub type RenderSurface<R> = (gfx::handle::Texture<R, RenderColorChannels>,
                             gfx::handle::ShaderResourceView<R, Float4>,
                             gfx::handle::RenderTargetView<R, RenderColorFormat>);

pub type DepthSurface<R> = (gfx::handle::Texture<R, gfx::format::D24>,
                            gfx::handle::ShaderResourceView<R, Float>,
                            gfx::handle::DepthStencilView<R, RenderDepthFormat>);

pub type RenderSurfaceWithDepth<R> = (gfx::handle::ShaderResourceView<R, Float4>,
                                      gfx::handle::RenderTargetView<R, RenderColorFormat>,
                                      gfx::handle::DepthStencilView<R, RenderDepthFormat>);

pub const MSAA_MODE: gfx::texture::AaMode = gfx::texture::AaMode::Multi(4);
<commit_msg>Reformat<commit_after>use gfx;
use core::color;

pub type Rgba = color::Rgba<f32>;
pub type Float4 = [f32; 4];
pub type Float = f32;
pub type RenderColorChannels = gfx::format::R16_G16_B16_A16;
pub type RenderColorFormat = (RenderColorChannels, gfx::format::Float);
pub type RenderDepthFormat = gfx::format::Depth;

pub type ScreenColorChannels = gfx::format::R8_G8_B8_A8;
// Srgba8 broken on Linux
pub type ScreenColorFormat = (ScreenColorChannels, gfx::format::Unorm);
// Srgba8 broken on Linux
pub type ScreenDepthFormat = gfx::format::Depth;

pub type RenderSurface<R> = (gfx::handle::Texture<R, RenderColorChannels>,
							 gfx::handle::ShaderResourceView<R, Float4>,
							 gfx::handle::RenderTargetView<R, RenderColorFormat>);

pub type DepthSurface<R> = (gfx::handle::Texture<R, gfx::format::D24>,
							gfx::handle::ShaderResourceView<R, Float>,
							gfx::handle::DepthStencilView<R, RenderDepthFormat>);

pub type RenderSurfaceWithDepth<R> = (gfx::handle::ShaderResourceView<R, Float4>,
									  gfx::handle::RenderTargetView<R, RenderColorFormat>,
									  gfx::handle::DepthStencilView<R, RenderDepthFormat>);

pub const MSAA_MODE: gfx::texture::AaMode = gfx::texture::AaMode::Multi(4);
<|endoftext|>


<commit_before>//! online reinforcement q learner (kind of double q and replay buffer learning)
#![allow(dead_code)]

extern crate rand;
extern crate nn;

use std::fs::File;
use std::io::{BufReader, BufWriter};
use std::io::prelude::*;
use self::rand::Rng;
use self::nn::{NN, HaltCondition};
use super::Player;
use super::super::field::Field;

const GAMMA:f64 = 0.99; //q gamma (action-reward time difference high) //1.0?
const LR:f64 = 0.5; //neural net learning rate
const LR_DECAY:f64 = 250000f64; //NN learning rate decrease (half every DECAY games)
const LR_MIN:f64 = 0.1; //minimum NN LR
const MOM:f64 = 0.1; //neural net momentum
const EPOCHS_PER_STEP:u32 = 1; //epochs to learn from each turn
const RND_PICK_START:f64 = 0.5f64; //exploration factor start
const RND_PICK_DEC:f64 = 250000f64; //random exploration decrease (half every DEC games)
const RND_PICK_MIN:f64 = 0.05f64; //exploration rate minimum

pub struct PlayerAIQ
{
	initialized: bool,
	fixed: bool, //should the agent learn or not (fixed => dont learn)
	filename: String,
	pid: i32, //player ID
	nn: Option<NN>, //online network
	targetnn: Option<NN>, //target network (temporarely fixed value network)
	games_played: u32,
	lr: f64,
	exploration: f64,
	startp: f64, //for NN input (1 = self starting, -1 = enemy starting)
	memstate: Vec<f64>, //memorize state learning next turn
	memqval: Vec<f64>, //same
	memreward: f64, //same
	memplay: u32, //same
}

impl PlayerAIQ
{
	pub fn new(fix:bool) -> Box<PlayerAIQ>
	{
		Box::new(PlayerAIQ { initialized: false, fixed: fix, filename: String::new(), pid: 0,
				nn: None, targetnn: None, games_played: 0, lr: LR, exploration: RND_PICK_START,
				startp: 0.0, memstate: Vec::new(), memqval: Vec::new(), memreward: -1.0, memplay: 0 })
	}

	fn get_exploration(&self) -> f64
	{
		RND_PICK_MIN.max(RND_PICK_START * (2f64).powf(-(self.games_played as f64)/RND_PICK_DEC))
	}

	fn get_lr(&self) -> f64
	{
		LR_MIN.max(LR * (2f64).powf(-(self.games_played as f64)/LR_DECAY))
	}

	fn argmax(slice:&[f64]) -> u32
	{
		let mut x:u32 = 0;
		let mut max = slice[0];
		for i in 1..slice.len()
		{
			if max<slice[i]
			{
				x = i as u32;
				max = slice[i];
			}
		}
		x
	}

	fn field_to_input(field:&mut Field, p:i32, startp:f64) -> Vec<f64>
	{
		let op:i32 = if p == 1 { 2 } else { 1 }; //other player
		let mut input:Vec<f64> = Vec::with_capacity((2*field.get_size() + field.get_w() + 1) as usize);
		for (i, val) in field.get_field().iter().enumerate()
		{ //2 nodes for every square: -1 enemy, 0 free, 1 own; 0 square will not be reached with one move, 1 square can be directly filled
			if *val == p { input.push(1f64); input.push(0f64); }
			else if *val == op { input.push(-1f64); input.push(0f64); }
			else
			{ //empty square
				input.push(0f64);
				if (i as u32) < (field.get_size()-field.get_w()) { input.push(if field.get_field()[i+field.get_w() as usize] != 0 { 1f64 } else { 0f64 }); }
				else { input.push(1f64); }
			}
		}
		for x in 0..field.get_w()
		{ //1 node for every column: 1 own win, -1 enemy win, 0 none (which consistent order of the nodes does not matter, fully connected)
			if field.play(p, x)
			{ //valid play
				match field.get_state()
				{
					-1 | 0 => input.push(0f64),
					pid => input.push(if pid == p {1f64} else {-1f64}),
				}
				field.undo();
			}
			else { input.push(0f64); } //illegal move, nobody can win
		}
		//1 node for starting player (-1 enemy, 1 self)
		input.push(startp);
		//return
		input
	}
}

impl Player for PlayerAIQ
{
	fn init(&mut self, field:&Field, p:i32) -> bool
	{
		self.pid = p;

		self.filename = format!("AIQ-{}x{}.NN", field.get_w(), field.get_h());
		let file = File::open(&self.filename);
		if file.is_err()
		{
			//create new neural net, as it could not be loaded
			let n = field.get_size();
			let w = field.get_w();
			self.nn = Some(NN::new(&[2*n+w+1, 4*n, n, n/2, w])); //set size of NN layers here
			//games_played, exploration, lr already set
		}
		else
		{
			//load neural net from file (and games played)
			let mut reader = BufReader::new(file.unwrap());
			let mut datas = String::new();
			let mut nns = String::new();

			let res1 = reader.read_line(&mut datas);
			let res2 = reader.read_to_string(&mut nns);
			if res1.is_err() || res2.is_err() { return false; }

			let res = datas.trim().parse::<u32>();
			if res.is_err() { return false; }
			self.games_played = res.unwrap();
			self.nn = Some(NN::from_json(&nns));

			self.lr = self.get_lr();
			self.exploration = self.get_exploration();
		}

		self.targetnn = self.nn.clone();
		self.initialized = true;
		true
	}

	fn startp(&mut self, p:i32)
	{
		if p == self.pid
		{
			self.startp = 1.0;
		}
		else
		{
			self.startp = -1.0;
		}
	}

	fn play(&mut self, field:&mut Field) -> bool
	{
		if !self.initialized { return false; }
		//variables
		let mut rng = rand::thread_rng();
		let nn = self.nn.as_mut().unwrap();
		let targetnn = self.targetnn.as_mut().unwrap();

		//get current state formatted for the neural net
		let state = PlayerAIQ::field_to_input(field, self.pid, self.startp);

		//learn if not first move (reward is already set, won/loose would be outcome) and NN not fixed
		if !self.fixed && self.memreward != -1.0
		{
			//get Q values for next state
			let qval2 = targetnn.run(&state); //use double q learning target nn, to decouple action and value a bit
			let max = qval2[PlayerAIQ::argmax(&qval2) as usize];
			//calculate q update
			self.memqval[self.memplay as usize] = (self.memreward + GAMMA * max) / (1.0 + GAMMA); //Q learning (divide to stay in [0,1] for sigmoid)
			//train on the latest experience (q update)
			nn.train(&[(self.memstate.clone(), self.memqval.clone())])
				.halt_condition(HaltCondition::Epochs(EPOCHS_PER_STEP))
				.log_interval(None)
				//.log_interval(Some(2)) //debug
				.momentum(MOM)
				.rate(self.lr)
				.go();
		}

		//choose action by e-greedy
		self.memqval = nn.run(&state);
		self.memplay = PlayerAIQ::argmax(&self.memqval);
		if !self.fixed && rng.gen::<f64>() < self.exploration //random exploration if agent should learn.
		{
			self.memplay = rng.gen::<u32>() % field.get_w();
		}

		//perform action and set reward
		self.memreward = 0.5;
		let mut res = field.play(self.pid, self.memplay);
		if !res { self.memqval[self.memplay as usize] = 0.1; } //move did not meet the rules, but learn anyway, even if another random move is made

		//random play when it was not rule conform, also modify q-value for it?
		while !res //infinite if it is already draw!
		{
			self.memplay = rng.gen::<u32>() % field.get_w();
			res = field.play(self.pid, self.memplay);
		}

		//save state for next turn
		self.memstate = state;

		//return
		res
	}

	#[allow(unused_variables)]
	fn outcome(&mut self, field:&mut Field, state:i32)
	{
		//learn if not fixed
		if !self.fixed
		{
			let nn = self.nn.as_mut().unwrap();
			let op:i32 = if self.pid == 1 { 2 } else { 1 }; //other player
			//set reward (if draw, reward already set properly)
			if state == self.pid { self.memreward = 1.0; }
			else if state == op { self.memreward = 0.0; }
			//end-values of network should meet reward exactly
			self.memqval[self.memplay as usize] = self.memreward;
			//train NN
			nn.train(&[(self.memstate.clone(), self.memqval.clone())])
				.halt_condition(HaltCondition::Epochs(EPOCHS_PER_STEP))
				.log_interval(None)
				//.log_interval(Some(2)) //debug
				.momentum(MOM)
				.rate(self.lr)
				.go();
		}

		//parameters
		self.games_played += 1;
		self.lr = self.get_lr();
		self.exploration = self.get_exploration();
		self.targetnn = self.nn.clone();
		self.memreward = -1.0;
	}
}

impl Drop for PlayerAIQ
{
	fn drop(&mut self)
	{
		//write neural net to file, if it may has learned and was initialized
		if self.initialized && !self.fixed
		{
			let file = File::create(&self.filename);
			if file.is_err() { println!("Warning: Could not write AIQ NN file!"); return; }
			let mut writer = BufWriter::new(file.unwrap());

			let res1 = writeln!(&mut writer, "{}", self.games_played);
			let res2 = write!(&mut writer, "{}", self.nn.as_mut().unwrap().to_json());
			if res1.is_err() || res2.is_err() { println!("Warning: There was an error while writing AIQ NN file!"); return; }
		}
	}
}
<commit_msg>factor and NN change<commit_after>//! online reinforcement q learner (kind of double q and replay buffer learning)
#![allow(dead_code)]

extern crate rand;
extern crate nn;

use std::fs::File;
use std::io::{BufReader, BufWriter};
use std::io::prelude::*;
use self::rand::Rng;
use self::nn::{NN, HaltCondition};
use super::Player;
use super::super::field::Field;

const GAMMA:f64 = 0.99; //q gamma (action-reward time difference high) //1.0?
const LR:f64 = 0.5; //neural net learning rate
const LR_DECAY:f64 = 500000f64; //NN learning rate decrease (half every DECAY games)
const LR_MIN:f64 = 0.1; //minimum NN LR
const MOM:f64 = 0.1; //neural net momentum
const EPOCHS_PER_STEP:u32 = 1; //epochs to learn from each turn
const RND_PICK_START:f64 = 0.5f64; //exploration factor start
const RND_PICK_DEC:f64 = 500000f64; //random exploration decrease (half every DEC games)
const RND_PICK_MIN:f64 = 0.05f64; //exploration rate minimum

pub struct PlayerAIQ
{
	initialized: bool,
	fixed: bool, //should the agent learn or not (fixed => dont learn)
	filename: String,
	pid: i32, //player ID
	nn: Option<NN>, //online network
	targetnn: Option<NN>, //target network (temporarely fixed value network)
	games_played: u32,
	lr: f64,
	exploration: f64,
	startp: f64, //for NN input (1 = self starting, -1 = enemy starting)
	memstate: Vec<f64>, //memorize state learning next turn
	memqval: Vec<f64>, //same
	memreward: f64, //same
	memplay: u32, //same
}

impl PlayerAIQ
{
	pub fn new(fix:bool) -> Box<PlayerAIQ>
	{
		Box::new(PlayerAIQ { initialized: false, fixed: fix, filename: String::new(), pid: 0,
				nn: None, targetnn: None, games_played: 0, lr: LR, exploration: RND_PICK_START,
				startp: 0.0, memstate: Vec::new(), memqval: Vec::new(), memreward: -1.0, memplay: 0 })
	}

	fn get_exploration(&self) -> f64
	{
		RND_PICK_MIN.max(RND_PICK_START * (2f64).powf(-(self.games_played as f64)/RND_PICK_DEC))
	}

	fn get_lr(&self) -> f64
	{
		LR_MIN.max(LR * (2f64).powf(-(self.games_played as f64)/LR_DECAY))
	}

	fn argmax(slice:&[f64]) -> u32
	{
		let mut x:u32 = 0;
		let mut max = slice[0];
		for i in 1..slice.len()
		{
			if max<slice[i]
			{
				x = i as u32;
				max = slice[i];
			}
		}
		x
	}

	fn field_to_input(field:&mut Field, p:i32, startp:f64) -> Vec<f64>
	{
		let op:i32 = if p == 1 { 2 } else { 1 }; //other player
		let mut input:Vec<f64> = Vec::with_capacity((2*field.get_size() + field.get_w() + 1) as usize);
		for (i, val) in field.get_field().iter().enumerate()
		{ //2 nodes for every square: -1 enemy, 0 free, 1 own; 0 square will not be reached with one move, 1 square can be directly filled
			if *val == p { input.push(1f64); input.push(0f64); }
			else if *val == op { input.push(-1f64); input.push(0f64); }
			else
			{ //empty square
				input.push(0f64);
				if (i as u32) < (field.get_size()-field.get_w()) { input.push(if field.get_field()[i+field.get_w() as usize] != 0 { 1f64 } else { 0f64 }); }
				else { input.push(1f64); }
			}
		}
		for x in 0..field.get_w()
		{ //1 node for every column: 1 own win, -1 enemy win, 0 none (which consistent order of the nodes does not matter, fully connected)
			if field.play(p, x)
			{ //valid play
				match field.get_state()
				{
					-1 | 0 => input.push(0f64),
					pid => input.push(if pid == p {1f64} else {-1f64}),
				}
				field.undo();
			}
			else { input.push(0f64); } //illegal move, nobody can win
		}
		//1 node for starting player (-1 enemy, 1 self)
		input.push(startp);
		//return
		input
	}
}

impl Player for PlayerAIQ
{
	fn init(&mut self, field:&Field, p:i32) -> bool
	{
		self.pid = p;

		self.filename = format!("AIQ-{}x{}.NN", field.get_w(), field.get_h());
		let file = File::open(&self.filename);
		if file.is_err()
		{
			//create new neural net, as it could not be loaded
			let n = field.get_size();
			let w = field.get_w();
			self.nn = Some(NN::new(&[2*n+w+1, 4*n, 2*n, n, n, n/2, w])); //set size of NN layers here
			//games_played, exploration, lr already set
		}
		else
		{
			//load neural net from file (and games played)
			let mut reader = BufReader::new(file.unwrap());
			let mut datas = String::new();
			let mut nns = String::new();

			let res1 = reader.read_line(&mut datas);
			let res2 = reader.read_to_string(&mut nns);
			if res1.is_err() || res2.is_err() { return false; }

			let res = datas.trim().parse::<u32>();
			if res.is_err() { return false; }
			self.games_played = res.unwrap();
			self.nn = Some(NN::from_json(&nns));

			self.lr = self.get_lr();
			self.exploration = self.get_exploration();
		}

		self.targetnn = self.nn.clone();
		self.initialized = true;
		true
	}

	fn startp(&mut self, p:i32)
	{
		if p == self.pid
		{
			self.startp = 1.0;
		}
		else
		{
			self.startp = -1.0;
		}
	}

	fn play(&mut self, field:&mut Field) -> bool
	{
		if !self.initialized { return false; }
		//variables
		let mut rng = rand::thread_rng();
		let nn = self.nn.as_mut().unwrap();
		let targetnn = self.targetnn.as_mut().unwrap();

		//get current state formatted for the neural net
		let state = PlayerAIQ::field_to_input(field, self.pid, self.startp);

		//learn if not first move (reward is already set, won/loose would be outcome) and NN not fixed
		if !self.fixed && self.memreward != -1.0
		{
			//get Q values for next state
			let qval2 = targetnn.run(&state); //use double q learning target nn, to decouple action and value a bit
			let max = qval2[PlayerAIQ::argmax(&qval2) as usize];
			//calculate q update
			self.memqval[self.memplay as usize] = (self.memreward + GAMMA * max) / (1.0 + GAMMA); //Q learning (divide to stay in [0,1] for sigmoid)
			//train on the latest experience (q update)
			nn.train(&[(self.memstate.clone(), self.memqval.clone())])
				.halt_condition(HaltCondition::Epochs(EPOCHS_PER_STEP))
				.log_interval(None)
				//.log_interval(Some(2)) //debug
				.momentum(MOM)
				.rate(self.lr)
				.go();
		}

		//choose action by e-greedy
		self.memqval = nn.run(&state);
		self.memplay = PlayerAIQ::argmax(&self.memqval);
		if !self.fixed && rng.gen::<f64>() < self.exploration //random exploration if agent should learn.
		{
			self.memplay = rng.gen::<u32>() % field.get_w();
		}

		//perform action and set reward
		self.memreward = 0.5;
		let mut res = field.play(self.pid, self.memplay);
		if !res { self.memqval[self.memplay as usize] = 0.1; } //move did not meet the rules, but learn anyway, even if another random move is made

		//random play when it was not rule conform, also modify q-value for it?
		while !res //infinite if it is already draw!
		{
			self.memplay = rng.gen::<u32>() % field.get_w();
			res = field.play(self.pid, self.memplay);
		}

		//save state for next turn
		self.memstate = state;

		//return
		res
	}

	#[allow(unused_variables)]
	fn outcome(&mut self, field:&mut Field, state:i32)
	{
		//learn if not fixed
		if !self.fixed
		{
			let nn = self.nn.as_mut().unwrap();
			let op:i32 = if self.pid == 1 { 2 } else { 1 }; //other player
			//set reward (if draw, reward already set properly)
			if state == self.pid { self.memreward = 1.0; }
			else if state == op { self.memreward = 0.0; }
			//end-values of network should meet reward exactly
			self.memqval[self.memplay as usize] = self.memreward;
			//train NN
			nn.train(&[(self.memstate.clone(), self.memqval.clone())])
				.halt_condition(HaltCondition::Epochs(EPOCHS_PER_STEP))
				.log_interval(None)
				//.log_interval(Some(2)) //debug
				.momentum(MOM)
				.rate(self.lr)
				.go();
		}

		//parameters
		self.games_played += 1;
		self.lr = self.get_lr();
		self.exploration = self.get_exploration();
		self.targetnn = self.nn.clone();
		self.memreward = -1.0;
	}
}

impl Drop for PlayerAIQ
{
	fn drop(&mut self)
	{
		//write neural net to file, if it may has learned and was initialized
		if self.initialized && !self.fixed
		{
			let file = File::create(&self.filename);
			if file.is_err() { println!("Warning: Could not write AIQ NN file!"); return; }
			let mut writer = BufWriter::new(file.unwrap());

			let res1 = writeln!(&mut writer, "{}", self.games_played);
			let res2 = write!(&mut writer, "{}", self.nn.as_mut().unwrap().to_json());
			if res1.is_err() || res2.is_err() { println!("Warning: There was an error while writing AIQ NN file!"); return; }
		}
	}
}
<|endoftext|>


<commit_before>use engine::Game;
use gui::{Console, Key};
use gui::screens::{self, Screen, ScreenChange};
use util::units::Point;

use std::fmt::{self, Display};

#[allow(missing_copy_implementations)]
pub struct MenuScreen {
    menu_items: Vec<MenuItem>,
    selected: usize,
}

#[derive(Debug)]
enum MenuItem {
    StartGame,
    Exit,
}

impl Display for MenuItem {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            MenuItem::StartGame => write!(fmt, "Start Game"),
            MenuItem::Exit => write!(fmt, "Exit Game"),
        }
    }
}

impl MenuScreen {
    pub fn new() -> Box<Screen + 'static> {
        Box::new(
            MenuScreen {
                menu_items: vec![MenuItem::StartGame, MenuItem::Exit],
                selected: 0,
            }
        )
    }
}

impl Screen for MenuScreen {

    #[allow(unused)]
    fn input(&mut self, game: &mut Game, console: &mut Console) -> Option<ScreenChange> {
        if let Some(key) = console.check_for_keypress() {
            match key {
                Key::Up => {
                    if self.selected > 0 { self.selected -= 1; }
                }
                Key::Down => {
                    if self.selected < self.menu_items.len() - 1 { self.selected += 1; }
                }
                Key::Enter => {
                    let action = &self.menu_items[self.selected];
                    match *action {
                        MenuItem::StartGame => return Some(ScreenChange::AddScreen(screens::GameScreen::new())),
                        MenuItem::Exit => return Some(ScreenChange::ExitGame),
                    }
                },
                _ => {}
            }
        }

        None
    }

    #[allow(unused)]
    fn update(&mut self, game: &mut Game, console: &mut Console) -> Option<ScreenChange> {
        return None;
    }

    #[allow(unused)]
    fn render(&mut self, game: &mut Game, console: &mut Console) {
        let logo_loc = Point::new(0, 0);
        console.print_plain(logo_loc + (0, 0), "                                    ");
        console.print_plain(logo_loc + (0, 1), "   ##### ##### #     #  #   # ##### ");
        console.print_plain(logo_loc + (0, 2), "   #   # #     #     #  #  #  #     ");
        console.print_plain(logo_loc + (0, 3), "   #   # #     #     #  # #   #     ");
        console.print_plain(logo_loc + (0, 4), "   ##### ##### #     #  ##    ###   ");
        console.print_plain(logo_loc + (0, 5), "   # #       # #     #  # #   #     ");
        console.print_plain(logo_loc + (0, 6), "   #  #      # #     #  #  #  #     ");
        console.print_plain(logo_loc + (0, 7), "   #   # ##### ##### #  #   # ##### ");
        console.print_plain(logo_loc + (0, 8), "                                    ");

        for (i, menu_item) in self.menu_items.iter().enumerate() {
            console.print_plain(Point::new(20, 20) + (0, i as i32), format!("{}", menu_item).as_slice());
            if self.selected == i {
                console.put_plain(Point::new(20, 20) + (-2, i as i32), '*');
            }
        }
    }
}
<commit_msg>Minor cleanup in Menu Screen<commit_after>use engine::Game;
use gui::{Console, Key};
use gui::screens::{self, Screen, ScreenChange};
use util::units::Point;

use std::fmt;

#[allow(missing_copy_implementations)]
pub struct MenuScreen {
    menu_items: Vec<MenuItem>,
    selected: usize,
}

enum MenuItem {
    StartGame,
    Exit,
}

impl fmt::Display for MenuItem {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            MenuItem::StartGame => write!(fmt, "Start Game"),
            MenuItem::Exit => write!(fmt, "Exit Game"),
        }
    }
}

impl MenuScreen {
    pub fn new() -> Box<Screen + 'static> {
        Box::new(
            MenuScreen {
                menu_items: vec![MenuItem::StartGame, MenuItem::Exit],
                selected: 0,
            }
        )
    }
}

impl Screen for MenuScreen {

    #[allow(unused)]
    fn input(&mut self, game: &mut Game, console: &mut Console) -> Option<ScreenChange> {
        if let Some(key) = console.check_for_keypress() {
            match key {
                Key::Up => {
                    if self.selected > 0 { self.selected -= 1; }
                }
                Key::Down => {
                    if self.selected < self.menu_items.len() - 1 { self.selected += 1; }
                }
                Key::Enter => {
                    let action = &self.menu_items[self.selected];
                    match *action {
                        MenuItem::StartGame => return Some(ScreenChange::AddScreen(screens::GameScreen::new())),
                        MenuItem::Exit => return Some(ScreenChange::ExitGame),
                    }
                },
                _ => {}
            }
        }

        None
    }

    #[allow(unused)]
    fn update(&mut self, game: &mut Game, console: &mut Console) -> Option<ScreenChange> {
        None
    }

    #[allow(unused)]
    fn render(&mut self, game: &mut Game, console: &mut Console) {
        let logo_loc = Point::new(0, 0);
        console.print_plain(logo_loc + (0, 0), "                                    ");
        console.print_plain(logo_loc + (0, 1), "   ##### ##### #     #  #   # ##### ");
        console.print_plain(logo_loc + (0, 2), "   #   # #     #     #  #  #  #     ");
        console.print_plain(logo_loc + (0, 3), "   #   # #     #     #  # #   #     ");
        console.print_plain(logo_loc + (0, 4), "   ##### ##### #     #  ##    ###   ");
        console.print_plain(logo_loc + (0, 5), "   # #       # #     #  # #   #     ");
        console.print_plain(logo_loc + (0, 6), "   #  #      # #     #  #  #  #     ");
        console.print_plain(logo_loc + (0, 7), "   #   # ##### ##### #  #   # ##### ");
        console.print_plain(logo_loc + (0, 8), "                                    ");

        for (i, menu_item) in self.menu_items.iter().enumerate() {
            console.print_plain(Point::new(20, 20) + (0, i as i32), format!("{}", menu_item).as_slice());
            if self.selected == i {
                console.put_plain(Point::new(20, 20) + (-2, i as i32), '*');
            }
        }
    }
}
<|endoftext|>


<commit_before>extern crate wechat;

use wechat::WeChatClient;

const APPID: &'static str = "wxd7aa56e2c7b1f4f1";
const SECRET: &'static str = "2817b66a1d5829847196cf2f96ab2816";

#[test]
fn test_fetch_access_token() {
    let client = WeChatClient::new(APPID, SECRET);
    let access_token = client.fetch_access_token();
    assert!(access_token.is_some());
    assert!(!client.access_token().is_empty());
}

#[test]
fn test_call_api_with_no_access_token_provided() {
    let client = WeChatClient::new(APPID, SECRET);
    let res = client.get("getcallbackip", vec![]);
    let data = match res {
        Ok(data) => data,
        Err(_) => { panic!("Error calling API"); },
    };
    let ip_list = data.find("ip_list").unwrap();
    let ips = ip_list.as_array().unwrap();
    assert!(ips.len() > 0);
}<commit_msg>Add WeChatClient::with_access_token test case<commit_after>extern crate wechat;

use wechat::WeChatClient;

const APPID: &'static str = "wxd7aa56e2c7b1f4f1";
const SECRET: &'static str = "2817b66a1d5829847196cf2f96ab2816";

#[test]
fn test_fetch_access_token() {
    let client = WeChatClient::new(APPID, SECRET);
    let access_token = client.fetch_access_token();
    assert!(access_token.is_some());
    assert!(!client.access_token().is_empty());
}

#[test]
fn test_call_api_with_no_access_token_provided() {
    let client = WeChatClient::new(APPID, SECRET);
    let res = client.get("getcallbackip", vec![]);
    let data = match res {
        Ok(data) => data,
        Err(_) => { panic!("Error calling API"); },
    };
    let ip_list = data.find("ip_list").unwrap();
    let ips = ip_list.as_array().unwrap();
    assert!(ips.len() > 0);
}

#[test]
fn test_call_api_with_access_token_provided() {
    let client0 = WeChatClient::new(APPID, SECRET);
    let access_token = client0.fetch_access_token();

    let client = WeChatClient::with_access_token(APPID, SECRET, &access_token.unwrap());
    let res = client.get("getcallbackip", vec![]);
    let data = match res {
        Ok(data) => data,
        Err(_) => { panic!("Error calling API"); },
    };
    let ip_list = data.find("ip_list").unwrap();
    let ips = ip_list.as_array().unwrap();
    assert!(ips.len() > 0);
}<|endoftext|>


<commit_before>use crate::clean;
use crate::docfs::PathError;
use crate::error::Error;
use crate::fold::DocFolder;
use crate::html::format::Buffer;
use crate::html::highlight;
use crate::html::layout;
use crate::html::render::{SharedContext, BASIC_KEYWORDS};
use rustc_hir::def_id::LOCAL_CRATE;
use rustc_span::source_map::FileName;
use std::ffi::OsStr;
use std::fs;
use std::path::{Component, Path, PathBuf};

crate fn render(
    dst: &Path,
    scx: &mut SharedContext,
    krate: clean::Crate,
) -> Result<clean::Crate, Error> {
    info!("emitting source files");
    let dst = dst.join("src").join(&krate.name);
    scx.ensure_dir(&dst)?;
    let mut folder = SourceCollector { dst, scx };
    Ok(folder.fold_crate(krate))
}

/// Helper struct to render all source code to HTML pages
struct SourceCollector<'a> {
    scx: &'a mut SharedContext,

    /// Root destination to place all HTML output into
    dst: PathBuf,
}

impl<'a> DocFolder for SourceCollector<'a> {
    fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> {
        // If we're including source files, and we haven't seen this file yet,
        // then we need to render it out to the filesystem.
        if self.scx.include_sources
            // skip all synthetic "files"
            && item.source.filename.is_real()
            // skip non-local files
            && item.source.cnum == LOCAL_CRATE
        {
            // If it turns out that we couldn't read this file, then we probably
            // can't read any of the files (generating html output from json or
            // something like that), so just don't include sources for the
            // entire crate. The other option is maintaining this mapping on a
            // per-file basis, but that's probably not worth it...
            self.scx.include_sources = match self.emit_source(&item.source.filename) {
                Ok(()) => true,
                Err(e) => {
                    println!(
                        "warning: source code was requested to be rendered, \
                         but processing `{}` had an error: {}",
                        item.source.filename, e
                    );
                    println!("         skipping rendering of source code");
                    false
                }
            };
        }
        self.fold_item_recur(item)
    }
}

impl<'a> SourceCollector<'a> {
    /// Renders the given filename into its corresponding HTML source file.
    fn emit_source(&mut self, filename: &FileName) -> Result<(), Error> {
        let p = match *filename {
            FileName::Real(ref file) => file.local_path().to_path_buf(),
            _ => return Ok(()),
        };
        if self.scx.local_sources.contains_key(&*p) {
            // We've already emitted this source
            return Ok(());
        }

        let mut contents = match fs::read_to_string(&p) {
            Ok(contents) => contents,
            Err(e) => {
                return Err(Error::new(e, &p));
            }
        };

        // Remove the utf-8 BOM if any
        if contents.starts_with("\u{feff}") {
            contents.drain(..3);
        }

        // Create the intermediate directories
        let mut cur = self.dst.clone();
        let mut root_path = String::from("../../");
        let mut href = String::new();
        clean_path(&self.scx.src_root, &p, false, |component| {
            cur.push(component);
            root_path.push_str("../");
            href.push_str(&component.to_string_lossy());
            href.push('/');
        });
        self.scx.ensure_dir(&cur)?;
        let mut fname = p.file_name().expect("source has no filename").to_os_string();
        fname.push(".html");
        cur.push(&fname);
        href.push_str(&fname.to_string_lossy());

        let title = format!(
            "{} -- source",
            cur.file_name().expect("failed to get file name").to_string_lossy()
        );
        let desc = format!("Source to the Rust file `{}`.", filename);
        let page = layout::Page {
            title: &title,
            css_class: "source",
            root_path: &root_path,
            static_root_path: self.scx.static_root_path.as_deref(),
            description: &desc,
            keywords: BASIC_KEYWORDS,
            resource_suffix: &self.scx.resource_suffix,
            extra_scripts: &[&format!("source-files{}", self.scx.resource_suffix)],
            static_extra_scripts: &[&format!("source-script{}", self.scx.resource_suffix)],
        };
        let v = layout::render(
            &self.scx.layout,
            &page,
            "",
            |buf: &mut _| print_src(buf, contents),
            &self.scx.style_files,
        );
        self.scx.fs.write(&cur, v.as_bytes())?;
        self.scx.local_sources.insert(p, href);
        Ok(())
    }
}

/// Takes a path to a source file and cleans the path to it. This canonicalizes
/// things like ".." to components which preserve the "top down" hierarchy of a
/// static HTML tree. Each component in the cleaned path will be passed as an
/// argument to `f`. The very last component of the path (ie the file name) will
/// be passed to `f` if `keep_filename` is true, and ignored otherwise.
pub fn clean_path<F>(src_root: &Path, p: &Path, keep_filename: bool, mut f: F)
where
    F: FnMut(&OsStr),
{
    // make it relative, if possible
    let p = p.strip_prefix(src_root).unwrap_or(p);

    let mut iter = p.components().peekable();

    while let Some(c) = iter.next() {
        if !keep_filename && iter.peek().is_none() {
            break;
        }

        match c {
            Component::ParentDir => f("up".as_ref()),
            Component::Normal(c) => f(c),
            _ => continue,
        }
    }
}

/// Wrapper struct to render the source code of a file. This will do things like
/// adding line numbers to the left-hand side.
fn print_src(buf: &mut Buffer, s: String) {
    let lines = s.lines().count();
    let mut cols = 0;
    let mut tmp = lines;
    while tmp > 0 {
        cols += 1;
        tmp /= 10;
    }
    write!(buf, "
");
    for i in 1..=lines {
        write!(buf, "{0:1$}\n", i, cols);
    }
    write!(buf, "
");
    write!(buf, "{}", highlight::render_with_highlighting(s, None, None, None));
}
<commit_msg>rustdoc: Show the correct source filename, without `.html`<commit_after>use crate::clean;
use crate::docfs::PathError;
use crate::error::Error;
use crate::fold::DocFolder;
use crate::html::format::Buffer;
use crate::html::highlight;
use crate::html::layout;
use crate::html::render::{SharedContext, BASIC_KEYWORDS};
use rustc_hir::def_id::LOCAL_CRATE;
use rustc_span::source_map::FileName;
use std::ffi::{OsStr, OsString};
use std::fs;
use std::path::{Component, Path, PathBuf};

crate fn render(
    dst: &Path,
    scx: &mut SharedContext,
    krate: clean::Crate,
) -> Result<clean::Crate, Error> {
    info!("emitting source files");
    let dst = dst.join("src").join(&krate.name);
    scx.ensure_dir(&dst)?;
    let mut folder = SourceCollector { dst, scx };
    Ok(folder.fold_crate(krate))
}

/// Helper struct to render all source code to HTML pages
struct SourceCollector<'a> {
    scx: &'a mut SharedContext,

    /// Root destination to place all HTML output into
    dst: PathBuf,
}

impl<'a> DocFolder for SourceCollector<'a> {
    fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> {
        // If we're including source files, and we haven't seen this file yet,
        // then we need to render it out to the filesystem.
        if self.scx.include_sources
            // skip all synthetic "files"
            && item.source.filename.is_real()
            // skip non-local files
            && item.source.cnum == LOCAL_CRATE
        {
            // If it turns out that we couldn't read this file, then we probably
            // can't read any of the files (generating html output from json or
            // something like that), so just don't include sources for the
            // entire crate. The other option is maintaining this mapping on a
            // per-file basis, but that's probably not worth it...
            self.scx.include_sources = match self.emit_source(&item.source.filename) {
                Ok(()) => true,
                Err(e) => {
                    println!(
                        "warning: source code was requested to be rendered, \
                         but processing `{}` had an error: {}",
                        item.source.filename, e
                    );
                    println!("         skipping rendering of source code");
                    false
                }
            };
        }
        self.fold_item_recur(item)
    }
}

impl<'a> SourceCollector<'a> {
    /// Renders the given filename into its corresponding HTML source file.
    fn emit_source(&mut self, filename: &FileName) -> Result<(), Error> {
        let p = match *filename {
            FileName::Real(ref file) => file.local_path().to_path_buf(),
            _ => return Ok(()),
        };
        if self.scx.local_sources.contains_key(&*p) {
            // We've already emitted this source
            return Ok(());
        }

        let mut contents = match fs::read_to_string(&p) {
            Ok(contents) => contents,
            Err(e) => {
                return Err(Error::new(e, &p));
            }
        };

        // Remove the utf-8 BOM if any
        if contents.starts_with('\u{feff}') {
            contents.drain(..3);
        }

        // Create the intermediate directories
        let mut cur = self.dst.clone();
        let mut root_path = String::from("../../");
        let mut href = String::new();
        clean_path(&self.scx.src_root, &p, false, |component| {
            cur.push(component);
            root_path.push_str("../");
            href.push_str(&component.to_string_lossy());
            href.push('/');
        });
        self.scx.ensure_dir(&cur)?;

        let src_fname =
            String::from(p.file_name().expect("source has no filename").to_string_lossy());
        let fname = OsString::from(src_fname.clone() + ".html");
        cur.push(&fname);
        href.push_str(&fname.to_string_lossy());

        let title = format!("{} – source", src_fname,);
        let desc = format!("Source of the Rust file `{}`.", filename);
        let page = layout::Page {
            title: &title,
            css_class: "source",
            root_path: &root_path,
            static_root_path: self.scx.static_root_path.as_deref(),
            description: &desc,
            keywords: BASIC_KEYWORDS,
            resource_suffix: &self.scx.resource_suffix,
            extra_scripts: &[&format!("source-files{}", self.scx.resource_suffix)],
            static_extra_scripts: &[&format!("source-script{}", self.scx.resource_suffix)],
        };
        let v = layout::render(
            &self.scx.layout,
            &page,
            "",
            |buf: &mut _| print_src(buf, contents),
            &self.scx.style_files,
        );
        self.scx.fs.write(&cur, v.as_bytes())?;
        self.scx.local_sources.insert(p, href);
        Ok(())
    }
}

/// Takes a path to a source file and cleans the path to it. This canonicalizes
/// things like ".." to components which preserve the "top down" hierarchy of a
/// static HTML tree. Each component in the cleaned path will be passed as an
/// argument to `f`. The very last component of the path (ie the file name) will
/// be passed to `f` if `keep_filename` is true, and ignored otherwise.
pub fn clean_path<F>(src_root: &Path, p: &Path, keep_filename: bool, mut f: F)
where
    F: FnMut(&OsStr),
{
    // make it relative, if possible
    let p = p.strip_prefix(src_root).unwrap_or(p);

    let mut iter = p.components().peekable();

    while let Some(c) = iter.next() {
        if !keep_filename && iter.peek().is_none() {
            break;
        }

        match c {
            Component::ParentDir => f("up".as_ref()),
            Component::Normal(c) => f(c),
            _ => continue,
        }
    }
}

/// Wrapper struct to render the source code of a file. This will do things like
/// adding line numbers to the left-hand side.
fn print_src(buf: &mut Buffer, s: String) {
    let lines = s.lines().count();
    let mut cols = 0;
    let mut tmp = lines;
    while tmp > 0 {
        cols += 1;
        tmp /= 10;
    }
    write!(buf, "
");
    for i in 1..=lines {
        write!(buf, "{0:1$}\n", i, cols);
    }
    write!(buf, "
");
    write!(buf, "{}", highlight::render_with_highlighting(s, None, None, None));
}
<|endoftext|>


<commit_before>extern crate chrono;

use self::chrono::{DateTime, Utc};
use std::env;

pub fn read_env_var(var: &str) -> String {
    env::var_os(var)
        .expect(&format!(
            "{} must be specified. \
             Did you forget to add it to your .env file?",
            var
        ))
        .into_string()
        .expect(&format!("{} does not contain a valid UTF8 string", var))
}

pub fn elapsed_ms(from: DateTime<Utc>, to: DateTime<Utc>) -> u64 {
    to.signed_duration_since(from).num_milliseconds() as u64
}

pub fn clamp<'a, T>(val: &'a T, min: &'a T, max: &'a T) -> &'a T
where
    T: Ord
{
    match (*val < *min, *val > *max) {
        (true, _) => min,
        (_, true) => max,
        _ => val
    }
}

#[test]
fn clamp_in_range() {
    let res = clamp(&1, &0, &2);
    assert_eq!(&1, res);
}<commit_msg>Core: Run rustfmt<commit_after>extern crate chrono;

use self::chrono::{DateTime, Utc};
use std::env;

pub fn read_env_var(var: &str) -> String {
    env::var_os(var)
        .expect(&format!(
            "{} must be specified. \
             Did you forget to add it to your .env file?",
            var
        ))
        .into_string()
        .expect(&format!("{} does not contain a valid UTF8 string", var))
}

pub fn elapsed_ms(from: DateTime<Utc>, to: DateTime<Utc>) -> u64 {
    to.signed_duration_since(from).num_milliseconds() as u64
}

pub fn clamp<'a, T>(val: &'a T, min: &'a T, max: &'a T) -> &'a T
where
    T: Ord,
{
    match (*val < *min, *val > *max) {
        (true, _) => min,
        (_, true) => max,
        _ => val,
    }
}

#[test]
fn clamp_in_range() {
    let res = clamp(&1, &0, &2);
    assert_eq!(&1, res);
}
<|endoftext|>


<commit_before>use std::path;

use frontend::render;
use frontend::input::EventMapper;
use frontend::input::GamepadEventLoop;
use frontend::render::{formats, Renderer, Overlay};
use frontend::audio::{self, SoundSystem};
use frontend::ui;
use frontend::gfx_window_glutin;

use conrod;

use core::resource::filesystem::ResourceLoaderBuilder;
use core::math::Directional;
use core::clock::{seconds, SecondsValue, Timer, Hourglass, SystemTimer};
use ctrlc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;

use app;
use winit::{self, WindowEvent, VirtualKeyCode, KeyboardInput};
use glutin;
use glutin::GlContext;

pub fn main_loop(minion_gene_pool: &str, fullscreen: Option<usize>, width: Option<u32>, height: Option<u32>) {
	const WIDTH: u32 = 1280;
	const HEIGHT: u32 = 1024;

	let mut events_loop = winit::EventsLoop::new();
	let mut gamepad = GamepadEventLoop::new();

	let builder = winit::WindowBuilder::new()
		.with_title("Rust-oids".to_string());
	let builder = if let Some(monitor_index) = fullscreen {
		let monitor = events_loop.get_available_monitors().nth(monitor_index).expect("Please enter a valid monitor ID");
		println!("Using {:?}", monitor.get_name());
		builder.with_fullscreen(Some(monitor))
	} else {
		builder.with_dimensions(width.unwrap_or(WIDTH), height.unwrap_or(HEIGHT))
	};
	let context_builder = glutin::ContextBuilder::new()
		.with_vsync(true);

	let (window,
		mut device,
		mut factory,
		mut frame_buffer,
		mut depth_buffer) =
		gfx_window_glutin::init::<formats::ScreenColorFormat, formats::ScreenDepthFormat>(
			builder,
			context_builder,
			&events_loop,
		);

	let (w, h, _, _) = frame_buffer.get_dimensions();

	let mut encoder = factory.create_command_buffer().into();

	let res = ResourceLoaderBuilder::new()
		.add(path::Path::new("resources"))
		.build();

	let renderer = &mut render::ForwardRenderer::new(&mut factory, &mut encoder, &res, &frame_buffer).unwrap();
	let mapper = app::WinitEventMapper::new();

	// Create a new game and run it.
	let mut app = app::App::new(w as u32, h as u32, 100.0, &res, minion_gene_pool);

	let mut ui = ui::conrod_ui::Ui::new(&res,
										&mut factory,
										&frame_buffer, window.hidpi_factor() as f64)
		.expect("Unable to create UI");

	let audio = audio::ThreadedSoundSystem::new().expect("Failure in audio initialization");
	let mut audio_alert_player = audio::ThreadedAlertPlayer::new(audio);
	app.init();

	'main: loop {
		gamepad.poll_events(|event| app.on_input_event(&event));

		events_loop.poll_events(|event| {
			if app.has_ui_overlay() {
				if let Some(event) = conrod::backend::winit::convert_event(event.clone(), window.window()) {
					ui.push_event(event);
				}
			}

			match event {
				winit::Event::WindowEvent { event, .. } => {
					match event {
						WindowEvent::Resized(new_width, new_height) => {
							gfx_window_glutin::update_views(&window, &mut frame_buffer, &mut depth_buffer);
							renderer.resize_to(&frame_buffer)
								.expect("Unable to resize window");
							ui.resize_to(&frame_buffer)
								.expect("Unable to resize window");
							app.on_resize(new_width, new_height);
						}
						WindowEvent::Closed => app.quit(),
						WindowEvent::KeyboardInput {
							input: KeyboardInput { virtual_keycode: Some(VirtualKeyCode::F5), .. }, ..
						} => renderer.rebuild().unwrap(),
						e => {
							mapper.translate(&e).map(|i| app.on_input_event(&i));
						}
					}
				}
				_ => {}
			}
		});

		if !app.is_running() {
			break 'main;
		}

		// update and measure, let the app determine the appropriate frame length
		let frame_update = app.update();

		let camera = render::Camera::ortho(
			app.camera.position(),
			app.viewport.scale,
			app.viewport.ratio,
		);

		let environment = app.environment();

		let light_positions = environment.light_positions.as_ref();
		renderer.setup_frame(
			&camera,
			environment.background_color,
			environment.light_color,
			light_positions,
		);
		// draw a frame
		renderer.begin_frame();
		// draw the scene
		app.render(renderer);
		// post-render effects and tone mapping
		renderer.resolve_frame_buffer();

		if app.has_ui_overlay() {
			let screen = ui::Screen::Main(frame_update);
			renderer.overlay(|_, encoder| {
				ui.update_and_draw_screen(&screen, encoder);
			});
			ui.handle_events();

			for app_event in ui.drain_app_events() {
				app.interact(app_event)
			}
		}

		app.play_alerts(&mut audio_alert_player);
		app.play_interactions(&mut audio_alert_player);

		// push the commands
		renderer.end_frame(&mut device);

		window.swap_buffers().unwrap();
		renderer.cleanup(&mut device);
	};
}

pub fn main_loop_headless(minion_gene_pool: &str) {
	const WIDTH: u32 = 1024;
	const HEIGHT: u32 = 1024;
	let res = ResourceLoaderBuilder::new()
		.add(path::Path::new("resources"))
		.build();

	let mut app = app::App::new(WIDTH, HEIGHT, 100.0, &res, minion_gene_pool);
	let mut no_audio = ui::NullAlertPlayer::new();
	app.init();

	let running = Arc::new(AtomicBool::new(true));
	let r = running.clone();

	ctrlc::set_handler(move || {
		r.store(false, Ordering::SeqCst);
	}).expect("Error setting Ctrl-C handler");

	let wall_clock = SystemTimer::new().shared();
	let mut output_hourglass = Hourglass::new(wall_clock.clone(), seconds(5.0));
	let mut save_hourglass = Hourglass::new(wall_clock.clone(), seconds(300.0));

	const FRAME_SIMULATION_LENGTH: SecondsValue = 1.0 / 60.0;
	'main: loop {
		if !app.is_running() {
			break 'main;
		}

		if !running.load(Ordering::SeqCst) {
			eprintln!("Interrupted, exiting");
			app.dump_to_file();
			break 'main;
		}
// update and measure
		let simulation_update = app.simulate(seconds(FRAME_SIMULATION_LENGTH));
		if save_hourglass.flip_if_expired() {
			app.dump_to_file();
		}

		app.play_alerts(&mut no_audio);
		if output_hourglass.flip_if_expired() {
			info!(
				"C: {} E: {:.3} FT: {:.2} P: {} X: {}",
				simulation_update.count,
				simulation_update.elapsed,
				simulation_update.dt,
				simulation_update.population,
				simulation_update.extinctions
			)
		}
	}
}
<commit_msg>Also changed the "paint" call<commit_after>use std::path;

use frontend::render;
use frontend::input::EventMapper;
use frontend::input::GamepadEventLoop;
use frontend::render::{formats, Renderer, Overlay};
use frontend::audio::{self, SoundSystem};
use frontend::ui;
use frontend::gfx_window_glutin;

use conrod;

use core::resource::filesystem::ResourceLoaderBuilder;
use core::math::Directional;
use core::clock::{seconds, SecondsValue, Timer, Hourglass, SystemTimer};
use ctrlc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;

use app;
use winit::{self, WindowEvent, VirtualKeyCode, KeyboardInput};
use glutin;
use glutin::GlContext;

pub fn main_loop(minion_gene_pool: &str, fullscreen: Option<usize>, width: Option<u32>, height: Option<u32>) {
	const WIDTH: u32 = 1280;
	const HEIGHT: u32 = 1024;

	let mut events_loop = winit::EventsLoop::new();
	let mut gamepad = GamepadEventLoop::new();

	let builder = winit::WindowBuilder::new()
		.with_title("Rust-oids".to_string());
	let builder = if let Some(monitor_index) = fullscreen {
		let monitor = events_loop.get_available_monitors().nth(monitor_index).expect("Please enter a valid monitor ID");
		println!("Using {:?}", monitor.get_name());
		builder.with_fullscreen(Some(monitor))
	} else {
		builder.with_dimensions(width.unwrap_or(WIDTH), height.unwrap_or(HEIGHT))
	};
	let context_builder = glutin::ContextBuilder::new()
		.with_vsync(true);

	let (window,
		mut device,
		mut factory,
		mut frame_buffer,
		mut depth_buffer) =
		gfx_window_glutin::init::<formats::ScreenColorFormat, formats::ScreenDepthFormat>(
			builder,
			context_builder,
			&events_loop,
		);

	let (w, h, _, _) = frame_buffer.get_dimensions();

	let mut encoder = factory.create_command_buffer().into();

	let res = ResourceLoaderBuilder::new()
		.add(path::Path::new("resources"))
		.build();

	let renderer = &mut render::ForwardRenderer::new(&mut factory, &mut encoder, &res, &frame_buffer).unwrap();
	let mapper = app::WinitEventMapper::new();

	// Create a new game and run it.
	let mut app = app::App::new(w as u32, h as u32, 100.0, &res, minion_gene_pool);

	let mut ui = ui::conrod_ui::Ui::new(&res,
										&mut factory,
										&frame_buffer, window.hidpi_factor() as f64)
		.expect("Unable to create UI");

	let audio = audio::ThreadedSoundSystem::new().expect("Failure in audio initialization");
	let mut audio_alert_player = audio::ThreadedAlertPlayer::new(audio);
	app.init();

	'main: loop {
		gamepad.poll_events(|event| app.on_input_event(&event));

		events_loop.poll_events(|event| {
			if app.has_ui_overlay() {
				if let Some(event) = conrod::backend::winit::convert_event(event.clone(), window.window()) {
					ui.push_event(event);
				}
			}

			match event {
				winit::Event::WindowEvent { event, .. } => {
					match event {
						WindowEvent::Resized(new_width, new_height) => {
							gfx_window_glutin::update_views(&window, &mut frame_buffer, &mut depth_buffer);
							renderer.resize_to(&frame_buffer)
								.expect("Unable to resize window");
							ui.resize_to(&frame_buffer)
								.expect("Unable to resize window");
							app.on_resize(new_width, new_height);
						}
						WindowEvent::Closed => app.quit(),
						WindowEvent::KeyboardInput {
							input: KeyboardInput { virtual_keycode: Some(VirtualKeyCode::F5), .. }, ..
						} => renderer.rebuild().unwrap(),
						e => {
							mapper.translate(&e).map(|i| app.on_input_event(&i));
						}
					}
				}
				_ => {}
			}
		});

		if !app.is_running() {
			break 'main;
		}

		// update and measure, let the app determine the appropriate frame length
		let frame_update = app.update();

		let camera = render::Camera::ortho(
			app.camera.position(),
			app.viewport.scale,
			app.viewport.ratio,
		);

		let environment = app.environment();

		let light_positions = environment.light_positions.as_ref();
		renderer.setup_frame(
			&camera,
			environment.background_color,
			environment.light_color,
			light_positions,
		);
		// draw a frame
		renderer.begin_frame();
		// draw the scene
		app.paint(renderer);
		// post-render effects and tone mapping
		renderer.resolve_frame_buffer();

		if app.has_ui_overlay() {
			let screen = ui::Screen::Main(frame_update);
			renderer.overlay(|_, encoder| {
				ui.update_and_draw_screen(&screen, encoder);
			});
			ui.handle_events();

			for app_event in ui.drain_app_events() {
				app.interact(app_event)
			}
		}

		app.play_alerts(&mut audio_alert_player);
		app.play_interactions(&mut audio_alert_player);

		// push the commands
		renderer.end_frame(&mut device);

		window.swap_buffers().unwrap();
		renderer.cleanup(&mut device);
	};
}

pub fn main_loop_headless(minion_gene_pool: &str) {
	const WIDTH: u32 = 1024;
	const HEIGHT: u32 = 1024;
	let res = ResourceLoaderBuilder::new()
		.add(path::Path::new("resources"))
		.build();

	let mut app = app::App::new(WIDTH, HEIGHT, 100.0, &res, minion_gene_pool);
	let mut no_audio = ui::NullAlertPlayer::new();
	app.init();

	let running = Arc::new(AtomicBool::new(true));
	let r = running.clone();

	ctrlc::set_handler(move || {
		r.store(false, Ordering::SeqCst);
	}).expect("Error setting Ctrl-C handler");

	let wall_clock = SystemTimer::new().shared();
	let mut output_hourglass = Hourglass::new(wall_clock.clone(), seconds(5.0));
	let mut save_hourglass = Hourglass::new(wall_clock.clone(), seconds(300.0));

	const FRAME_SIMULATION_LENGTH: SecondsValue = 1.0 / 60.0;
	'main: loop {
		if !app.is_running() {
			break 'main;
		}

		if !running.load(Ordering::SeqCst) {
			eprintln!("Interrupted, exiting");
			app.dump_to_file();
			break 'main;
		}
// update and measure
		let simulation_update = app.simulate(seconds(FRAME_SIMULATION_LENGTH));
		if save_hourglass.flip_if_expired() {
			app.dump_to_file();
		}

		app.play_alerts(&mut no_audio);
		if output_hourglass.flip_if_expired() {
			info!(
				"C: {} E: {:.3} FT: {:.2} P: {} X: {}",
				simulation_update.count,
				simulation_update.elapsed,
				simulation_update.dt,
				simulation_update.population,
				simulation_update.extinctions
			)
		}
	}
}
<|endoftext|>