<commit_before>//! Definition of Thinkerbell scripts.
//!
//! Typical applications will not interact with script
//! objects. Rather, they will use module `parse` to parse a script
//! and module `run` to execute it.

use foxbox_taxonomy::values::*;
use foxbox_taxonomy::selector::*;
use foxbox_taxonomy::services::*;
use foxbox_taxonomy::util::Phantom;

use serde::ser::{ Serialize, Serializer };
use serde::de::{ Deserialize, Deserializer, Error };

/// A thinkerbell script.
#[derive(Serialize, Deserialize)]
pub struct Script<Ctx> where Ctx: Context {
    /// A set of rules, stating what must be done in which circumstance.
    pub rules: Vec<Rule<Ctx>>,

    #[serde(default)]
    #[allow(dead_code)]
    pub phantom: Phantom<Ctx>,
}

/// A single rule, i.e. "when some condition becomes true, do
/// something".
#[derive(Serialize, Deserialize)]
pub struct Rule<Ctx> where Ctx: Context {
    /// The condition in which to execute the trigger. The condition
    /// is matched once *all* the `Match` branches are true. Whenever
    /// `conditions` was false and becomes true, we execute `execute`.
    pub conditions: Vec<Match<Ctx>>,

    /// Stuff to do once `condition` is met.
    pub execute: Vec<Statement<Ctx>>,

    #[serde(default)]
    #[allow(dead_code)]
    pub phantom: Phantom<Ctx>,
}

/// An individual match.
///
/// Matchs always take the form: "data received from getter channel
/// is in given range".
///
/// A condition is true if *any* of the corresponding getter channels
/// yielded a value that is in the given range.
#[derive(Serialize, Deserialize)]
pub struct Match<Ctx> where Ctx: Context {
    /// The set of getters to watch. Note that the set of getters may
    /// change (e.g. when devices are added/removed) without rebooting
    /// the script.
    pub source: Vec<GetterSelector>,

    /// The kind of channel expected from `source`, e.g. "the current
    /// time of day", "is the door opened?", etc. During compilation,
    /// we make sure that we restrict to the elements of `source` that
    /// offer `kind`.
    pub kind: ChannelKind,

    /// The range of values for which the condition is considered met.
    /// During compilation, we check that the type of `range` is
    /// compatible with that of `getter`.
    pub range: Range,

    /// If specified, the values must remain in the `range` for at least
    /// `duration` before the match is considered valid. This is useful
    /// for sensors that may oscillate around a threshold or for detecting
    /// e.g. that a door has been forgotten open.
    #[serde(default)]
    pub duration: Option<Duration>,

    #[serde(default)]
    #[allow(dead_code)]
    pub phantom: Phantom<Ctx>,
}

/// Stuff to actually do. In practice, this means placing calls to devices.
#[derive(Serialize, Deserialize)]
pub struct Statement<Ctx> where Ctx: Context {
    /// The set of setters to which to send a command. Note that the
    /// set of setters may change (e.g. when devices are
    /// added/removed) without rebooting the script.
    pub destination: Vec<SetterSelector>,

    /// Data to send to the resource. During compilation, we check
    /// that the type of `value` is compatible with that of
    /// `destination`.
    pub value: Value,

    /// The kind of channel expected from `destination`, e.g. "close
    /// the door", "set the temperature", etc. During compilation, we
    /// make sure that we restrict to the elements of `destination` that
    /// offer `kind`.
    pub kind: ChannelKind,

    #[serde(default)]
    #[allow(dead_code)]
    pub phantom: Phantom<Ctx>,
}

/// A manner of representing internal nodes.
///
/// Two data structures implement `Context`:
///
/// - `UncheckedCtx`, designed to mark the fact that a script has not
/// been compiled/checked yet and must not be executed;
/// - `compile::CompiledCtx`, designed to mark the fact that a script
/// has been compiled and can be executed.
pub trait Context: Serialize + Deserialize + Default {
}

/// A Context used to represent a script that hasn't been compiled
/// yet.
#[derive(Default, Serialize, Deserialize)]
pub struct UncheckedCtx;
impl Context for UncheckedCtx {
}
<commit_msg>[fxbox/thinkerbell] Removed Serialize from ast<commit_after>//! Definition of Thinkerbell scripts.
//!
//! Typical applications will not interact with script
//! objects. Rather, they will use module `parse` to parse a script
//! and module `run` to execute it.

use foxbox_taxonomy::values::*;
use foxbox_taxonomy::selector::*;
use foxbox_taxonomy::services::*;
use foxbox_taxonomy::util::Phantom;

use serde::de::{ Deserialize, Deserializer, Error };

/// A thinkerbell script.
#[derive(Deserialize)]
pub struct Script<Ctx> where Ctx: Context {
    /// A set of rules, stating what must be done in which circumstance.
    pub rules: Vec<Rule<Ctx>>,

    #[serde(default)]
    #[allow(dead_code)]
    pub phantom: Phantom<Ctx>,
}

/// A single rule, i.e. "when some condition becomes true, do
/// something".
#[derive(Deserialize)]
pub struct Rule<Ctx> where Ctx: Context {
    /// The condition in which to execute the trigger. The condition
    /// is matched once *all* the `Match` branches are true. Whenever
    /// `conditions` was false and becomes true, we execute `execute`.
    pub conditions: Vec<Match<Ctx>>,

    /// Stuff to do once `condition` is met.
    pub execute: Vec<Statement<Ctx>>,

    #[serde(default)]
    #[allow(dead_code)]
    pub phantom: Phantom<Ctx>,
}

/// An individual match.
///
/// Matchs always take the form: "data received from getter channel
/// is in given range".
///
/// A condition is true if *any* of the corresponding getter channels
/// yielded a value that is in the given range.
#[derive(Deserialize)]
pub struct Match<Ctx> where Ctx: Context {
    /// The set of getters to watch. Note that the set of getters may
    /// change (e.g. when devices are added/removed) without rebooting
    /// the script.
    pub source: Vec<GetterSelector>,

    /// The kind of channel expected from `source`, e.g. "the current
    /// time of day", "is the door opened?", etc. During compilation,
    /// we make sure that we restrict to the elements of `source` that
    /// offer `kind`.
    pub kind: ChannelKind,

    /// The range of values for which the condition is considered met.
    /// During compilation, we check that the type of `range` is
    /// compatible with that of `getter`.
    pub range: Range,

    /// If specified, the values must remain in the `range` for at least
    /// `duration` before the match is considered valid. This is useful
    /// for sensors that may oscillate around a threshold or for detecting
    /// e.g. that a door has been forgotten open.
    #[serde(default)]
    pub duration: Option<Duration>,

    #[serde(default)]
    #[allow(dead_code)]
    pub phantom: Phantom<Ctx>,
}

/// Stuff to actually do. In practice, this means placing calls to devices.
#[derive(Deserialize)]
pub struct Statement<Ctx> where Ctx: Context {
    /// The set of setters to which to send a command. Note that the
    /// set of setters may change (e.g. when devices are
    /// added/removed) without rebooting the script.
    pub destination: Vec<SetterSelector>,

    /// Data to send to the resource. During compilation, we check
    /// that the type of `value` is compatible with that of
    /// `destination`.
    pub value: Value,

    /// The kind of channel expected from `destination`, e.g. "close
    /// the door", "set the temperature", etc. During compilation, we
    /// make sure that we restrict to the elements of `destination` that
    /// offer `kind`.
    pub kind: ChannelKind,

    #[serde(default)]
    #[allow(dead_code)]
    pub phantom: Phantom<Ctx>,
}

/// A manner of representing internal nodes.
///
/// Two data structures implement `Context`:
///
/// - `UncheckedCtx`, designed to mark the fact that a script has not
/// been compiled/checked yet and must not be executed;
/// - `compile::CompiledCtx`, designed to mark the fact that a script
/// has been compiled and can be executed.
pub trait Context: Deserialize + Default {
}

/// A Context used to represent a script that hasn't been compiled
/// yet.
#[derive(Default, Deserialize)]
pub struct UncheckedCtx;
impl Context for UncheckedCtx {
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use std::fs::File;
use std::io::{self, Read};
use std::path::PathBuf;

#[cfg(target_os = "android")]
pub fn resources_dir_path() -> PathBuf {
    PathBuf::from("/sdcard/servo/")
}

#[cfg(not(target_os = "android"))]
pub fn resources_dir_path() -> PathBuf {
    use opts;
    use std::env;
    use std::fs::PathExt;

    match opts::get().resources_path {
        Some(ref path) => PathBuf::from(path),
        None => {
            // FIXME: Find a way to not rely on the executable being
            // under `<servo source>/components/servo/target`
            // or `<servo source>/components/servo/target/release`.
            let mut path = env::current_exe().ok().expect("can't get exe path");
            path.pop();
            path.pop();
            path.pop();
            path.pop();
            path.push("resources");
            if !path.is_dir() {  // self_exe_path() is probably in .../target/release
                path.pop();
                path.pop();
                path.push("resources");
            }
            path
        }
    }
}

pub fn read_resource_file(relative_path_components: &[&str]) -> io::Result<Vec<u8>> {
    let mut path = resources_dir_path();
    for component in relative_path_components {
        path.push(component);
    }
    let mut file = try!(File::open(&path));
    let mut data = Vec::new();
    try!(file.read_to_end(&mut data));
    Ok(data)
}
<commit_msg>when finding resource_dir, try exe launch directory<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use std::fs::File;
use std::io::{self, Read};
use std::path::PathBuf;

#[cfg(target_os = "android")]
pub fn resources_dir_path() -> PathBuf {
    PathBuf::from("/sdcard/servo/")
}

#[cfg(not(target_os = "android"))]
pub fn resources_dir_path() -> PathBuf {
    use opts;
    use std::env;
    use std::fs::PathExt;

    match opts::get().resources_path {
        Some(ref path) => PathBuf::from(path),
        None => {
            // FIXME: Find a way to not rely on the executable being
            // under `<servo source>/components/servo/target`
            // or `<servo source>/components/servo/target/release`.
            let mut path = env::current_exe().ok().expect("can't get exe path");
            path.pop();
            path.push("resources");
            if !path.is_dir() {   // resources dir not in same dir as exe?
                path.pop();
                path.pop();
                path.pop();
                path.pop();
                path.push("resources");
                if !path.is_dir() {  // self_exe_path() is probably in .../target/release
                    path.pop();
                    path.pop();
                    path.push("resources");
                    if !path.is_dir() { // self_exe_path() is probably in .../target/release
                        path.pop();
                        path.pop();
                        path.push("resources");
                    }
                }
            }
            path
        }
    }
}

pub fn read_resource_file(relative_path_components: &[&str]) -> io::Result<Vec<u8>> {
    let mut path = resources_dir_path();
    for component in relative_path_components {
        path.push(component);
    }
    let mut file = try!(File::open(&path));
    let mut data = Vec::new();
    try!(file.read_to_end(&mut data));
    Ok(data)
}
<|endoftext|>


<commit_before>use cargo_edit::{Dependency, Manifest};
use curl::{ErrCode, http};
use curl::http::handle::{Method, Request};
use regex::Regex;
use rustc_serialize::json;
use rustc_serialize::json::{BuilderError, Json, Object};
use semver::Version;
use std::env;
use std::path::Path;

const REGISTRY_HOST: &'static str = "https://crates.io";

/// Query latest version from crates.io
///
/// The latest version will be returned as a `Dependency`. This will fail, when
///
/// - there is no Internet connection,
/// - the response from crates.io is an error or in an incorrect format,
/// - or when a crate with the given name does not exist on crates.io.
pub fn get_latest_dependency(crate_name: &str, flag_allow_prerelease: bool) -> Result<Dependency, FetchVersionError> {
    if env::var("CARGO_IS_TEST").is_ok() {
        // We are in a simulated reality. Nothing is real here.
        // FIXME: Use actual test handling code.
        return Ok(Dependency::new(crate_name)
            .set_version(&format!("{}--CURRENT_VERSION_TEST", crate_name)));
    }

    let crate_data = try!(fetch_cratesio(&format!("/crates/{}", crate_name)));
    let crate_json = try!(Json::from_str(&crate_data));

    let dep = try!(read_latest_version(&crate_json, flag_allow_prerelease));

    if dep.name != crate_name {
        println!("WARN: Added `{}` instead of `{}`", dep.name, crate_name);
    }

    Ok(dep)
}

// Checks whether a version object is a stable release
fn version_is_stable(version: &Object) -> bool {
    version.get("num")
        .and_then(Json::as_string)
        .and_then(|s| Version::parse(s).ok())
        .map(|s| !s.is_prerelease())
        .unwrap_or(false)
}

/// Read latest version from JSON structure
///
/// Assumes the version are sorted so that the first non-yanked version is the
/// latest, and thus the one we want.
fn read_latest_version(crate_json: &Json, flag_allow_prerelease: bool) -> Result<Dependency, FetchVersionError> {
    let versions = try!(crate_json.as_object()
        .and_then(|c| c.get("versions"))
        .and_then(Json::as_array)
        .ok_or(FetchVersionError::GetVersion));

    let latest = try!(versions.iter()
        .filter_map(Json::as_object)
        .filter(|&v| flag_allow_prerelease || version_is_stable(v))
        .find(|&v| !v.get("yanked").and_then(Json::as_boolean).unwrap_or(true))
        .ok_or(FetchVersionError::NoneAvailable));

    let name = try!(latest.get("crate")
        .and_then(Json::as_string)
        .map(String::from)
        .ok_or(FetchVersionError::CratesIo(CratesIoError::NotFound)));

    let version = try!(latest.get("num")
        .and_then(Json::as_string)
        .map(String::from)
        .ok_or(FetchVersionError::GetVersion));

    Ok(Dependency::new(&name).set_version(&version))
}

#[test]
fn get_latest_stable_version_from_json() {
    let json = Json::from_str(r#"{
      "versions": [
        {
          "crate": "foo",
          "num": "0.6.0-alpha",
          "yanked": false
        },
        {
          "crate": "foo",
          "num": "0.5.0",
          "yanked": false
        }
      ]
    }"#)
        .unwrap();

    assert_eq!(read_latest_version(json, false).unwrap().version().unwrap(),
               "0.5.0");
}

#[test]
fn get_latest_unstable_or_stable_version_from_json() {
    let json = Json::from_str(r#"{
      "versions": [
        {
          "crate": "foo",
          "num": "0.6.0-alpha",
          "yanked": false
        },
        {
          "crate": "foo",
          "num": "0.5.0",
          "yanked": false
        }
      ]
    }"#)
        .unwrap();

    assert_eq!(read_latest_version(json, true).unwrap().version().unwrap(),
               "0.6.0-alpha");
}

#[test]
fn get_latest_version_from_json_test() {
    let json = Json::from_str(r#"{
      "versions": [
        {
          "crate": "treexml",
          "num": "0.3.1",
          "yanked": true
        },
        {
          "crate": "treexml",
          "num": "0.3.0",
          "yanked": false
        }
      ]
    }"#)
        .unwrap();

    assert_eq!(read_latest_version(json, false).unwrap().version().unwrap(),
               "0.3.0");
}

#[test]
fn get_no_latest_version_from_json_when_all_are_yanked() {
    let json = Json::from_str(r#"{
      "versions": [
        {
          "crate": "treexml",
          "num": "0.3.1",
          "yanked": true
        },
        {
          "crate": "treexml",
          "num": "0.3.0",
          "yanked": true
        }
      ]
    }"#)
        .unwrap();

    assert!(read_latest_version(json, false).is_err());
}

quick_error! {
    #[derive(Debug)]
    pub enum FetchVersionError {
        CratesIo(err: CratesIoError) {
            from()
            description("crates.io Error")
            display("crates.io Error: {}", err)
            cause(err)
        }
        Json(err: BuilderError) {
            from()
            description("JSON Error")
            display("Error parsing JSON: {}", err)
            cause(err)
        }
        GetVersion { description("get version error") }
        NoneAvailable { description("No available versions exist. Either all were yanked or only \
            prerelease versions exist. Trying with the --fetch-prereleases flag might solve \
            the issue.") }
    }
}

// ---
// The following was mostly copied from [1] and is therefore
// (c) 2015 Alex Crichton <alex@alexcrichton.com>
//
// [1]: https://github.com/rust-lang/cargo/blob/bd690d8dff83c7b7714f236a08304ee20732382b/src/crates-io/lib.rs
// ---

fn fetch_cratesio(path: &str) -> Result<String, FetchVersionError> {
    let mut http_handle = http::Handle::new();
    let req = Request::new(&mut http_handle, Method::Get)
        .uri(format!("{}/api/v1{}", REGISTRY_HOST, path))
        .header("Accept", "application/json")
        .content_type("application/json");
    handle_request(req.exec()).map_err(From::from)
}

fn handle_request(response: Result<http::Response, ErrCode>) -> Result<String, CratesIoError> {
    let response = try!(response.map_err(CratesIoError::Curl));
    match response.get_code() {
        0 | 200 => {}
        403 => return Err(CratesIoError::Unauthorized),
        404 => return Err(CratesIoError::NotFound),
        _ => return Err(CratesIoError::NotOkResponse(response)),
    }

    let body = match String::from_utf8(response.move_body()) {
        Ok(body) => body,
        Err(..) => return Err(CratesIoError::NonUtf8Body),
    };

    if let Ok(errors) = json::decode::<ApiErrorList>(&body) {
        return Err(CratesIoError::Api(errors.errors.into_iter().map(|s| s.detail).collect()));
    }

    Ok(body)
}

#[derive(RustcDecodable)]
struct ApiErrorList {
    errors: Vec<ApiError>,
}
#[derive(RustcDecodable)]
struct ApiError {
    detail: String,
}

quick_error! {
    #[derive(Debug)]
    pub enum CratesIoError {
        Curl(e: ErrCode) {}
        NotOkResponse(e: http::Response)  {}
        NonUtf8Body  {}
        Api(e: Vec<String>)  {}
        Unauthorized  {}
        NotFound {}
    }
}

#[cfg_attr(rustfmt, rustfmt_skip)]
quick_error! {
    #[derive(Debug)]
    pub enum FetchGitError {
        FetchGit(err: CratesIoError) {
            from()
            description("fetch error: ")
            display("fetch error: {}", err)
            cause(err)
        }
        ParseRegex { description("parse error: unable to parse git repo url") }
        IncompleteCaptures { description("parse error: the git repo url seems incomplete") }
        LocalCargoToml { description("path error: unable to open Cargo.toml") }
        // RemoteCargoToml { description("path error: unable to open Cargo.toml from the provided repo") }
        ParseCargoToml { description("parse error: unable to parse the external Cargo.toml") }
    }
}

/// Query crate name by accessing a github repo Cargo.toml
///
/// The name will be returned as a string. This will fail, when
///
/// - there is no Internet connection,
/// - Cargo.toml is not present in the root of the master branch,
/// - the response from github is an error or in an incorrect format.
pub fn get_crate_name_from_github(repo: &str) -> Result<String, FetchGitError> {
    let re = Regex::new(r"^https://github.com/([-_0-9a-zA-Z]+)/([-_0-9a-zA-Z]+)(/|.git)?$")
        .unwrap();

    re.captures(repo)
        .ok_or(FetchGitError::ParseRegex)
        .and_then(|cap| {
            match (cap.get(1), cap.get(2)) {
                (Some(user), Some(repo)) => {
                    let url = format!("https://raw.githubusercontent.com/{}/{}/master/Cargo.toml",
                                      user.as_str(),
                                      repo.as_str());

                    let data: Result<Manifest, _> = get_cargo_toml_from_git_url(&url)
                        .and_then(|m| {
                            m.parse()
                                .map_err(|_| FetchGitError::ParseCargoToml)
                        });
                    data.and_then(|ref manifest| get_name_from_manifest(manifest))
                }
                _ => Err(FetchGitError::IncompleteCaptures),
            }
        })
}

/// Query crate name by accessing a gitlab repo Cargo.toml
///
/// The name will be returned as a string. This will fail, when
///
/// - there is no Internet connection,
/// - Cargo.toml is not present in the root of the master branch,
/// - the response from gitlab is an error or in an incorrect format.
pub fn get_crate_name_from_gitlab(repo: &str) -> Result<String, FetchGitError> {
    let re = Regex::new(r"^https://gitlab.com/([-_0-9a-zA-Z]+)/([-_0-9a-zA-Z]+)(/|.git)?$")
        .unwrap();

    re.captures(repo)
        .ok_or(FetchGitError::ParseRegex)
        .and_then(|cap| {
            match (cap.get(1), cap.get(2)) {
                (Some(user), Some(repo)) => {
                    let url = format!("https://gitlab.com/{}/{}/raw/master/Cargo.toml",
                                      user.as_str(),
                                      repo.as_str());

                    let data: Result<Manifest, _> = get_cargo_toml_from_git_url(&url)
                        .and_then(|m| {
                            m.parse()
                                .map_err(|_| FetchGitError::ParseCargoToml)
                        });
                    data.and_then(|ref manifest| get_name_from_manifest(manifest))
                }
                _ => Err(FetchGitError::IncompleteCaptures),
            }
        })
}

/// Query crate name by accessing Cargo.toml in a local path
///
/// The name will be returned as a string. This will fail, when
/// Cargo.toml is not present in the root of the path.
pub fn get_crate_name_from_path(path: &str) -> Result<String, FetchGitError> {
    let cargo_file = Path::new(path).join("Cargo.toml");
    Manifest::open(&cargo_file.to_str())
        .map_err(|_| FetchGitError::LocalCargoToml)
        .and_then(|ref manifest| get_name_from_manifest(manifest))
}

fn get_name_from_manifest(manifest: &Manifest) -> Result<String, FetchGitError> {
    manifest.data
        .get("package")
        .and_then(|m| m.get("name"))
        .and_then(|name| name.as_str().map(|s| s.to_string()))
        .ok_or(FetchGitError::ParseCargoToml)
}

fn get_cargo_toml_from_git_url(url: &str) -> Result<String, FetchGitError> {
    let mut http_handle = http::Handle::new();
    let req = Request::new(&mut http_handle, Method::Get)
        .uri(url)
        .header("Accept", "text/plain")
        .content_type("text/plain");
    handle_request(req.exec()).map_err(From::from)
}
<commit_msg>Fix tests<commit_after>use cargo_edit::{Dependency, Manifest};
use curl::{ErrCode, http};
use curl::http::handle::{Method, Request};
use regex::Regex;
use rustc_serialize::json;
use rustc_serialize::json::{BuilderError, Json, Object};
use semver::Version;
use std::env;
use std::path::Path;

const REGISTRY_HOST: &'static str = "https://crates.io";

/// Query latest version from crates.io
///
/// The latest version will be returned as a `Dependency`. This will fail, when
///
/// - there is no Internet connection,
/// - the response from crates.io is an error or in an incorrect format,
/// - or when a crate with the given name does not exist on crates.io.
pub fn get_latest_dependency(crate_name: &str, flag_allow_prerelease: bool) -> Result<Dependency, FetchVersionError> {
    if env::var("CARGO_IS_TEST").is_ok() {
        // We are in a simulated reality. Nothing is real here.
        // FIXME: Use actual test handling code.
        return Ok(Dependency::new(crate_name)
            .set_version(&format!("{}--CURRENT_VERSION_TEST", crate_name)));
    }

    let crate_data = try!(fetch_cratesio(&format!("/crates/{}", crate_name)));
    let crate_json = try!(Json::from_str(&crate_data));

    let dep = try!(read_latest_version(&crate_json, flag_allow_prerelease));

    if dep.name != crate_name {
        println!("WARN: Added `{}` instead of `{}`", dep.name, crate_name);
    }

    Ok(dep)
}

// Checks whether a version object is a stable release
fn version_is_stable(version: &Object) -> bool {
    version.get("num")
        .and_then(Json::as_string)
        .and_then(|s| Version::parse(s).ok())
        .map(|s| !s.is_prerelease())
        .unwrap_or(false)
}

/// Read latest version from JSON structure
///
/// Assumes the version are sorted so that the first non-yanked version is the
/// latest, and thus the one we want.
fn read_latest_version(crate_json: &Json, flag_allow_prerelease: bool) -> Result<Dependency, FetchVersionError> {
    let versions = try!(crate_json.as_object()
        .and_then(|c| c.get("versions"))
        .and_then(Json::as_array)
        .ok_or(FetchVersionError::GetVersion));

    let latest = try!(versions.iter()
        .filter_map(Json::as_object)
        .filter(|&v| flag_allow_prerelease || version_is_stable(v))
        .find(|&v| !v.get("yanked").and_then(Json::as_boolean).unwrap_or(true))
        .ok_or(FetchVersionError::NoneAvailable));

    let name = try!(latest.get("crate")
        .and_then(Json::as_string)
        .map(String::from)
        .ok_or(FetchVersionError::CratesIo(CratesIoError::NotFound)));

    let version = try!(latest.get("num")
        .and_then(Json::as_string)
        .map(String::from)
        .ok_or(FetchVersionError::GetVersion));

    Ok(Dependency::new(&name).set_version(&version))
}

#[test]
fn get_latest_stable_version_from_json() {
    let json = Json::from_str(r#"{
      "versions": [
        {
          "crate": "foo",
          "num": "0.6.0-alpha",
          "yanked": false
        },
        {
          "crate": "foo",
          "num": "0.5.0",
          "yanked": false
        }
      ]
    }"#)
        .unwrap();

    assert_eq!(read_latest_version(&json, false).unwrap().version().unwrap(),
               "0.5.0");
}

#[test]
fn get_latest_unstable_or_stable_version_from_json() {
    let json = Json::from_str(r#"{
      "versions": [
        {
          "crate": "foo",
          "num": "0.6.0-alpha",
          "yanked": false
        },
        {
          "crate": "foo",
          "num": "0.5.0",
          "yanked": false
        }
      ]
    }"#)
        .unwrap();

    assert_eq!(read_latest_version(&json, true).unwrap().version().unwrap(),
               "0.6.0-alpha");
}

#[test]
fn get_latest_version_from_json_test() {
    let json = Json::from_str(r#"{
      "versions": [
        {
          "crate": "treexml",
          "num": "0.3.1",
          "yanked": true
        },
        {
          "crate": "treexml",
          "num": "0.3.0",
          "yanked": false
        }
      ]
    }"#)
        .unwrap();

    assert_eq!(read_latest_version(&json, false).unwrap().version().unwrap(),
               "0.3.0");
}

#[test]
fn get_no_latest_version_from_json_when_all_are_yanked() {
    let json = Json::from_str(r#"{
      "versions": [
        {
          "crate": "treexml",
          "num": "0.3.1",
          "yanked": true
        },
        {
          "crate": "treexml",
          "num": "0.3.0",
          "yanked": true
        }
      ]
    }"#)
        .unwrap();

    assert!(read_latest_version(&json, false).is_err());
}

quick_error! {
    #[derive(Debug)]
    pub enum FetchVersionError {
        CratesIo(err: CratesIoError) {
            from()
            description("crates.io Error")
            display("crates.io Error: {}", err)
            cause(err)
        }
        Json(err: BuilderError) {
            from()
            description("JSON Error")
            display("Error parsing JSON: {}", err)
            cause(err)
        }
        GetVersion { description("get version error") }
        NoneAvailable { description("No available versions exist. Either all were yanked or only \
            prerelease versions exist. Trying with the --fetch-prereleases flag might solve \
            the issue.") }
    }
}

// ---
// The following was mostly copied from [1] and is therefore
// (c) 2015 Alex Crichton <alex@alexcrichton.com>
//
// [1]: https://github.com/rust-lang/cargo/blob/bd690d8dff83c7b7714f236a08304ee20732382b/src/crates-io/lib.rs
// ---

fn fetch_cratesio(path: &str) -> Result<String, FetchVersionError> {
    let mut http_handle = http::Handle::new();
    let req = Request::new(&mut http_handle, Method::Get)
        .uri(format!("{}/api/v1{}", REGISTRY_HOST, path))
        .header("Accept", "application/json")
        .content_type("application/json");
    handle_request(req.exec()).map_err(From::from)
}

fn handle_request(response: Result<http::Response, ErrCode>) -> Result<String, CratesIoError> {
    let response = try!(response.map_err(CratesIoError::Curl));
    match response.get_code() {
        0 | 200 => {}
        403 => return Err(CratesIoError::Unauthorized),
        404 => return Err(CratesIoError::NotFound),
        _ => return Err(CratesIoError::NotOkResponse(response)),
    }

    let body = match String::from_utf8(response.move_body()) {
        Ok(body) => body,
        Err(..) => return Err(CratesIoError::NonUtf8Body),
    };

    if let Ok(errors) = json::decode::<ApiErrorList>(&body) {
        return Err(CratesIoError::Api(errors.errors.into_iter().map(|s| s.detail).collect()));
    }

    Ok(body)
}

#[derive(RustcDecodable)]
struct ApiErrorList {
    errors: Vec<ApiError>,
}
#[derive(RustcDecodable)]
struct ApiError {
    detail: String,
}

quick_error! {
    #[derive(Debug)]
    pub enum CratesIoError {
        Curl(e: ErrCode) {}
        NotOkResponse(e: http::Response)  {}
        NonUtf8Body  {}
        Api(e: Vec<String>)  {}
        Unauthorized  {}
        NotFound {}
    }
}

#[cfg_attr(rustfmt, rustfmt_skip)]
quick_error! {
    #[derive(Debug)]
    pub enum FetchGitError {
        FetchGit(err: CratesIoError) {
            from()
            description("fetch error: ")
            display("fetch error: {}", err)
            cause(err)
        }
        ParseRegex { description("parse error: unable to parse git repo url") }
        IncompleteCaptures { description("parse error: the git repo url seems incomplete") }
        LocalCargoToml { description("path error: unable to open Cargo.toml") }
        // RemoteCargoToml { description("path error: unable to open Cargo.toml from the provided repo") }
        ParseCargoToml { description("parse error: unable to parse the external Cargo.toml") }
    }
}

/// Query crate name by accessing a github repo Cargo.toml
///
/// The name will be returned as a string. This will fail, when
///
/// - there is no Internet connection,
/// - Cargo.toml is not present in the root of the master branch,
/// - the response from github is an error or in an incorrect format.
pub fn get_crate_name_from_github(repo: &str) -> Result<String, FetchGitError> {
    let re = Regex::new(r"^https://github.com/([-_0-9a-zA-Z]+)/([-_0-9a-zA-Z]+)(/|.git)?$")
        .unwrap();

    re.captures(repo)
        .ok_or(FetchGitError::ParseRegex)
        .and_then(|cap| {
            match (cap.get(1), cap.get(2)) {
                (Some(user), Some(repo)) => {
                    let url = format!("https://raw.githubusercontent.com/{}/{}/master/Cargo.toml",
                                      user.as_str(),
                                      repo.as_str());

                    let data: Result<Manifest, _> = get_cargo_toml_from_git_url(&url)
                        .and_then(|m| {
                            m.parse()
                                .map_err(|_| FetchGitError::ParseCargoToml)
                        });
                    data.and_then(|ref manifest| get_name_from_manifest(manifest))
                }
                _ => Err(FetchGitError::IncompleteCaptures),
            }
        })
}

/// Query crate name by accessing a gitlab repo Cargo.toml
///
/// The name will be returned as a string. This will fail, when
///
/// - there is no Internet connection,
/// - Cargo.toml is not present in the root of the master branch,
/// - the response from gitlab is an error or in an incorrect format.
pub fn get_crate_name_from_gitlab(repo: &str) -> Result<String, FetchGitError> {
    let re = Regex::new(r"^https://gitlab.com/([-_0-9a-zA-Z]+)/([-_0-9a-zA-Z]+)(/|.git)?$")
        .unwrap();

    re.captures(repo)
        .ok_or(FetchGitError::ParseRegex)
        .and_then(|cap| {
            match (cap.get(1), cap.get(2)) {
                (Some(user), Some(repo)) => {
                    let url = format!("https://gitlab.com/{}/{}/raw/master/Cargo.toml",
                                      user.as_str(),
                                      repo.as_str());

                    let data: Result<Manifest, _> = get_cargo_toml_from_git_url(&url)
                        .and_then(|m| {
                            m.parse()
                                .map_err(|_| FetchGitError::ParseCargoToml)
                        });
                    data.and_then(|ref manifest| get_name_from_manifest(manifest))
                }
                _ => Err(FetchGitError::IncompleteCaptures),
            }
        })
}

/// Query crate name by accessing Cargo.toml in a local path
///
/// The name will be returned as a string. This will fail, when
/// Cargo.toml is not present in the root of the path.
pub fn get_crate_name_from_path(path: &str) -> Result<String, FetchGitError> {
    let cargo_file = Path::new(path).join("Cargo.toml");
    Manifest::open(&cargo_file.to_str())
        .map_err(|_| FetchGitError::LocalCargoToml)
        .and_then(|ref manifest| get_name_from_manifest(manifest))
}

fn get_name_from_manifest(manifest: &Manifest) -> Result<String, FetchGitError> {
    manifest.data
        .get("package")
        .and_then(|m| m.get("name"))
        .and_then(|name| name.as_str().map(|s| s.to_string()))
        .ok_or(FetchGitError::ParseCargoToml)
}

fn get_cargo_toml_from_git_url(url: &str) -> Result<String, FetchGitError> {
    let mut http_handle = http::Handle::new();
    let req = Request::new(&mut http_handle, Method::Get)
        .uri(url)
        .header("Accept", "text/plain")
        .content_type("text/plain");
    handle_request(req.exec()).map_err(From::from)
}
<|endoftext|>


<commit_before>mod mmap_directory;
mod ram_directory;
mod directory;
mod read_only_source;
mod shared_vec_slice;
mod managed_directory;

/// Errors specific to the directory module.
pub mod error;

use std::io::{Write, Seek};

use std::io::BufWriter;
pub use self::read_only_source::ReadOnlySource;
pub use self::directory::Directory;
pub use self::ram_directory::RAMDirectory;
pub use self::mmap_directory::MmapDirectory;
pub use self::managed_directory::ManagedDirectory;

/// Synonym of Seek + Write
pub trait SeekableWrite: Seek + Write {}
impl<T: Seek + Write> SeekableWrite for T {}

/// Write object for Directory.
///
/// `WritePtr` are required to implement both Write
/// and Seek.
pub type WritePtr = BufWriter<Box<SeekableWrite>>;

#[cfg(test)]
mod tests {

    use super::*;
    use std::path::Path;
    use std::io::{Write, Seek, SeekFrom};

    lazy_static! {
        static ref TEST_PATH: &'static Path = Path::new("some_path_for_test");
    }

    #[test]
    fn test_ram_directory() {
        let mut ram_directory = RAMDirectory::create();
        test_directory(&mut ram_directory);
    }

    #[test]
    fn test_mmap_directory() {
        let mut mmap_directory = MmapDirectory::create_from_tempdir().unwrap();
        test_directory(&mut mmap_directory);
    }

    #[test]
    #[should_panic]
    fn ram_directory_panics_if_flush_forgotten() {
        let mut ram_directory = RAMDirectory::create();
        let mut write_file = ram_directory.open_write(*TEST_PATH).unwrap();
        assert!(write_file.write_all(&[4]).is_ok());
    }

    fn test_simple(directory: &mut Directory) {
        {
            {
                let mut write_file = directory.open_write(*TEST_PATH).unwrap();
                assert!(directory.exists(*TEST_PATH));
                write_file.write_all(&[4]).unwrap();
                write_file.write_all(&[3]).unwrap();
                write_file.write_all(&[7,3,5]).unwrap();
                write_file.flush().unwrap();
            }
            let read_file = directory.open_read(*TEST_PATH).unwrap();
            let data: &[u8] = &*read_file;
            assert_eq!(data, &[4u8, 3u8, 7u8, 3u8, 5u8]);
        }

        assert!(directory.delete(*TEST_PATH).is_ok());
        assert!(!directory.exists(*TEST_PATH));
    }

    fn test_seek(directory: &mut Directory) {
        {
            {
                let mut write_file = directory.open_write(*TEST_PATH).unwrap();
                write_file.write_all(&[4, 3, 7,3,5]).unwrap();
                write_file.seek(SeekFrom::Start(0)).unwrap();
                write_file.write_all(&[3,1]).unwrap();
                write_file.flush().unwrap();
            }
            let read_file = directory.open_read(*TEST_PATH).unwrap();
            let data: &[u8] = &*read_file;
            assert_eq!(data, &[3u8, 1u8, 7u8, 3u8, 5u8]);
        }

        assert!(directory.delete(*TEST_PATH).is_ok());
    }

    fn test_rewrite_forbidden(directory: &mut Directory) {
        {
            directory.open_write(*TEST_PATH).unwrap();
            assert!(directory.exists(*TEST_PATH));

        }
        {
            assert!(directory.open_write(*TEST_PATH).is_err());
        }
        assert!(directory.delete(*TEST_PATH).is_ok());
    }

    fn test_write_create_the_file(directory: &mut Directory) {
        {
            assert!(directory.open_read(*TEST_PATH).is_err());
            let _w = directory.open_write(*TEST_PATH).unwrap();
            assert!(directory.exists(*TEST_PATH));
            if let Err(e) = directory.open_read(*TEST_PATH) {
                println!("{:?}", e);
            }
            assert!(directory.open_read(*TEST_PATH).is_ok());
            assert!(directory.delete(*TEST_PATH).is_ok());
        }
    }

    fn test_directory_delete(directory: &mut Directory) {
        assert!(directory.open_read(*TEST_PATH).is_err());
        let mut write_file = directory.open_write(*TEST_PATH).unwrap();
        write_file.write_all(&[1, 2, 3, 4]).unwrap();
        write_file.flush().unwrap();
        {
            let read_handle = directory.open_read(*TEST_PATH).unwrap();
            {
                assert_eq!(&*read_handle, &[1u8, 2u8, 3u8, 4u8]);

                // Mapped files can't be deleted on Windows
                if !cfg!(windows) {
                    assert!(directory.delete(*TEST_PATH).is_ok());
                    assert_eq!(&*read_handle, &[1u8, 2u8, 3u8, 4u8]);
                }

                assert!(directory.delete(Path::new("SomeOtherPath")).is_err());
            }
        }

        assert!(directory.delete(*TEST_PATH).is_ok());
        assert!(directory.open_read(*TEST_PATH).is_err());
        assert!(directory.delete(*TEST_PATH).is_err());
    }

    fn test_directory(directory: &mut Directory) {
        test_simple(directory);
        test_seek(directory);
        test_rewrite_forbidden(directory);
        test_write_create_the_file(directory);
        test_directory_delete(directory);
    }

}
<commit_msg>Don't delete twice on not(windows)<commit_after>mod mmap_directory;
mod ram_directory;
mod directory;
mod read_only_source;
mod shared_vec_slice;
mod managed_directory;

/// Errors specific to the directory module.
pub mod error;

use std::io::{Write, Seek};

use std::io::BufWriter;
pub use self::read_only_source::ReadOnlySource;
pub use self::directory::Directory;
pub use self::ram_directory::RAMDirectory;
pub use self::mmap_directory::MmapDirectory;
pub use self::managed_directory::ManagedDirectory;

/// Synonym of Seek + Write
pub trait SeekableWrite: Seek + Write {}
impl<T: Seek + Write> SeekableWrite for T {}

/// Write object for Directory.
///
/// `WritePtr` are required to implement both Write
/// and Seek.
pub type WritePtr = BufWriter<Box<SeekableWrite>>;

#[cfg(test)]
mod tests {

    use super::*;
    use std::path::Path;
    use std::io::{Write, Seek, SeekFrom};

    lazy_static! {
        static ref TEST_PATH: &'static Path = Path::new("some_path_for_test");
    }

    #[test]
    fn test_ram_directory() {
        let mut ram_directory = RAMDirectory::create();
        test_directory(&mut ram_directory);
    }

    #[test]
    fn test_mmap_directory() {
        let mut mmap_directory = MmapDirectory::create_from_tempdir().unwrap();
        test_directory(&mut mmap_directory);
    }

    #[test]
    #[should_panic]
    fn ram_directory_panics_if_flush_forgotten() {
        let mut ram_directory = RAMDirectory::create();
        let mut write_file = ram_directory.open_write(*TEST_PATH).unwrap();
        assert!(write_file.write_all(&[4]).is_ok());
    }

    fn test_simple(directory: &mut Directory) {
        {
            {
                let mut write_file = directory.open_write(*TEST_PATH).unwrap();
                assert!(directory.exists(*TEST_PATH));
                write_file.write_all(&[4]).unwrap();
                write_file.write_all(&[3]).unwrap();
                write_file.write_all(&[7,3,5]).unwrap();
                write_file.flush().unwrap();
            }
            let read_file = directory.open_read(*TEST_PATH).unwrap();
            let data: &[u8] = &*read_file;
            assert_eq!(data, &[4u8, 3u8, 7u8, 3u8, 5u8]);
        }

        assert!(directory.delete(*TEST_PATH).is_ok());
        assert!(!directory.exists(*TEST_PATH));
    }

    fn test_seek(directory: &mut Directory) {
        {
            {
                let mut write_file = directory.open_write(*TEST_PATH).unwrap();
                write_file.write_all(&[4, 3, 7,3,5]).unwrap();
                write_file.seek(SeekFrom::Start(0)).unwrap();
                write_file.write_all(&[3,1]).unwrap();
                write_file.flush().unwrap();
            }
            let read_file = directory.open_read(*TEST_PATH).unwrap();
            let data: &[u8] = &*read_file;
            assert_eq!(data, &[3u8, 1u8, 7u8, 3u8, 5u8]);
        }

        assert!(directory.delete(*TEST_PATH).is_ok());
    }

    fn test_rewrite_forbidden(directory: &mut Directory) {
        {
            directory.open_write(*TEST_PATH).unwrap();
            assert!(directory.exists(*TEST_PATH));

        }
        {
            assert!(directory.open_write(*TEST_PATH).is_err());
        }
        assert!(directory.delete(*TEST_PATH).is_ok());
    }

    fn test_write_create_the_file(directory: &mut Directory) {
        {
            assert!(directory.open_read(*TEST_PATH).is_err());
            let _w = directory.open_write(*TEST_PATH).unwrap();
            assert!(directory.exists(*TEST_PATH));
            if let Err(e) = directory.open_read(*TEST_PATH) {
                println!("{:?}", e);
            }
            assert!(directory.open_read(*TEST_PATH).is_ok());
            assert!(directory.delete(*TEST_PATH).is_ok());
        }
    }

    fn test_directory_delete(directory: &mut Directory) {
        assert!(directory.open_read(*TEST_PATH).is_err());
        let mut write_file = directory.open_write(*TEST_PATH).unwrap();
        write_file.write_all(&[1, 2, 3, 4]).unwrap();
        write_file.flush().unwrap();
        {
            let read_handle = directory.open_read(*TEST_PATH).unwrap();
            {
                assert_eq!(&*read_handle, &[1u8, 2u8, 3u8, 4u8]);

                // Mapped files can't be deleted on Windows
                if !cfg!(windows) {
                    assert!(directory.delete(*TEST_PATH).is_ok());
                    assert_eq!(&*read_handle, &[1u8, 2u8, 3u8, 4u8]);
                }

                assert!(directory.delete(Path::new("SomeOtherPath")).is_err());
            }
        }

        if cfg!(windows) {
            assert!(directory.delete(*TEST_PATH).is_ok());
        }

        assert!(directory.open_read(*TEST_PATH).is_err());
        assert!(directory.delete(*TEST_PATH).is_err());
    }

    fn test_directory(directory: &mut Directory) {
        test_simple(directory);
        test_seek(directory);
        test_rewrite_forbidden(directory);
        test_write_create_the_file(directory);
        test_directory_delete(directory);
    }

}
<|endoftext|>


<commit_before>use internal::{
    CanColor,
    CanRectangle,
    CanSourceRectangle,
    CanTransform,
    Color,
    ColorComponent,
    HasColor,
    HasRectangle,
    HasSourceRectangle,
    HasTransform,
    Rectangle,
    SourceRectangle,
};
use vecmath::{
    hsv,
    margin_rectangle,
    multiply,
    orient,
    relative_rectangle,
    relative_source_rectangle,
    rotate_radians,
    scale,
    shear,
    translate,
    Matrix2d,
    Scalar,
};

/// Implemented by contexts that contains color.
pub trait RelativeColor<T> {
    /// Multiplies with red, green, blue and alpha values.
    fn mul_rgba(
        &self,
        r: ColorComponent,
        g: ColorComponent,
        b: ColorComponent,
        a: ColorComponent
    ) -> T;

    /// Mixes the current color with white.
    ///
    /// 0 is black and 1 is white.
    #[inline(always)]
    fn tint(&self, f: ColorComponent) -> T {
        self.mul_rgba(f, f, f, 1.0)
    }

    /// Mixes the current color with black.
    ///
    /// 0 is white and 1 is black.
    #[inline(always)]
    fn shade(&self, f: ColorComponent) -> T {
        let f = 1.0 - f;
        self.mul_rgba(f, f, f, 1.0)
    }

    /// Rotates hue by degrees.
    #[inline(always)]
    fn hue_deg(&self, angle: ColorComponent) -> T {
        let pi: ColorComponent = Float::pi();
        self.hue_rad(angle * pi / 180.0)
    }

    /// Rotates hue by radians.
    fn hue_rad(&self, angle: ColorComponent) -> T;
}

impl<
    T: HasColor<Color> + CanColor<U, Color>,
    U
> RelativeColor<U> for T {
    #[inline(always)]
    fn mul_rgba(
        &self,
        r: ColorComponent,
        g: ColorComponent,
        b: ColorComponent,
        a: ColorComponent
    ) -> U {
        let color = self.get_color();
        self.color([color[0] * r, color[1] * g, color[2] * b, color[3] * a])
    }

    #[inline(always)]
    fn hue_rad(&self, angle: ColorComponent) -> U {
        self.color(hsv(self.get_color(), angle, 1.0, 1.0))
    }
}

/// Should be implemented by contexts that have rectangle information.
pub trait RelativeRectangle<T> {
    /// Shrinks the current rectangle equally by all sides.
    fn margin(&self, m: f64) -> T;

    /// Expands the current rectangle equally by all sides.
    #[inline(always)]
    fn expand(&self, m: f64) -> T {
        self.margin(-m)
    }

    /// Moves to a relative rectangle using the current rectangle as tile.
    fn rel(&self, x: f64, y: f64) -> T;
}

impl<
    T: HasRectangle<Rectangle> + CanRectangle<U, Rectangle>,
    U
> RelativeRectangle<U> for T {
    #[inline(always)]
    fn margin(&self, m: f64) -> U {
        self.rectangle(margin_rectangle(self.get_rectangle(), m))
    }

    #[inline(always)]
    fn rel(&self, x: f64, y: f64) -> U {
        self.rectangle(relative_rectangle(self.get_rectangle(), x, y))
    }
}

/// Should be implemented by contexts that
/// have source rectangle information.
pub trait RelativeSourceRectangle<T> {
    /// Adds a source rectangle.
    fn src_rect(&self, x: i32, y: i32, w: i32, h: i32) -> T;

    /// Moves to a relative source rectangle using
    /// the current source rectangle as tile.
    fn src_rel(&self, x: i32, y: i32) -> T;

    /// Flips the source rectangle horizontally.
    fn src_flip_h(&self) -> T;

    /// Flips the source rectangle vertically.
    fn src_flip_v(&self) -> T;

    /// Flips the source rectangle horizontally and vertically.
    fn src_flip_hv(&self) -> T;
}

impl<T: HasSourceRectangle<SourceRectangle>
        + CanSourceRectangle<U, SourceRectangle>,
    U
> RelativeSourceRectangle<U> for T {
    #[inline(always)]
    fn src_rel(&self, x: i32, y: i32) -> U {
        self.source_rectangle(
            relative_source_rectangle(self.get_source_rectangle(), x, y)
        )
    }

    #[inline(always)]
    fn src_rect(&self, x: i32, y: i32, w: i32, h: i32) -> U {
        self.source_rectangle([x, y, w, h])
    }

    #[inline(always)]
    fn src_flip_h(&self) -> U {
        let source_rect = self.get_source_rectangle();
        self.source_rectangle([
            source_rect[0] + source_rect[2],
            source_rect[1],
            -source_rect[2],
            source_rect[3]
        ])
    }

    #[inline(always)]
    fn src_flip_v(&self) -> U {
        let source_rect = self.get_source_rectangle();
        self.source_rectangle([
            source_rect[0],
            source_rect[1] + source_rect[3],
            source_rect[2],
            -source_rect[3]
        ])
    }

    #[inline(always)]
    fn src_flip_hv(&self) -> U {
        let source_rect = self.get_source_rectangle();
        self.source_rectangle([
            source_rect[0] + source_rect[2],
            source_rect[1] + source_rect[3],
            -source_rect[2],
            -source_rect[3]
        ])
    }
}

/// Implemented by contexts that can transform.
pub trait RelativeTransform2d {
    /// Translate x an y in local coordinates.
    fn trans(&self, x: Scalar, y: Scalar) -> Self;

    /// Rotates degrees in local coordinates.
    #[inline(always)]
    fn rot_deg(&self, angle: Scalar) -> Self {
        let pi: Scalar = Float::pi();
        self.rot_rad(angle * pi / 180.0)
    }

    /// Rotate radians in local coordinates.
    fn rot_rad(&self, angle: Scalar) -> Self;

    /// Orients x axis to look at point locally.
    ///
    /// Leaves x axis unchanged if the point to
    /// look at is the origin.
    fn orient(&self, x: Scalar, y: Scalar) -> Self;

    /// Scales in local coordinates.
    fn scale(&self, sx: Scalar, sy: Scalar) -> Self;

    /// Scales in both directions in local coordinates.
    #[inline(always)]
    fn zoom(&self, s: Scalar) -> Self {
        self.scale(s, s)
    }

    /// Flips vertically in local coordinates.
    #[inline(always)]
    fn flip_v(&self) -> Self {
        self.scale(1.0, -1.0)
    }

    /// Flips horizontally in local coordinates.
    #[inline(always)]
    fn flip_h(&self) -> Self {
        self.scale(-1.0, 0.0)
    }

    /// Flips horizontally and vertically in local coordinates.
    #[inline(always)]
    fn flip_hv(&self) -> Self {
        self.scale(-1.0, -1.0)
    }

    /// Shears in local coordinates.
    fn shear(&self, sx: Scalar, sy: Scalar) -> Self;
}

impl<T: HasTransform<Matrix2d> + CanTransform<T, Matrix2d>
> RelativeTransform2d for T {
    #[inline(always)]
    fn trans(&self, x: Scalar, y: Scalar) -> T {
        let trans = translate(x, y);
        self.transform(multiply(self.get_transform(), trans))
    }

    #[inline(always)]
    fn rot_rad(&self, angle: Scalar) -> T {
        let rot = rotate_radians(angle);
        self.transform(multiply(self.get_transform(), rot))
    }

    #[inline(always)]
    fn orient(&self, x: Scalar, y: Scalar) -> T {
        let orient = orient(x, y);
        self.transform(multiply(self.get_transform(), orient))
    }

    #[inline(always)]
    fn scale(&self, sx: Scalar, sy: Scalar) -> T {
        let scale = scale(sx, sy);
        self.transform(multiply(self.get_transform(), scale))
    }

    #[inline(always)]
    fn shear(&self, sx: Scalar, sy: Scalar) -> T {
        let shear = shear(sx, sy);
        self.transform(multiply(self.get_transform(), shear))
    }
}

<commit_msg>Fixed a bug of `flip_h` of trait `RelativeTransform2d` in `relative.rs`<commit_after>use internal::{
    CanColor,
    CanRectangle,
    CanSourceRectangle,
    CanTransform,
    Color,
    ColorComponent,
    HasColor,
    HasRectangle,
    HasSourceRectangle,
    HasTransform,
    Rectangle,
    SourceRectangle,
};
use vecmath::{
    hsv,
    margin_rectangle,
    multiply,
    orient,
    relative_rectangle,
    relative_source_rectangle,
    rotate_radians,
    scale,
    shear,
    translate,
    Matrix2d,
    Scalar,
};

/// Implemented by contexts that contains color.
pub trait RelativeColor<T> {
    /// Multiplies with red, green, blue and alpha values.
    fn mul_rgba(
        &self,
        r: ColorComponent,
        g: ColorComponent,
        b: ColorComponent,
        a: ColorComponent
    ) -> T;

    /// Mixes the current color with white.
    ///
    /// 0 is black and 1 is white.
    #[inline(always)]
    fn tint(&self, f: ColorComponent) -> T {
        self.mul_rgba(f, f, f, 1.0)
    }

    /// Mixes the current color with black.
    ///
    /// 0 is white and 1 is black.
    #[inline(always)]
    fn shade(&self, f: ColorComponent) -> T {
        let f = 1.0 - f;
        self.mul_rgba(f, f, f, 1.0)
    }

    /// Rotates hue by degrees.
    #[inline(always)]
    fn hue_deg(&self, angle: ColorComponent) -> T {
        let pi: ColorComponent = Float::pi();
        self.hue_rad(angle * pi / 180.0)
    }

    /// Rotates hue by radians.
    fn hue_rad(&self, angle: ColorComponent) -> T;
}

impl<
    T: HasColor<Color> + CanColor<U, Color>,
    U
> RelativeColor<U> for T {
    #[inline(always)]
    fn mul_rgba(
        &self,
        r: ColorComponent,
        g: ColorComponent,
        b: ColorComponent,
        a: ColorComponent
    ) -> U {
        let color = self.get_color();
        self.color([color[0] * r, color[1] * g, color[2] * b, color[3] * a])
    }

    #[inline(always)]
    fn hue_rad(&self, angle: ColorComponent) -> U {
        self.color(hsv(self.get_color(), angle, 1.0, 1.0))
    }
}

/// Should be implemented by contexts that have rectangle information.
pub trait RelativeRectangle<T> {
    /// Shrinks the current rectangle equally by all sides.
    fn margin(&self, m: f64) -> T;

    /// Expands the current rectangle equally by all sides.
    #[inline(always)]
    fn expand(&self, m: f64) -> T {
        self.margin(-m)
    }

    /// Moves to a relative rectangle using the current rectangle as tile.
    fn rel(&self, x: f64, y: f64) -> T;
}

impl<
    T: HasRectangle<Rectangle> + CanRectangle<U, Rectangle>,
    U
> RelativeRectangle<U> for T {
    #[inline(always)]
    fn margin(&self, m: f64) -> U {
        self.rectangle(margin_rectangle(self.get_rectangle(), m))
    }

    #[inline(always)]
    fn rel(&self, x: f64, y: f64) -> U {
        self.rectangle(relative_rectangle(self.get_rectangle(), x, y))
    }
}

/// Should be implemented by contexts that
/// have source rectangle information.
pub trait RelativeSourceRectangle<T> {
    /// Adds a source rectangle.
    fn src_rect(&self, x: i32, y: i32, w: i32, h: i32) -> T;

    /// Moves to a relative source rectangle using
    /// the current source rectangle as tile.
    fn src_rel(&self, x: i32, y: i32) -> T;

    /// Flips the source rectangle horizontally.
    fn src_flip_h(&self) -> T;

    /// Flips the source rectangle vertically.
    fn src_flip_v(&self) -> T;

    /// Flips the source rectangle horizontally and vertically.
    fn src_flip_hv(&self) -> T;
}

impl<T: HasSourceRectangle<SourceRectangle>
        + CanSourceRectangle<U, SourceRectangle>,
    U
> RelativeSourceRectangle<U> for T {
    #[inline(always)]
    fn src_rel(&self, x: i32, y: i32) -> U {
        self.source_rectangle(
            relative_source_rectangle(self.get_source_rectangle(), x, y)
        )
    }

    #[inline(always)]
    fn src_rect(&self, x: i32, y: i32, w: i32, h: i32) -> U {
        self.source_rectangle([x, y, w, h])
    }

    #[inline(always)]
    fn src_flip_h(&self) -> U {
        let source_rect = self.get_source_rectangle();
        self.source_rectangle([
            source_rect[0] + source_rect[2],
            source_rect[1],
            -source_rect[2],
            source_rect[3]
        ])
    }

    #[inline(always)]
    fn src_flip_v(&self) -> U {
        let source_rect = self.get_source_rectangle();
        self.source_rectangle([
            source_rect[0],
            source_rect[1] + source_rect[3],
            source_rect[2],
            -source_rect[3]
        ])
    }

    #[inline(always)]
    fn src_flip_hv(&self) -> U {
        let source_rect = self.get_source_rectangle();
        self.source_rectangle([
            source_rect[0] + source_rect[2],
            source_rect[1] + source_rect[3],
            -source_rect[2],
            -source_rect[3]
        ])
    }
}

/// Implemented by contexts that can transform.
pub trait RelativeTransform2d {
    /// Translate x an y in local coordinates.
    fn trans(&self, x: Scalar, y: Scalar) -> Self;

    /// Rotates degrees in local coordinates.
    #[inline(always)]
    fn rot_deg(&self, angle: Scalar) -> Self {
        let pi: Scalar = Float::pi();
        self.rot_rad(angle * pi / 180.0)
    }

    /// Rotate radians in local coordinates.
    fn rot_rad(&self, angle: Scalar) -> Self;

    /// Orients x axis to look at point locally.
    ///
    /// Leaves x axis unchanged if the point to
    /// look at is the origin.
    fn orient(&self, x: Scalar, y: Scalar) -> Self;

    /// Scales in local coordinates.
    fn scale(&self, sx: Scalar, sy: Scalar) -> Self;

    /// Scales in both directions in local coordinates.
    #[inline(always)]
    fn zoom(&self, s: Scalar) -> Self {
        self.scale(s, s)
    }

    /// Flips vertically in local coordinates.
    #[inline(always)]
    fn flip_v(&self) -> Self {
        self.scale(1.0, -1.0)
    }

    /// Flips horizontally in local coordinates.
    #[inline(always)]
    fn flip_h(&self) -> Self {
        self.scale(-1.0, 1.0)
    }

    /// Flips horizontally and vertically in local coordinates.
    #[inline(always)]
    fn flip_hv(&self) -> Self {
        self.scale(-1.0, -1.0)
    }

    /// Shears in local coordinates.
    fn shear(&self, sx: Scalar, sy: Scalar) -> Self;
}

impl<T: HasTransform<Matrix2d> + CanTransform<T, Matrix2d>
> RelativeTransform2d for T {
    #[inline(always)]
    fn trans(&self, x: Scalar, y: Scalar) -> T {
        let trans = translate(x, y);
        self.transform(multiply(self.get_transform(), trans))
    }

    #[inline(always)]
    fn rot_rad(&self, angle: Scalar) -> T {
        let rot = rotate_radians(angle);
        self.transform(multiply(self.get_transform(), rot))
    }

    #[inline(always)]
    fn orient(&self, x: Scalar, y: Scalar) -> T {
        let orient = orient(x, y);
        self.transform(multiply(self.get_transform(), orient))
    }

    #[inline(always)]
    fn scale(&self, sx: Scalar, sy: Scalar) -> T {
        let scale = scale(sx, sy);
        self.transform(multiply(self.get_transform(), scale))
    }

    #[inline(always)]
    fn shear(&self, sx: Scalar, sy: Scalar) -> T {
        let shear = shear(sx, sy);
        self.transform(multiply(self.get_transform(), shear))
    }
}

<|endoftext|>


<commit_before>use math::relative_source_rectangle;

/// Should be implemented by contexts that
/// have source rectangle information.
pub trait SourceRectangled {
    /// Adds a source rectangle.
    fn src_rect(&self, x: i32, y: i32, w: i32, h: i32) -> Self;

    /// Moves to a relative source rectangle using
    /// the current source rectangle as tile.
    fn src_rel(&self, x: i32, y: i32) -> Self;

    /// Flips the source rectangle horizontally.
    fn src_flip_h(&self) -> Self;

    /// Flips the source rectangle vertically.
    fn src_flip_v(&self) -> Self;

    /// Flips the source rectangle horizontally and vertically.
    fn src_flip_hv(&self) -> Self;
}

/*
impl<T: Get<SrcRect>
      + Set<SrcRect>
      + Clone
> SourceRectangled for T {
    #[inline(always)]
    fn src_rect(&self, x: i32, y: i32, w: i32, h: i32) -> Self {
        self.clone().set(SrcRect([x, y, w, h]))
    }

    #[inline(always)]
    fn src_rel(&self, x: i32, y: i32) -> Self {
        let SrcRect(val) = self.get();
        self.clone().set(SrcRect(
            relative_source_rectangle(val, x, y)
        ))
    }

    #[inline(always)]
    fn src_flip_h(&self) -> Self {
        let SrcRect(source_rect) = self.get();
        self.clone().set(SrcRect([
            source_rect[0] + source_rect[2],
            source_rect[1],
            -source_rect[2],
            source_rect[3]
        ]))
    }

    #[inline(always)]
    fn src_flip_v(&self) -> Self {
        let SrcRect(source_rect) = self.get();
        self.clone().set(SrcRect([
            source_rect[0],
            source_rect[1] + source_rect[3],
            source_rect[2],
            -source_rect[3]
        ]))
    }

    #[inline(always)]
    fn src_flip_hv(&self) -> Self {
        let SrcRect(source_rect) = self.get();
        self.clone().set(SrcRect([
            source_rect[0] + source_rect[2],
            source_rect[1] + source_rect[3],
            -source_rect[2],
            -source_rect[3]
        ]))
    }
}
*/
<commit_msg>Made `SourceRectangled` take `self` by value<commit_after>use math::relative_source_rectangle;

/// Should be implemented by contexts that
/// have source rectangle information.
pub trait SourceRectangled {
    /// Adds a source rectangle.
    fn src_rect(self, x: i32, y: i32, w: i32, h: i32) -> Self;

    /// Moves to a relative source rectangle using
    /// the current source rectangle as tile.
    fn src_rel(self, x: i32, y: i32) -> Self;

    /// Flips the source rectangle horizontally.
    fn src_flip_h(self) -> Self;

    /// Flips the source rectangle vertically.
    fn src_flip_v(self) -> Self;

    /// Flips the source rectangle horizontally and vertically.
    fn src_flip_hv(self) -> Self;
}

/*
impl<T: Get<SrcRect>
      + Set<SrcRect>
      + Clone
> SourceRectangled for T {
    #[inline(always)]
    fn src_rect(&self, x: i32, y: i32, w: i32, h: i32) -> Self {
        self.clone().set(SrcRect([x, y, w, h]))
    }

    #[inline(always)]
    fn src_rel(&self, x: i32, y: i32) -> Self {
        let SrcRect(val) = self.get();
        self.clone().set(SrcRect(
            relative_source_rectangle(val, x, y)
        ))
    }

    #[inline(always)]
    fn src_flip_h(&self) -> Self {
        let SrcRect(source_rect) = self.get();
        self.clone().set(SrcRect([
            source_rect[0] + source_rect[2],
            source_rect[1],
            -source_rect[2],
            source_rect[3]
        ]))
    }

    #[inline(always)]
    fn src_flip_v(&self) -> Self {
        let SrcRect(source_rect) = self.get();
        self.clone().set(SrcRect([
            source_rect[0],
            source_rect[1] + source_rect[3],
            source_rect[2],
            -source_rect[3]
        ]))
    }

    #[inline(always)]
    fn src_flip_hv(&self) -> Self {
        let SrcRect(source_rect) = self.get();
        self.clone().set(SrcRect([
            source_rect[0] + source_rect[2],
            source_rect[1] + source_rect[3],
            -source_rect[2],
            -source_rect[3]
        ]))
    }
}
*/
<|endoftext|>


<commit_before>use cube_shell::cube_shell;
use id_allocator::IdAllocator;
use in_progress_terrain::InProgressTerrain;
use terrain::Terrain;
use terrain_block::BlockPosition;
use terrain_vram_buffers;
use terrain_vram_buffers::TerrainVRAMBuffers;
use physics::Physics;
use state::EntityId;
use std::collections::HashSet;
use std::collections::RingBuf;
use std::iter::range_inclusive;
use std::num::Float;
use stopwatch::TimerSet;
use terrain_block::SAMPLES_PER_BLOCK;
use yaglw::gl_context::GLContext;

#[cfg(test)]
use cube_shell::cube_shell_area;
#[cfg(test)]
use nalgebra::Pnt3;
#[cfg(test)]
use ncollide::bounding_volume::AABB;
#[cfg(test)]
use std::cmp::max;
#[cfg(test)]
use std::num::SignedInt;

// values are approximately in microseconds, but they don't have to be.
pub const BLOCK_UPDATE_BUDGET: int = 8000;
pub const BLOCK_LOAD_COST: int = 600;
pub const BLOCK_UNLOAD_COST: int = 300;

pub const POLYGONS_PER_BLOCK: i32 = SAMPLES_PER_BLOCK as i32 * SAMPLES_PER_BLOCK as i32 * 4;

/// Keep surroundings loaded around a given world position.
pub struct SurroundingsLoader {
  pub terrain: Terrain,
  pub in_progress_terrain: InProgressTerrain,

  pub load_queue: RingBuf<BlockPosition>,
  pub unload_queue: RingBuf<BlockPosition>,

  pub load_distance: i32,

  // the set of blocks that are currently loaded
  pub loaded: HashSet<BlockPosition>,

  pub last_position: Option<BlockPosition>,
}

impl SurroundingsLoader {
  pub fn new(portion_of_polygon_budget: i32) -> SurroundingsLoader {
    assert!(portion_of_polygon_budget > 0);

    let block_budget =
      terrain_vram_buffers::POLYGON_BUDGET as i32
      / (portion_of_polygon_budget * POLYGONS_PER_BLOCK);
    // We'll have at most load_width^2 full blocks loaded.
    // This is because we generate flat terrain! If that changes, this changes!
    let load_width = (block_budget as f32).sqrt() as i32;
    let load_distance = (load_width - 1) / 2;

    info!("load_distance {}", lost_distance);

    SurroundingsLoader {
      terrain: Terrain::new(),
      in_progress_terrain: InProgressTerrain::new(),

      load_queue: RingBuf::new(),
      unload_queue: RingBuf::new(),

      load_distance: load_distance,

      loaded: HashSet::new(),

      last_position: None,
    }
  }

  pub fn update(
    &mut self,
    timers: &TimerSet,
    gl: &mut GLContext,
    terrain_buffers: &mut TerrainVRAMBuffers,
    id_allocator: &mut IdAllocator<EntityId>,
    physics: &mut Physics,
    position: BlockPosition,
  ) {
    timers.time("update.update_queues", || {
      self.update_queues(timers, id_allocator, physics, position);
    });
    timers.time("update.load_some", || {
      self.load_some(timers, gl, terrain_buffers, id_allocator, physics);
    });
  }

  #[inline]
  fn update_queues(
    &mut self,
    timers: &TimerSet,
    id_allocator: &mut IdAllocator<EntityId>,
    physics: &mut Physics,
    block_position: BlockPosition,
  ) {
    if Some(block_position) != self.last_position {
      self.last_position = Some(block_position);

      let (want_loaded_vec, want_loaded_set) =
        SurroundingsLoader::wanted_blocks(timers, &block_position, self.load_distance);

      timers.time("update.update_queues.load_queue", || {
        for block_position in self.load_queue.iter() {
          self.in_progress_terrain.remove(physics, block_position);
        }
        self.load_queue.clear();
        for block_position in want_loaded_vec.iter() {
          let is_loaded = self.loaded.contains(block_position);
          if !is_loaded {
            self.in_progress_terrain.insert(id_allocator, physics, block_position);
            self.load_queue.push_back(*block_position);
          }
        }
      });

      timers.time("update.update_queues.unload_queue", || {
        self.unload_queue.clear();
        for block_position in self.loaded.iter() {
          let is_needed = want_loaded_set.contains(block_position);
          if !is_needed {
            self.unload_queue.push_back(*block_position);
          }
        }
      });
    }
  }

  #[inline]
  // Get the set of all blocks we want loaded around a given position.
  // Produces a Vec because order is important, and a HashSet for quick membership tests.
  fn wanted_blocks(
    timers: &TimerSet,
    position: &BlockPosition,
    load_distance: i32,
  ) -> (Vec<BlockPosition>, HashSet<BlockPosition>) {
    timers.time("update.update_queues.want_loaded", || {
      let mut want_loaded_vec = Vec::new();
      let mut want_loaded_set = HashSet::new();

      want_loaded_vec.push(*position);
      want_loaded_set.insert(*position);

      for radius in range_inclusive(1, load_distance) {
        let blocks_at_radius = cube_shell(position, radius);
        want_loaded_vec.push_all(blocks_at_radius.as_slice());
        for position in blocks_at_radius.into_iter() {
          want_loaded_set.insert(position);
        }
      }

      (want_loaded_vec, want_loaded_set)
    })
  }

  // Load some blocks. Prioritizes unloading unneeded ones over loading new ones.
  #[inline]
  fn load_some(
    &mut self,
    timers: &TimerSet,
    gl: &mut GLContext,
    terrain_buffers: &mut TerrainVRAMBuffers,
    id_allocator: &mut IdAllocator<EntityId>,
    physics: &mut Physics,
  ) {
    let mut budget = BLOCK_UPDATE_BUDGET;
    while budget > 0 {
      match self.unload_queue.pop_front() {
        None =>
          match self.load_queue.pop_front() {
            None => {
              break;
            },
            Some(block_position) => {
              timers.time("update.load_some.load", || {
                let block = unsafe {
                  self.terrain.load(timers, id_allocator, &block_position)
                };

                timers.time("update.load_some.load.physics", || {
                  for (&id, bounds) in block.bounds.iter() {
                    physics.insert_terrain(id, bounds);
                  }
                });

                timers.time("update.load_some.load.vram", || {
                  terrain_buffers.push(
                    gl,
                    block.vertex_coordinates.as_slice(),
                    block.normals.as_slice(),
                    block.typs.as_slice(),
                    block.ids.as_slice(),
                  );
                });
              });

              self.in_progress_terrain.remove(physics, &block_position);
              self.loaded.insert(block_position);
              budget -= BLOCK_LOAD_COST;
            },
          },
        Some(block_position) => {
          timers.time("update.load_some.unload", || {
            let block = self.terrain.all_blocks.get(&block_position).unwrap();
            for id in block.ids.iter() {
              physics.remove_terrain(*id);
              terrain_buffers.swap_remove(gl, *id);
            }

            self.loaded.remove(&block_position);
            budget -= BLOCK_UNLOAD_COST;
          })
        }
      }
    }
  }
}

#[test]
fn shell_ordering() {
  fn radius_between(p1: &BlockPosition, p2: &BlockPosition) -> i32 {
    let dx = (p1.as_pnt().x - p2.as_pnt().x).abs();
    let dy = (p1.as_pnt().y - p2.as_pnt().y).abs();
    let dz = (p1.as_pnt().z - p2.as_pnt().z).abs();
    max(max(dx, dy), dz)
  }

  let mut loader = SurroundingsLoader::new(1);
  let timers = TimerSet::new();
  let mut id_allocator = IdAllocator::new();
  let mut physics = Physics::new(AABB::new(Pnt3::new(-128.0, -128.0, -128.0), Pnt3::new(128.0, 128.0, 128.0)));
  let position = BlockPosition::new(1, -4, 7);
  loader.update_queues(&timers, &mut id_allocator, &mut physics, position);
  let mut load_positions = loader.load_queue.into_iter();

  // The load queue should contain cube shells in increasing order of radius.
  for radius in range_inclusive(0, loader.load_distance) {
    for _ in range(0, cube_shell_area(radius)) {
      let load_position = load_positions.next();
      println!("radius {}", radius);
      println!("load_position {}", load_position);
      // The next load position should be in the cube shell of the given radius, relative to the center position.
      assert_eq!(radius_between(&position, &load_position.unwrap()), radius);
    }
  }

  // The load queue should be exactly the shells specified above, and nothing else.
  assert!(load_positions.next().is_none());
}
<commit_msg>fix typo<commit_after>use cube_shell::cube_shell;
use id_allocator::IdAllocator;
use in_progress_terrain::InProgressTerrain;
use terrain::Terrain;
use terrain_block::BlockPosition;
use terrain_vram_buffers;
use terrain_vram_buffers::TerrainVRAMBuffers;
use physics::Physics;
use state::EntityId;
use std::collections::HashSet;
use std::collections::RingBuf;
use std::iter::range_inclusive;
use std::num::Float;
use stopwatch::TimerSet;
use terrain_block::SAMPLES_PER_BLOCK;
use yaglw::gl_context::GLContext;

#[cfg(test)]
use cube_shell::cube_shell_area;
#[cfg(test)]
use nalgebra::Pnt3;
#[cfg(test)]
use ncollide::bounding_volume::AABB;
#[cfg(test)]
use std::cmp::max;
#[cfg(test)]
use std::num::SignedInt;

// values are approximately in microseconds, but they don't have to be.
pub const BLOCK_UPDATE_BUDGET: int = 8000;
pub const BLOCK_LOAD_COST: int = 600;
pub const BLOCK_UNLOAD_COST: int = 300;

pub const POLYGONS_PER_BLOCK: i32 = SAMPLES_PER_BLOCK as i32 * SAMPLES_PER_BLOCK as i32 * 4;

/// Keep surroundings loaded around a given world position.
pub struct SurroundingsLoader {
  pub terrain: Terrain,
  pub in_progress_terrain: InProgressTerrain,

  pub load_queue: RingBuf<BlockPosition>,
  pub unload_queue: RingBuf<BlockPosition>,

  pub load_distance: i32,

  // the set of blocks that are currently loaded
  pub loaded: HashSet<BlockPosition>,

  pub last_position: Option<BlockPosition>,
}

impl SurroundingsLoader {
  pub fn new(portion_of_polygon_budget: i32) -> SurroundingsLoader {
    assert!(portion_of_polygon_budget > 0);

    let block_budget =
      terrain_vram_buffers::POLYGON_BUDGET as i32
      / (portion_of_polygon_budget * POLYGONS_PER_BLOCK);
    // We'll have at most load_width^2 full blocks loaded.
    // This is because we generate flat terrain! If that changes, this changes!
    let load_width = (block_budget as f32).sqrt() as i32;
    let load_distance = (load_width - 1) / 2;

    info!("load_distance {}", load_distance);

    SurroundingsLoader {
      terrain: Terrain::new(),
      in_progress_terrain: InProgressTerrain::new(),

      load_queue: RingBuf::new(),
      unload_queue: RingBuf::new(),

      load_distance: load_distance,

      loaded: HashSet::new(),

      last_position: None,
    }
  }

  pub fn update(
    &mut self,
    timers: &TimerSet,
    gl: &mut GLContext,
    terrain_buffers: &mut TerrainVRAMBuffers,
    id_allocator: &mut IdAllocator<EntityId>,
    physics: &mut Physics,
    position: BlockPosition,
  ) {
    timers.time("update.update_queues", || {
      self.update_queues(timers, id_allocator, physics, position);
    });
    timers.time("update.load_some", || {
      self.load_some(timers, gl, terrain_buffers, id_allocator, physics);
    });
  }

  #[inline]
  fn update_queues(
    &mut self,
    timers: &TimerSet,
    id_allocator: &mut IdAllocator<EntityId>,
    physics: &mut Physics,
    block_position: BlockPosition,
  ) {
    if Some(block_position) != self.last_position {
      self.last_position = Some(block_position);

      let (want_loaded_vec, want_loaded_set) =
        SurroundingsLoader::wanted_blocks(timers, &block_position, self.load_distance);

      timers.time("update.update_queues.load_queue", || {
        for block_position in self.load_queue.iter() {
          self.in_progress_terrain.remove(physics, block_position);
        }
        self.load_queue.clear();
        for block_position in want_loaded_vec.iter() {
          let is_loaded = self.loaded.contains(block_position);
          if !is_loaded {
            self.in_progress_terrain.insert(id_allocator, physics, block_position);
            self.load_queue.push_back(*block_position);
          }
        }
      });

      timers.time("update.update_queues.unload_queue", || {
        self.unload_queue.clear();
        for block_position in self.loaded.iter() {
          let is_needed = want_loaded_set.contains(block_position);
          if !is_needed {
            self.unload_queue.push_back(*block_position);
          }
        }
      });
    }
  }

  #[inline]
  // Get the set of all blocks we want loaded around a given position.
  // Produces a Vec because order is important, and a HashSet for quick membership tests.
  fn wanted_blocks(
    timers: &TimerSet,
    position: &BlockPosition,
    load_distance: i32,
  ) -> (Vec<BlockPosition>, HashSet<BlockPosition>) {
    timers.time("update.update_queues.want_loaded", || {
      let mut want_loaded_vec = Vec::new();
      let mut want_loaded_set = HashSet::new();

      want_loaded_vec.push(*position);
      want_loaded_set.insert(*position);

      for radius in range_inclusive(1, load_distance) {
        let blocks_at_radius = cube_shell(position, radius);
        want_loaded_vec.push_all(blocks_at_radius.as_slice());
        for position in blocks_at_radius.into_iter() {
          want_loaded_set.insert(position);
        }
      }

      (want_loaded_vec, want_loaded_set)
    })
  }

  // Load some blocks. Prioritizes unloading unneeded ones over loading new ones.
  #[inline]
  fn load_some(
    &mut self,
    timers: &TimerSet,
    gl: &mut GLContext,
    terrain_buffers: &mut TerrainVRAMBuffers,
    id_allocator: &mut IdAllocator<EntityId>,
    physics: &mut Physics,
  ) {
    let mut budget = BLOCK_UPDATE_BUDGET;
    while budget > 0 {
      match self.unload_queue.pop_front() {
        None =>
          match self.load_queue.pop_front() {
            None => {
              break;
            },
            Some(block_position) => {
              timers.time("update.load_some.load", || {
                let block = unsafe {
                  self.terrain.load(timers, id_allocator, &block_position)
                };

                timers.time("update.load_some.load.physics", || {
                  for (&id, bounds) in block.bounds.iter() {
                    physics.insert_terrain(id, bounds);
                  }
                });

                timers.time("update.load_some.load.vram", || {
                  terrain_buffers.push(
                    gl,
                    block.vertex_coordinates.as_slice(),
                    block.normals.as_slice(),
                    block.typs.as_slice(),
                    block.ids.as_slice(),
                  );
                });
              });

              self.in_progress_terrain.remove(physics, &block_position);
              self.loaded.insert(block_position);
              budget -= BLOCK_LOAD_COST;
            },
          },
        Some(block_position) => {
          timers.time("update.load_some.unload", || {
            let block = self.terrain.all_blocks.get(&block_position).unwrap();
            for id in block.ids.iter() {
              physics.remove_terrain(*id);
              terrain_buffers.swap_remove(gl, *id);
            }

            self.loaded.remove(&block_position);
            budget -= BLOCK_UNLOAD_COST;
          })
        }
      }
    }
  }
}

#[test]
fn shell_ordering() {
  fn radius_between(p1: &BlockPosition, p2: &BlockPosition) -> i32 {
    let dx = (p1.as_pnt().x - p2.as_pnt().x).abs();
    let dy = (p1.as_pnt().y - p2.as_pnt().y).abs();
    let dz = (p1.as_pnt().z - p2.as_pnt().z).abs();
    max(max(dx, dy), dz)
  }

  let mut loader = SurroundingsLoader::new(1);
  let timers = TimerSet::new();
  let mut id_allocator = IdAllocator::new();
  let mut physics = Physics::new(AABB::new(Pnt3::new(-128.0, -128.0, -128.0), Pnt3::new(128.0, 128.0, 128.0)));
  let position = BlockPosition::new(1, -4, 7);
  loader.update_queues(&timers, &mut id_allocator, &mut physics, position);
  let mut load_positions = loader.load_queue.into_iter();

  // The load queue should contain cube shells in increasing order of radius.
  for radius in range_inclusive(0, loader.load_distance) {
    for _ in range(0, cube_shell_area(radius)) {
      let load_position = load_positions.next();
      println!("radius {}", radius);
      println!("load_position {}", load_position);
      // The next load position should be in the cube shell of the given radius, relative to the center position.
      assert_eq!(radius_between(&position, &load_position.unwrap()), radius);
    }
  }

  // The load queue should be exactly the shells specified above, and nothing else.
  assert!(load_positions.next().is_none());
}
<|endoftext|>