<commit_before>//! Issues interface

use self::super::{Github, Result, SortDirection, State};
use rep::{Issue, IssueReq, Label};
use rustc_serialize::json;
use std::fmt;
use std::default::Default;
use url::form_urlencoded;

pub enum Filter {
    Assigned,
    Created,
    Mentioned,
    Subscribed,
    All,
}

impl fmt::Display for Filter {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f,
               "{}",
               match *self {
                   Filter::Assigned => "assigned",
                   Filter::Created => "created",
                   Filter::Mentioned => "mentioned",
                   Filter::Subscribed => "subscribed",
                   Filter::All => "all",
               })
    }
}

impl Default for Filter {
    fn default() -> Filter {
        Filter::Assigned
    }
}

pub enum Sort {
    Created,
    Updated,
    Comments,
}

impl fmt::Display for Sort {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f,
               "{}",
               match *self {
                   Sort::Created => "created",
                   Sort::Updated => "updated",
                   Sort::Comments => "comments",
               })
    }
}

impl Default for Sort {
    fn default() -> Sort {
        Sort::Created
    }
}

pub struct IssueLabels<'a> {
    github: &'a Github<'a>,
    owner: String,
    repo: String,
    number: u64,
}

impl<'a> IssueLabels<'a> {
    pub fn new<O, R>(github: &'a Github<'a>, owner: O, repo: R, number: u64) -> IssueLabels<'a>
        where O: Into<String>,
              R: Into<String>
    {
        IssueLabels {
            github: github,
            owner: owner.into(),
            repo: repo.into(),
            number: number,
        }
    }

    fn path(&self, more: &str) -> String {
        format!("/repos/{}/{}/issues/{}/labels{}",
                self.owner,
                self.repo,
                self.number,
                more)
    }

    /// add a set of labels to this issue ref
    pub fn add(&self, labels: Vec<&str>) -> Result<Vec<Label>> {
        self.github
            .post::<Vec<Label>>(&self.path(""), try!(json::encode(&labels)).as_bytes())
    }

    /// remove a label from this issue
    pub fn remove(&self, label: &str) -> Result<()> {
        self.github
            .delete(&self.path(&format!("/{}", label)))
    }

    /// replace all labels associated with this issue with a new set.
    /// providing an empty set of labels is the same as clearing the
    /// current labels
    pub fn set(&self, labels: Vec<&str>) -> Result<Vec<Label>> {
        self.github.put::<Vec<Label>>(&self.path(""), try!(json::encode(&labels)).as_bytes())
    }

    /// remove all labels from an issue
    pub fn clear(&self) -> Result<()> {
        self.github
            .delete(&self.path(""))
    }
}

pub struct IssueRef<'a> {
    github: &'a Github<'a>,
    owner: String,
    repo: String,
    number: u64,
}

impl<'a> IssueRef<'a> {
    /// create a new instance of a github repo issue ref
    pub fn new<O, R>(github: &'a Github<'a>, owner: O, repo: R, number: u64) -> IssueRef<'a>
        where O: Into<String>,
              R: Into<String>
    {
        IssueRef {
            github: github,
            owner: owner.into(),
            repo: repo.into(),
            number: number,
        }
    }

    fn path(&self, more: &str) -> String {
        format!("/repos/{}/{}/issues/{}{}",
                self.owner,
                self.repo,
                self.number,
                more)
    }

    pub fn labels(&self) -> IssueLabels {
        IssueLabels::new(self.github,
                         self.owner.as_ref(),
                         self.repo.as_ref(),
                         self.number)
    }

    pub fn edit(&self, is: &IssueReq) -> Result<Issue> {
        let data = try!(json::encode(&is));
        self.github.patch::<Issue>(&self.path(""), data.as_bytes())
    }
}

pub struct Issues<'a> {
    github: &'a Github<'a>,
    owner: String,
    repo: String,
}

/// an mutable issue list builder
pub struct ListBuilder<'a> {
    issues: &'a Issues<'a>,
    state: State,
    sort: Sort,
    direction: SortDirection,
    assignee: Option<String>,
    creator: Option<String>,
    mentioned: Option<String>,
    labels: Vec<String>,
    since: Option<String>,
}

impl<'a> ListBuilder<'a> {

    pub fn new(is: &'a Issues<'a>) -> ListBuilder<'a> {
        ListBuilder {
            issues: is,
            state: Default::default(),
            sort: Default::default(),
            direction: Default::default(),
            assignee: Default::default(),
            creator: Default::default(),
            mentioned: Default::default(),
            labels: Default::default(),
            since: Default::default(),
        }
    }

    pub fn state(&mut self, state: State) -> &mut ListBuilder<'a> {
        self.state = state;
        self
    }

    pub fn sort(&mut self, sort: Sort) -> &mut ListBuilder<'a> {
        self.sort = sort;
        self
    }

    pub fn direction(&mut self, direction: SortDirection) -> &mut ListBuilder<'a> {
        self.direction = direction;
        self
    }

    pub fn assignee<A>(&mut self, assignee: A) -> &mut ListBuilder<'a> where A: Into<String> {
        self.assignee = Some(assignee.into());
        self
    }

    pub fn creator<C>(&mut self, creator: C) -> &mut ListBuilder<'a> where C: Into<String> {
        self.creator = Some(creator.into());
        self
    }

    pub fn mentioned<M>(&mut self, mentioned: M) -> &mut ListBuilder<'a> where M: Into<String> {
        self.mentioned = Some(mentioned.into());
        self
    }

    pub fn labels<L>(&mut self, labels: Vec<L>) -> &mut ListBuilder<'a> where L: Into<String> {
        self.labels = labels.into_iter().map(|l|l.into()).collect::<Vec<String>>();
        self
    }

    pub fn since<S>(&mut self, since: S) -> &mut ListBuilder<'a> where S: Into<String> {
        self.since = Some(since.into());
        self
    }

    pub fn get(&self) -> Result<Vec<Issue>> {
        let mut params = Vec::new();
        params.push(("state", self.state.to_string()));
        params.push(("sort", self.sort.to_string()));
        params.push(("direction", self.direction.to_string()));
        if let Some(ref a) = self.assignee {
            params.push(("assignee", a.to_owned()));
        }
        if let Some(ref c) = self.creator {
            params.push(("creator", c.to_owned()));
        }
        if let Some(ref m) = self.mentioned {
            params.push(("mentioned", m.to_owned()));
        }
        if let Some(ref s) = self.since {
            params.push(("since", s.to_owned()));
        }
        if !self.labels.is_empty() {
            params.push(("labels", self.labels.connect(",")));
        }
        let url = self.issues.path(&format!("?{}", form_urlencoded::serialize(params)));
        self.issues.github.get::<Vec<Issue>>(&url)
    }

}

impl<'a> Issues<'a> {
    /// create a new instance of a github repo issue ref
    pub fn new<O, R>(github: &'a Github<'a>, owner: O, repo: R) -> Issues<'a>
        where O: Into<String>,
              R: Into<String>
    {
        Issues {
            github: github,
            owner: owner.into(),
            repo: repo.into(),
        }
    }

    fn path(&self, more: &str) -> String {
        format!("/repos/{}/{}/issues{}", self.owner, self.repo, more)
    }

    pub fn get(&self, number: u64) -> IssueRef {
        IssueRef::new(self.github, self.owner.as_ref(), self.repo.as_ref(), number)
    }

    pub fn create(&self, is: &IssueReq) -> Result<Issue> {
        let data = try!(json::encode(&is));
        self.github.post::<Issue>(&self.path(""), data.as_bytes())
    }

    pub fn list(&self) -> ListBuilder {
        ListBuilder::new(self)
    }
}
<commit_msg>separate execution from req build for issue list reqs<commit_after>//! Issues interface

use self::super::{Github, Result, SortDirection, State};
use rep::{Issue, IssueReq, Label};
use rustc_serialize::json;
use std::fmt;
use std::default::Default;
use url::form_urlencoded;

pub enum Filter {
    Assigned,
    Created,
    Mentioned,
    Subscribed,
    All,
}

impl fmt::Display for Filter {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f,
               "{}",
               match *self {
                   Filter::Assigned => "assigned",
                   Filter::Created => "created",
                   Filter::Mentioned => "mentioned",
                   Filter::Subscribed => "subscribed",
                   Filter::All => "all",
               })
    }
}

impl Default for Filter {
    fn default() -> Filter {
        Filter::Assigned
    }
}

#[derive(Clone)]
pub enum Sort {
    Created,
    Updated,
    Comments,
}

impl fmt::Display for Sort {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f,
               "{}",
               match *self {
                   Sort::Created => "created",
                   Sort::Updated => "updated",
                   Sort::Comments => "comments",
               })
    }
}

impl Default for Sort {
    fn default() -> Sort {
        Sort::Created
    }
}

pub struct IssueLabels<'a> {
    github: &'a Github<'a>,
    owner: String,
    repo: String,
    number: u64,
}

impl<'a> IssueLabels<'a> {
    pub fn new<O, R>(github: &'a Github<'a>, owner: O, repo: R, number: u64) -> IssueLabels<'a>
        where O: Into<String>,
              R: Into<String>
    {
        IssueLabels {
            github: github,
            owner: owner.into(),
            repo: repo.into(),
            number: number,
        }
    }

    fn path(&self, more: &str) -> String {
        format!("/repos/{}/{}/issues/{}/labels{}",
                self.owner,
                self.repo,
                self.number,
                more)
    }

    /// add a set of labels to this issue ref
    pub fn add(&self, labels: Vec<&str>) -> Result<Vec<Label>> {
        self.github
            .post::<Vec<Label>>(&self.path(""), try!(json::encode(&labels)).as_bytes())
    }

    /// remove a label from this issue
    pub fn remove(&self, label: &str) -> Result<()> {
        self.github
            .delete(&self.path(&format!("/{}", label)))
    }

    /// replace all labels associated with this issue with a new set.
    /// providing an empty set of labels is the same as clearing the
    /// current labels
    pub fn set(&self, labels: Vec<&str>) -> Result<Vec<Label>> {
        self.github.put::<Vec<Label>>(&self.path(""), try!(json::encode(&labels)).as_bytes())
    }

    /// remove all labels from an issue
    pub fn clear(&self) -> Result<()> {
        self.github
            .delete(&self.path(""))
    }
}

pub struct IssueRef<'a> {
    github: &'a Github<'a>,
    owner: String,
    repo: String,
    number: u64,
}

impl<'a> IssueRef<'a> {
    /// create a new instance of a github repo issue ref
    pub fn new<O, R>(github: &'a Github<'a>, owner: O, repo: R, number: u64) -> IssueRef<'a>
        where O: Into<String>,
              R: Into<String>
    {
        IssueRef {
            github: github,
            owner: owner.into(),
            repo: repo.into(),
            number: number,
        }
    }

    fn path(&self, more: &str) -> String {
        format!("/repos/{}/{}/issues/{}{}",
                self.owner,
                self.repo,
                self.number,
                more)
    }

    pub fn labels(&self) -> IssueLabels {
        IssueLabels::new(self.github,
                         self.owner.as_ref(),
                         self.repo.as_ref(),
                         self.number)
    }

    pub fn edit(&self, is: &IssueReq) -> Result<Issue> {
        let data = try!(json::encode(&is));
        self.github.patch::<Issue>(&self.path(""), data.as_bytes())
    }
}

pub struct Issues<'a> {
    github: &'a Github<'a>,
    owner: String,
    repo: String,
}

pub struct ListReq {
    state: State,
    sort: Sort,
    direction: SortDirection,
    assignee: Option<String>,
    creator: Option<String>,
    mentioned: Option<String>,
    labels: Vec<String>,
    since: Option<String>,
}

impl ListReq {
    pub fn builder() -> ListReqBuilder {
        ListReqBuilder::new()
    }
}

/// an mutable issue list builder
#[derive(Default)]
pub struct ListReqBuilder {
    state: State,
    sort: Sort,
    direction: SortDirection,
    assignee: Option<String>,
    creator: Option<String>,
    mentioned: Option<String>,
    labels: Vec<String>,
    since: Option<String>,
}

impl ListReqBuilder {

    pub fn new() -> ListReqBuilder {
        ListReqBuilder {
            ..Default::default()
        }
    }

    pub fn state(&mut self, state: State) -> &mut ListReqBuilder {
        self.state = state;
        self
    }

    pub fn sort(&mut self, sort: Sort) -> &mut ListReqBuilder {
        self.sort = sort;
        self
    }

    pub fn direction(&mut self, direction: SortDirection) -> &mut ListReqBuilder {
        self.direction = direction;
        self
    }

    pub fn assignee<A>(&mut self, assignee: A) -> &mut ListReqBuilder where A: Into<String> {
        self.assignee = Some(assignee.into());
        self
    }

    pub fn creator<C>(&mut self, creator: C) -> &mut ListReqBuilder where C: Into<String> {
        self.creator = Some(creator.into());
        self
    }

    pub fn mentioned<M>(&mut self, mentioned: M) -> &mut ListReqBuilder where M: Into<String> {
        self.mentioned = Some(mentioned.into());
        self
    }

    pub fn labels<L>(&mut self, labels: Vec<L>) -> &mut ListReqBuilder where L: Into<String> {
        self.labels = labels.into_iter().map(|l|l.into()).collect::<Vec<String>>();
        self
    }

    pub fn since<S>(&mut self, since: S) -> &mut ListReqBuilder where S: Into<String> {
        self.since = Some(since.into());
        self
    }

    pub fn build(&self) -> ListReq {
        ListReq {
            state: self.state.clone(),
            sort: self.sort.clone(),
            direction: self.direction.clone(),
            assignee: self.assignee.clone(),
            creator: self.creator.clone(),
            mentioned: self.mentioned.clone(),
            labels: self.labels.clone(),
            since: self.since.clone(),
        }
    }

}

impl<'a> Issues<'a> {
    /// create a new instance of a github repo issue ref
    pub fn new<O, R>(github: &'a Github<'a>, owner: O, repo: R) -> Issues<'a>
        where O: Into<String>,
              R: Into<String>
    {
        Issues {
            github: github,
            owner: owner.into(),
            repo: repo.into(),
        }
    }

    fn path(&self, more: &str) -> String {
        format!("/repos/{}/{}/issues{}", self.owner, self.repo, more)
    }

    pub fn get(&self, number: u64) -> IssueRef {
        IssueRef::new(self.github, self.owner.as_ref(), self.repo.as_ref(), number)
    }

    pub fn create(&self, is: &IssueReq) -> Result<Issue> {
        let data = try!(json::encode(&is));
        self.github.post::<Issue>(&self.path(""), data.as_bytes())
    }

    pub fn list(&self, req: &ListReq) -> Result<Vec<Issue>> {
        let mut params = Vec::new();
        params.push(("state", req.state.to_string()));
        params.push(("sort", req.sort.to_string()));
        params.push(("direction", req.direction.to_string()));
        if let Some(ref a) = req.assignee {
            params.push(("assignee", a.to_owned()));
        }
        if let Some(ref c) = req.creator {
            params.push(("creator", c.to_owned()));
        }
        if let Some(ref m) = req.mentioned {
            params.push(("mentioned", m.to_owned()));
        }
        if let Some(ref s) = req.since {
            params.push(("since", s.to_owned()));
        }
        if !req.labels.is_empty() {
            params.push(("labels", req.labels.connect(",")));
        }
        let url = self.path(&format!("?{}", form_urlencoded::serialize(params)));
        self.github.get::<Vec<Issue>>(&url)
    }
}
<|endoftext|>


<commit_before>use Prefab;
use Rng;
use calx_grid::{Dijkstra, Dir6};
use euclid::vec2;
use field::Field;
use form::{self, Form};
use location::{Location, Portal};
use rand::{self, SeedableRng};
use serde;
use std::cmp;
use std::collections::BTreeSet;
use std::collections::HashMap;
use std::slice;
use terrain::Terrain;
use world::Loadout;

/// Static generated world.
pub struct Worldgen {
    seed: u32,
    terrain: HashMap<Location, Terrain>,
    portals: HashMap<Location, Portal>,
    spawns: Vec<(Location, Loadout)>,
    player_entry: Location,
}

impl Worldgen {
    pub fn new(seed: u32) -> Worldgen {
        let mut ret = Worldgen {
            seed: seed,
            terrain: HashMap::new(),
            portals: HashMap::new(),
            spawns: Vec::new(),
            player_entry: Location::new(0, 0, 0),
        };

        let mut rng: Rng = SeedableRng::from_seed([seed, seed, seed, seed]);

        let mut cave_entrance = Location::new(9, 0, 0);

        for cave_z in 1..11 {
            ret.cave_entrance(cave_entrance, Location::new(0, 0, cave_z));
            cave_entrance = ret.gen_caves(&mut rng, Location::new(0, 0, cave_z));
        }

        ret
    }

    fn load_prefab(&mut self, origin: Location, prefab: &Prefab) {
        for (p, &(ref terrain, ref entities)) in prefab.iter() {
            let loc = origin + p;

            self.terrain.insert(loc, *terrain);

            for spawn in entities.iter() {
                if spawn == "player" {
                    self.player_entry = loc;
                } else {
                    let form = Form::named(spawn).expect(&format!(
                        "Bad prefab: Form '{}' not found!",
                        spawn
                    ));
                    self.spawns.push((loc, form.loadout.clone()));
                }
            }
        }
    }

    pub fn seed(&self) -> u32 { self.seed }

    pub fn get_terrain(&self, loc: Location) -> Terrain {
        if let Some(&t) = self.terrain.get(&loc) {
            t
        } else {
            self.default_terrain(loc)
        }
    }

    fn default_terrain(&self, loc: Location) -> Terrain {
        use Terrain::*;
        if loc.z == 0 {
            match loc.noise() {
                n if n > 0.8 => Tree,
                n if n > -0.8 => Grass,
                _ => Water,
            }
        } else {
            Terrain::Rock
        }
    }

    pub fn get_portal(&self, loc: Location) -> Option<Location> {
        self.portals.get(&loc).map(|&p| loc + p)
    }

    pub fn spawns(&self) -> slice::Iter<(Location, Loadout)> { self.spawns.iter() }

    pub fn player_entry(&self) -> Location { self.player_entry }

    fn gen_caves<R: rand::Rng>(&mut self, rng: &mut R, entrance: Location) -> Location {
        use self::Prototerrain::*;

        let mut cells_to_dig = 700;

        let mut map = screen_map(Location::new(0, 0, entrance.z));

        debug_assert!(map.get(entrance) == Unused);

        // Create portal enclosure
        entry_cave_enclosure(&mut map, entrance);

        let entrance = entrance + vec2(1, 1);

        let mut edge: BTreeSet<Location> = Dir6::iter()
            .map(|&d| entrance + vec2(2, 2) + d)
            .filter(|&loc| map.get(loc) == Unused)
            .collect();

        // Arbitrary long iteration, should break after digging a sufficient number of cells before
        // this.
        for _ in 0..10000 {
            if edge.is_empty() {
                break;
            }

            let dig_loc = *rand::sample(rng, edge.iter(), 1)[0];

            // Prefer digging narrow corridors, there's an increasing chance to abort the dig when the
            // selected location is in a very open space.
            let adjacent_floors = Dir6::iter()
                .filter(|d| map.get(dig_loc + **d) == Floor)
                .count();
            if rng.gen_range(0, cmp::max(1, adjacent_floors * adjacent_floors)) != 0 {
                continue;
            }

            map.set(dig_loc, Floor);
            edge.extend(Dir6::iter().map(|&d| dig_loc + d).filter(|&loc| {
                map.get(loc) == Unused
            }));

            cells_to_dig -= 1;
            if cells_to_dig == 0 {
                break;
            }
        }

        // Postprocess
        let cells: Vec<_> = map.iter().map(|(&loc, &c)| (loc, c)).collect();
        for (loc, c) in cells.into_iter() {
            // Clear pillars
            if c == Unused {
                let adjacent_floors = Dir6::iter().filter(|d| map.get(loc + **d) == Floor).count();

                if adjacent_floors == 6 {
                    map.set(loc, Floor);
                }
            }
        }

        // Find opening for next map
        let openings: Vec<Location> = map.iter()
            .map(|(&loc, _)| loc)
            .filter(|&loc| can_be_path_down_opening(&map, entrance, loc))
            .collect();
        // XXX FIXME: There's actually no guarantees made that this can't fail
        // Fallback should be something like carving the exit to the bottom edge of the map
        assert!(!openings.is_empty(), "No exit candidates");
        let exit_loc = rand::sample(rng, openings, 1)[0];

        // Spawns
        const MIN_DISTANCE_FROM_ENTRANCE: u32 = 10;
        let depth = entrance.z as i32;
        // Flood-fill the new map
        let mut spawn_map = Dijkstra::new(vec![entrance], |&loc| map.get(loc) == Floor, 10000)
            .weights;
        // Filter stuff too close to entrance
        spawn_map.retain(|_, &mut w| w >= MIN_DISTANCE_FROM_ENTRANCE);
        // Don't need weights anymore, convert to Vec.
        let mut spawn_locs: Vec<Location> = spawn_map.into_iter().map(|(loc, _)| loc).collect();
        // Filter stuff next to walls, only spawn in open areas
        spawn_locs.retain(|&loc| {
            Dir6::iter()
                .map(|&d| (map.get(loc + d) == Floor) as u32)
                .sum::<u32>() > 3
        });

        let mut spawn_locs = rand::sample(rng, spawn_locs.iter(), 20);
        let n_spawns = spawn_locs.len();

        let items = Form::filter(|f| f.is_item() && f.at_depth(depth));
        for &loc in spawn_locs.drain(0..n_spawns / 2) {
            self.spawns.push((
                loc,
                form::rand(rng, &items)
                    .expect("No item spawn")
                    .loadout
                    .clone(),
            ))
        }

        let mobs = Form::filter(|f| f.is_mob() && f.at_depth(depth));
        for &loc in spawn_locs.into_iter() {
            self.spawns.push((
                loc,
                form::rand(rng, &mobs)
                    .expect("No mob spawn")
                    .loadout
                    .clone(),
            ))
        }

        // Map to actual terrains
        self.terrain.extend(map.iter().map(|(&loc, &t)| {
            let t = match t {
                Outside => Terrain::Empty,
                Unused => Terrain::Rock,
                Border => Terrain::Rock,
                Floor => Terrain::Ground,
                Wall => Terrain::Rock,
                Door => Terrain::Door,
            };
            (loc, t)
        }));

        // XXX: This thing needs to be more automatic
        // Make the backportal cell have transparent terrain
        self.terrain.insert(entrance - vec2(2, 2), Terrain::Empty);

        return exit_loc;

        fn can_be_path_down_opening(
            map: &Field<Prototerrain>,
            origin: Location,
            loc: Location,
        ) -> bool {
            const MIN_EXIT_DISTANCE: i32 = 12;

            loc.metric_distance(origin) > MIN_EXIT_DISTANCE && map.get(loc) == Floor &&
                map.get(loc + Dir6::North) == Floor &&
                map.get(loc + Dir6::Northeast) != Floor &&
                map.get(loc + Dir6::Southeast) != Floor &&
                map.get(loc + Dir6::South) != Floor &&
                map.get(loc + Dir6::Southwest) != Floor &&
                map.get(loc + Dir6::Northwest) != Floor
        }
    }

    /// Make a cave entrance going down.
    fn cave_entrance(&mut self, loc: Location, cave_start: Location) {
        const DOWNBOUND_ENCLOSURE: [(i32, i32); 5] = [(1, 0), (0, 1), (2, 1), (1, 2), (2, 2)];

        for &v in &DOWNBOUND_ENCLOSURE {
            let loc = loc + vec2(v.0, v.1);
            self.terrain.insert(loc, Terrain::Rock);
        }

        self.terrain.insert(loc, Terrain::Ground);
        self.terrain.insert(loc + vec2(1, 1), Terrain::Ground);

        // Connect the points
        self.portal(loc + vec2(1, 1), cave_start);
        self.portal(cave_start - vec2(1, 1), loc);
    }

    /// Punch a (one-way) portal between two points.
    fn portal(&mut self, origin: Location, destination: Location) {
        self.portals.insert(
            origin,
            Portal::new(origin, destination),
        );
        self.terrain.insert(origin, Terrain::Empty);
    }
}

impl serde::Serialize for Worldgen {
    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
        self.seed.serialize(s)
    }
}

impl<'a> serde::Deserialize<'a> for Worldgen {
    fn deserialize<D: serde::Deserializer<'a>>(d: D) -> Result<Self, D::Error> {
        Ok(Worldgen::new(serde::Deserialize::deserialize(d)?))
    }
}

#[derive(Copy, Clone, Eq, PartialEq, Debug)]
enum Prototerrain {
    /// Area completely outside the map to be generated.
    Outside,
    /// Area that should be filled with map content but hasn't been yet.
    Unused,
    /// Area that the player cannot enter but that may be visible on screen.
    Border,
    Floor,
    Wall,
    Door,
}

impl Default for Prototerrain {
    fn default() -> Self { Prototerrain::Outside }
}

fn screen_map(origin: Location) -> Field<Prototerrain> {
    let mut ret = Field::new();

    // Cells two cells away from the live area at the bottom of the screen can still affect
    // the visible terrain via cell reshaping, so add those as well to the border.
    const BORDER_MASK: [(i32, i32); 9] = [
        (-1, -1),
        (0, -1),
        (1, 0),
        (1, 1),
        (0, 1),
        (-1, 0),
        (2, 1),
        (2, 2),
        (1, 2),
    ];

    for &v in ::onscreen_locations() {
        let loc = origin + v;
        // Paint border for every cell, all except the one from the cells at the actual border will
        // be overwritten.
        for d in &BORDER_MASK {
            let vec = vec2(d.0, d.1);
            if ret.get(loc + vec) == Default::default() {
                ret.set(loc + vec, Prototerrain::Border);
            }
        }

        ret.set(loc, Prototerrain::Unused);
    }

    ret
}

fn entry_cave_enclosure(map: &mut Field<Prototerrain>, entrance: Location) {
    use self::Prototerrain::*;

    debug_assert!(map.get(entrance) == Unused);

    const UPBOUND_ENCLOSURE: [(i32, i32); 7] = [
        (-2, -2),
        (-1, -2),
        (0, -1),
        (-1, 0),
        (-2, -1),
        (1, 0),
        (0, 1),
    ];

    for &v in &UPBOUND_ENCLOSURE {
        let loc = entrance + vec2(v.0, v.1);
        map.set(loc, Wall);
    }

    // Portal site
    map.set(entrance - vec2(1, 1), Floor);
    // Entrance cell
    map.set(entrance, Floor);
    // Enclosure mouth
    map.set(entrance + vec2(1, 1), Floor);
}
<commit_msg>Make cave mapgen less fiddly<commit_after>use Prefab;
use Rng;
use calx_grid::{Dijkstra, Dir6};
use euclid::vec2;
use field::Field;
use form::{self, Form};
use location::{Location, Portal};
use rand::{self, SeedableRng};
use serde;
use std::cmp;
use std::collections::BTreeSet;
use std::collections::HashMap;
use std::slice;
use terrain::Terrain;
use world::Loadout;

/// Static generated world.
pub struct Worldgen {
    seed: u32,
    terrain: HashMap<Location, Terrain>,
    portals: HashMap<Location, Portal>,
    spawns: Vec<(Location, Loadout)>,
    player_entry: Location,
}

impl Worldgen {
    pub fn new(seed: u32) -> Worldgen {
        let mut ret = Worldgen {
            seed: seed,
            terrain: HashMap::new(),
            portals: HashMap::new(),
            spawns: Vec::new(),
            player_entry: Location::new(0, 0, 0),
        };

        let mut rng: Rng = SeedableRng::from_seed([seed, seed, seed, seed]);

        let mut cave_entrance = Location::new(9, 0, 0);

        for cave_z in 1..11 {
            ret.cave_entrance(cave_entrance, Location::new(0, 0, cave_z));
            cave_entrance = ret.gen_caves(&mut rng, Location::new(0, 0, cave_z));
        }

        ret
    }

    fn load_prefab(&mut self, origin: Location, prefab: &Prefab) {
        for (p, &(ref terrain, ref entities)) in prefab.iter() {
            let loc = origin + p;

            self.terrain.insert(loc, *terrain);

            for spawn in entities.iter() {
                if spawn == "player" {
                    self.player_entry = loc;
                } else {
                    let form = Form::named(spawn).expect(&format!(
                        "Bad prefab: Form '{}' not found!",
                        spawn
                    ));
                    self.spawns.push((loc, form.loadout.clone()));
                }
            }
        }
    }

    pub fn seed(&self) -> u32 { self.seed }

    pub fn get_terrain(&self, loc: Location) -> Terrain {
        if let Some(&t) = self.terrain.get(&loc) {
            t
        } else {
            self.default_terrain(loc)
        }
    }

    fn default_terrain(&self, loc: Location) -> Terrain {
        use Terrain::*;
        if loc.z == 0 {
            match loc.noise() {
                n if n > 0.8 => Tree,
                n if n > -0.8 => Grass,
                _ => Water,
            }
        } else {
            Terrain::Rock
        }
    }

    pub fn get_portal(&self, loc: Location) -> Option<Location> {
        self.portals.get(&loc).map(|&p| loc + p)
    }

    pub fn spawns(&self) -> slice::Iter<(Location, Loadout)> { self.spawns.iter() }

    pub fn player_entry(&self) -> Location { self.player_entry }

    fn gen_caves<R: rand::Rng>(&mut self, rng: &mut R, entrance: Location) -> Location {
        use self::Prototerrain::*;

        let mut cells_to_dig = 700;

        let mut map = screen_map(Location::new(0, 0, entrance.z));

        debug_assert!(map.get(entrance) == Unused);

        // Create portal enclosure
        entry_cave_enclosure(&mut map, entrance);

        let entrance = entrance + vec2(1, 1);

        let mut edge: BTreeSet<Location> = Dir6::iter()
            .map(|&d| entrance + vec2(2, 2) + d)
            .filter(|&loc| map.get(loc) == Unused)
            .collect();

        // Arbitrary long iteration, should break after digging a sufficient number of cells before
        // this.
        for _ in 0..10000 {
            if edge.is_empty() {
                break;
            }

            let dig_loc = *rand::sample(rng, edge.iter(), 1)[0];

            // Prefer digging narrow corridors, there's an increasing chance to abort the dig when the
            // selected location is in a very open space.
            let adjacent_floors = Dir6::iter()
                .filter(|d| map.get(dig_loc + **d) == Floor)
                .count();
            if rng.gen_range(0, cmp::max(1, adjacent_floors * adjacent_floors)) != 0 {
                continue;
            }

            map.set(dig_loc, Floor);
            edge.extend(Dir6::iter().map(|&d| dig_loc + d).filter(|&loc| {
                map.get(loc) == Unused
            }));

            cells_to_dig -= 1;
            if cells_to_dig == 0 {
                break;
            }
        }

        // Postprocess
        let cells: Vec<_> = map.iter().map(|(&loc, &c)| (loc, c)).collect();
        for (loc, c) in cells.into_iter() {
            // Clear pillars
            if c == Unused {
                let adjacent_floors = Dir6::iter().filter(|d| map.get(loc + **d) == Floor).count();

                if adjacent_floors == 6 {
                    map.set(loc, Floor);
                }
            }
        }

        // Find opening for next map
        let openings: Vec<Location> = map.iter()
            .map(|(&loc, _)| loc)
            .filter(|&loc| can_be_path_down_opening(&map, entrance, loc))
            .collect();
        // XXX FIXME: There's actually no guarantees made that this can't fail
        // Fallback should be something like carving the exit to the bottom edge of the map
        assert!(!openings.is_empty(), "No exit candidates");
        let exit_loc = rand::sample(rng, openings, 1)[0];
        map.set(exit_loc, Border);

        // Spawns
        const MIN_DISTANCE_FROM_ENTRANCE: u32 = 10;
        let depth = entrance.z as i32;
        // Flood-fill the new map
        let mut spawn_map = Dijkstra::new(vec![entrance], |&loc| map.get(loc) == Floor, 10000)
            .weights;
        // Filter stuff too close to entrance
        spawn_map.retain(|_, &mut w| w >= MIN_DISTANCE_FROM_ENTRANCE);
        // Don't need weights anymore, convert to Vec.
        let mut spawn_locs: Vec<Location> = spawn_map.into_iter().map(|(loc, _)| loc).collect();
        // Filter stuff next to walls, only spawn in open areas
        spawn_locs.retain(|&loc| {
            Dir6::iter()
                .map(|&d| (map.get(loc + d) == Floor) as u32)
                .sum::<u32>() > 3
        });

        let mut spawn_locs = rand::sample(rng, spawn_locs.iter(), 20);
        let n_spawns = spawn_locs.len();

        let items = Form::filter(|f| f.is_item() && f.at_depth(depth));
        for &loc in spawn_locs.drain(0..n_spawns / 2) {
            self.spawns.push((
                loc,
                form::rand(rng, &items)
                    .expect("No item spawn")
                    .loadout
                    .clone(),
            ))
        }

        let mobs = Form::filter(|f| f.is_mob() && f.at_depth(depth));
        for &loc in spawn_locs.into_iter() {
            self.spawns.push((
                loc,
                form::rand(rng, &mobs)
                    .expect("No mob spawn")
                    .loadout
                    .clone(),
            ))
        }

        // Map to actual terrains
        self.terrain.extend(map.iter().map(|(&loc, &t)| {
            let t = match t {
                Outside => Terrain::Empty,
                Unused => Terrain::Rock,
                Border => Terrain::Rock,
                Floor => Terrain::Ground,
                Wall => Terrain::Rock,
                Door => Terrain::Door,
            };
            (loc, t)
        }));

        // XXX: This thing needs to be more automatic
        // Make the backportal cell have transparent terrain
        self.terrain.insert(entrance - vec2(2, 2), Terrain::Empty);

        return exit_loc;

        fn can_be_path_down_opening(
            map: &Field<Prototerrain>,
            origin: Location,
            loc: Location,
        ) -> bool {
            const MIN_EXIT_DISTANCE: i32 = 12;

            loc.metric_distance(origin) > MIN_EXIT_DISTANCE &&
                map.get(loc + Dir6::North) == Floor &&
                map.get(loc + Dir6::Northeast) != Floor &&
                map.get(loc + Dir6::Southeast) != Floor &&
                map.get(loc + Dir6::South) != Floor &&
                map.get(loc + Dir6::Southwest) != Floor &&
                map.get(loc + Dir6::Northwest) != Floor
        }
    }

    /// Make a cave entrance going down.
    fn cave_entrance(&mut self, loc: Location, cave_start: Location) {
        const DOWNBOUND_ENCLOSURE: [(i32, i32); 5] = [(1, 0), (0, 1), (2, 1), (1, 2), (2, 2)];

        for &v in &DOWNBOUND_ENCLOSURE {
            let loc = loc + vec2(v.0, v.1);
            self.terrain.insert(loc, Terrain::Rock);
        }

        self.terrain.insert(loc, Terrain::Ground);
        self.terrain.insert(loc + vec2(1, 1), Terrain::Ground);

        // Connect the points
        self.portal(loc + vec2(1, 1), cave_start);
        self.portal(cave_start - vec2(1, 1), loc);
    }

    /// Punch a (one-way) portal between two points.
    fn portal(&mut self, origin: Location, destination: Location) {
        self.portals.insert(
            origin,
            Portal::new(origin, destination),
        );
        self.terrain.insert(origin, Terrain::Empty);
    }
}

impl serde::Serialize for Worldgen {
    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
        self.seed.serialize(s)
    }
}

impl<'a> serde::Deserialize<'a> for Worldgen {
    fn deserialize<D: serde::Deserializer<'a>>(d: D) -> Result<Self, D::Error> {
        Ok(Worldgen::new(serde::Deserialize::deserialize(d)?))
    }
}

#[derive(Copy, Clone, Eq, PartialEq, Debug)]
enum Prototerrain {
    /// Area completely outside the map to be generated.
    Outside,
    /// Area that should be filled with map content but hasn't been yet.
    Unused,
    /// Area that the player cannot enter but that may be visible on screen.
    Border,
    Floor,
    Wall,
    Door,
}

impl Default for Prototerrain {
    fn default() -> Self { Prototerrain::Outside }
}

fn screen_map(origin: Location) -> Field<Prototerrain> {
    let mut ret = Field::new();

    // Cells two cells away from the live area at the bottom of the screen can still affect
    // the visible terrain via cell reshaping, so add those as well to the border.
    const BORDER_MASK: [(i32, i32); 9] = [
        (-1, -1),
        (0, -1),
        (1, 0),
        (1, 1),
        (0, 1),
        (-1, 0),
        (2, 1),
        (2, 2),
        (1, 2),
    ];

    for &v in ::onscreen_locations() {
        let loc = origin + v;
        // Paint border for every cell, all except the one from the cells at the actual border will
        // be overwritten.
        for d in &BORDER_MASK {
            let vec = vec2(d.0, d.1);
            if ret.get(loc + vec) == Default::default() {
                ret.set(loc + vec, Prototerrain::Border);
            }
        }

        ret.set(loc, Prototerrain::Unused);
    }

    ret
}

fn entry_cave_enclosure(map: &mut Field<Prototerrain>, entrance: Location) {
    use self::Prototerrain::*;

    debug_assert!(map.get(entrance) == Unused);

    const UPBOUND_ENCLOSURE: [(i32, i32); 7] = [
        (-2, -2),
        (-1, -2),
        (0, -1),
        (-1, 0),
        (-2, -1),
        (1, 0),
        (0, 1),
    ];

    for &v in &UPBOUND_ENCLOSURE {
        let loc = entrance + vec2(v.0, v.1);
        map.set(loc, Wall);
    }

    // Portal site
    map.set(entrance - vec2(1, 1), Floor);
    // Entrance cell
    map.set(entrance, Floor);
    // Enclosure mouth
    map.set(entrance + vec2(1, 1), Floor);
}
<|endoftext|>


<commit_before>use super::*;

// This is the runtime cursor on iteration
// It hold a copy of the containing page next page lock guard
// These lock guards are preventing the node and their neighbourhoods been changed externally
// Ordering are specified that can also change lock pattern
pub struct RTCursor<KS, PS>
where
    KS: Slice<EntryKey> + Debug + 'static,
    PS: Slice<NodeCellRef> + 'static,
{
    pub index: usize,
    pub ordering: Ordering,
    pub page: Option<NodeCellRef>,
    pub deleted: DeletionSet,
    pub marker: PhantomData<(KS, PS)>,
}

impl<KS, PS> RTCursor<KS, PS>
where
    KS: Slice<EntryKey> + Debug + 'static,
    PS: Slice<NodeCellRef> + 'static,
{
    pub fn new(pos: usize, page: &NodeCellRef, ordering: Ordering, deleted: &DeletionSet) -> Self {
        let mut cursor = RTCursor {
            index: pos,
            ordering,
            page: Some(page.clone()),
            deleted: deleted.clone(),
            marker: PhantomData,
        };
        match ordering {
            Ordering::Forward => {
                let len = read_node(page, |node: &NodeReadHandler<KS, PS>| node.len());
                if pos >= len {
                    cursor.next();
                }
            }
            Ordering::Backward => {}
        }
        cursor
    }
    fn boxed(self) -> Box<IndexCursor> {
        box self
    }

    fn next_candidate(&mut self) -> bool {
        if self.page.is_some() {
            let current_page = self.page.clone().unwrap();
            read_node(&current_page, |page: &NodeReadHandler<KS, PS>| {
                let ext_page = page.extnode();
                // debug!("Next id with index: {}, length: {}", self.index + 1, ext_page.len);
                match self.ordering {
                    Ordering::Forward => {
                        if page.is_empty() {
                            return read_node(
                                &ext_page.next,
                                |next_node: &NodeReadHandler<KS, PS>| {
                                    self.index = 0;
                                    self.page = Some(ext_page.next.clone());
                                    if next_node.is_none() {
                                        self.page = None;
                                        return false;
                                    } else if next_node.is_empty() {
                                        return self.next();
                                    } else if next_node.is_ext() {
                                        return true;
                                    } else {
                                        unreachable!()
                                    }
                                },
                            );
                        }
                        if self.index + 1 >= page.len() {
                            // next page
                            return read_node(
                                &ext_page.next,
                                |next_page: &NodeReadHandler<KS, PS>| {
                                    if !next_page.is_none() {
                                        // debug!("Shifting page forward, next page len: {}", next_page.len());
                                        self.index = 0;
                                        self.page = Some(ext_page.next.clone());
                                        return true;
                                    } else {
                                        // debug!("iterated to end");
                                        self.page = None;
                                        return false;
                                    }
                                },
                            );
                        } else {
                            self.index += 1;
                            // debug!("Advancing cursor to index {}", self.index);
                        }
                    }
                    Ordering::Backward => {
                        if page.is_empty() {
                            return read_node(
                                &ext_page.prev,
                                |prev_node: &NodeReadHandler<KS, PS>| {
                                    self.index = prev_node.len() - 1;
                                    self.page = Some(ext_page.prev.clone());
                                    if prev_node.is_none() {
                                        self.page = None;
                                        return false;
                                    } else if prev_node.is_empty() {
                                        return self.next();
                                    } else if prev_node.is_ext() {
                                        return true;
                                    } else {
                                        unreachable!()
                                    }
                                },
                            );
                        }
                        if self.index == 0 {
                            // next page
                            return read_node(
                                &ext_page.prev,
                                |prev_page: &NodeReadHandler<KS, PS>| {
                                    if !prev_page.is_none() {
                                        debug!("Shifting page backward");
                                        self.page = Some(ext_page.prev.clone());
                                        self.index = prev_page.len() - 1;
                                        return true;
                                    } else {
                                        debug!("iterated to end");
                                        self.page = None;
                                        return false;
                                    }
                                },
                            );
                        } else {
                            self.index -= 1;
                            // debug!("Advancing cursor to index {}", self.index);
                        }
                    }
                }
                true
            })
        } else {
            false
        }
    }
}

impl<KS, PS> IndexCursor for RTCursor<KS, PS>
where
    KS: Slice<EntryKey> + Debug + 'static,
    PS: Slice<NodeCellRef> + 'static,
{
    // TODO: Copy current after next
    fn next(&mut self) -> bool {
        loop {
            let has_candidate = self.next_candidate();
            if has_candidate {
                // search in deleted set and skip if exists
                if !self.deleted.read().contains(self.current().unwrap()) {
                    return true;
                }
            } else {
                return false;
            }
        }
    }

    // TODO: Use copied key reference
    fn current(&self) -> Option<&EntryKey> {
        if let &Some(ref page_ref) = &self.page {
            read_node(page_ref, |handler: &NodeReadHandler<KS, PS>| {
                let page = unsafe { &*handler.ptr };
                if self.index >= page.len() {
                    panic!(
                        "FIXME: cursor position overflowed {}/{}",
                        self.index,
                        page.len()
                    )
                }
                Some(&page.extnode().keys.as_slice_immute()[self.index])
            })
        } else {
            return None;
        }
    }
}
<commit_msg>simplify branches<commit_after>use super::*;

// This is the runtime cursor on iteration
// It hold a copy of the containing page next page lock guard
// These lock guards are preventing the node and their neighbourhoods been changed externally
// Ordering are specified that can also change lock pattern
pub struct RTCursor<KS, PS>
where
    KS: Slice<EntryKey> + Debug + 'static,
    PS: Slice<NodeCellRef> + 'static,
{
    pub index: usize,
    pub ordering: Ordering,
    pub page: Option<NodeCellRef>,
    pub deleted: DeletionSet,
    pub marker: PhantomData<(KS, PS)>,
}

impl<KS, PS> RTCursor<KS, PS>
where
    KS: Slice<EntryKey> + Debug + 'static,
    PS: Slice<NodeCellRef> + 'static,
{
    pub fn new(pos: usize, page: &NodeCellRef, ordering: Ordering, deleted: &DeletionSet) -> Self {
        let mut cursor = RTCursor {
            index: pos,
            ordering,
            page: Some(page.clone()),
            deleted: deleted.clone(),
            marker: PhantomData,
        };
        match ordering {
            Ordering::Forward => {
                let len = read_node(page, |node: &NodeReadHandler<KS, PS>| node.len());
                if pos >= len {
                    cursor.next();
                }
            }
            Ordering::Backward => {}
        }
        cursor
    }
    fn boxed(self) -> Box<IndexCursor> {
        box self
    }

    fn next_candidate(&mut self) -> bool {
        if self.page.is_some() {
            let current_page = self.page.clone().unwrap();
            read_node(&current_page, |page: &NodeReadHandler<KS, PS>| {
                let ext_page = page.extnode();
                // debug!("Next id with index: {}, length: {}", self.index + 1, ext_page.len);
                match self.ordering {
                    Ordering::Forward => {
                        if page.is_empty() || self.index + 1 >= page.len() {
                            return read_node(
                                &ext_page.next,
                                |next_node: &NodeReadHandler<KS, PS>| {
                                    if next_node.is_none() {
                                        self.page = None;
                                        return false;
                                    } else if next_node.is_empty() {
                                        return self.next();
                                    } else if next_node.is_ext() {
                                        self.index = 0;
                                        self.page = Some(ext_page.next.clone());
                                        return true;
                                    } else {
                                        unreachable!()
                                    }
                                },
                            );
                        } else {
                            self.index += 1;
                            // debug!("Advancing cursor to index {}", self.index);
                        }
                    }
                    Ordering::Backward => {
                        if page.is_empty() || self.index == 0 {
                            return read_node(
                                &ext_page.prev,
                                |prev_node: &NodeReadHandler<KS, PS>| {
                                    if prev_node.is_none() {
                                        self.page = None;
                                        return false;
                                    } else if prev_node.is_empty() {
                                        return self.next();
                                    } else if prev_node.is_ext() {
                                        self.index = prev_node.len() - 1;
                                        self.page = Some(ext_page.prev.clone());
                                        return true;
                                    } else {
                                        unreachable!()
                                    }
                                },
                            );
                        } else {
                            self.index -= 1;
                            // debug!("Advancing cursor to index {}", self.index);
                        }
                    }
                }
                true
            })
        } else {
            false
        }
    }
}

impl<KS, PS> IndexCursor for RTCursor<KS, PS>
where
    KS: Slice<EntryKey> + Debug + 'static,
    PS: Slice<NodeCellRef> + 'static,
{
    // TODO: Copy current after next
    fn next(&mut self) -> bool {
        loop {
            let has_candidate = self.next_candidate();
            if has_candidate {
                // search in deleted set and skip if exists
                if !self.deleted.read().contains(self.current().unwrap()) {
                    return true;
                }
            } else {
                return false;
            }
        }
    }

    // TODO: Use copied key reference
    fn current(&self) -> Option<&EntryKey> {
        if let &Some(ref page_ref) = &self.page {
            read_node(page_ref, |handler: &NodeReadHandler<KS, PS>| {
                let page = unsafe { &*handler.ptr };
                if self.index >= page.len() {
                    panic!(
                        "FIXME: cursor position overflowed {}/{}",
                        self.index,
                        page.len()
                    )
                }
                Some(&page.extnode().keys.as_slice_immute()[self.index])
            })
        } else {
            return None;
        }
    }
}
<|endoftext|>


<commit_before>//  rpc-perf - RPC Performance Testing
//  Copyright 2015 Twitter, Inc
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.

extern crate time;
extern crate log;

use log::{Log, LogLevel, LogMetadata, LogRecord};

pub struct SimpleLogger;

impl log::Log for SimpleLogger {
    fn enabled(&self, metadata: &LogMetadata) -> bool {
        metadata.level() <= LogLevel::Trace
    }

    fn log(&self, record: &LogRecord) {
        if self.enabled(record.metadata()) {
            println!("{} {:<5} [{}] {}",
                     time::strftime("%Y-%m-%d %H:%M:%S", &time::now()).unwrap(),
                     record.level().to_string(),
                     "rpc-perf",
                     record.args());
        }
    }
}
<commit_msg>revert trace logging<commit_after>//  rpc-perf - RPC Performance Testing
//  Copyright 2015 Twitter, Inc
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.

extern crate time;
extern crate log;

use log::{Log, LogLevel, LogMetadata, LogRecord};

pub struct SimpleLogger;

impl log::Log for SimpleLogger {
    fn enabled(&self, metadata: &LogMetadata) -> bool {
        metadata.level() <= LogLevel::Debug
    }

    fn log(&self, record: &LogRecord) {
        if self.enabled(record.metadata()) && record.location().module_path() == "rpc_perf" {
            println!("{} {:<5} [{}] {}",
                     time::strftime("%Y-%m-%d %H:%M:%S", &time::now()).unwrap(),
                     record.level().to_string(),
                     "rpc-perf",
                     record.args());
        }
    }
}
<|endoftext|>


<commit_before>export build_display_list;

import dl = display_list;
import dom::rcu::Scope;
import dom::base::{Text, NodeScope};
import gfx::geometry::{au, au_to_px, box, px_to_au};
import gfx::renderer;
import layout::base::*;
import util::color::methods;
import util::tree;
import box_builder::box_builder_methods;
import text::text_layout_methods;
import geom::size::Size2D;
import geom::point::Point2D;
import geom::rect::Rect;

#[doc = "

Builds a display list for a box and all its children

"]
fn build_display_list(box : @Box) -> dl::display_list {
    ret build_display_list_from_origin(box, Point2D(au(0), au(0)));
}

#[doc="

Builds a display list for a box and all its children.

# Arguments

* `box` - The box to build the display list for.
* `origin` - The coordinates of upper-left corner of the box containing the
             passed-in box.

"]
fn build_display_list_from_origin(box: @Box, origin: Point2D<au>)
    -> dl::display_list {
    let box_origin = Point2D(
        px_to_au(au_to_px(origin.x) + au_to_px(box.bounds.origin.x)),
        px_to_au(au_to_px(origin.y) + au_to_px(box.bounds.origin.y)));
    #debug("Handed origin %?, box has bounds %?, starting with origin %?", origin, copy box.bounds, box_origin);

    let mut list = box_to_display_items(box, box_origin);

    for btree.each_child(box) {|c|
        #debug("Recursively building display list with origin %?", box_origin);
        list += build_display_list_from_origin(c, box_origin);
    }

    #debug("display_list: %?", list);
    ret list;
}

#[doc="

Creates a display list item for a single block.
Args:
-box: the box to build the display list for
-origin: the coordinates of upper-left corner of the passed in box.

"]
fn box_to_display_items(box: @Box, origin: Point2D<au>) -> [dl::display_item] {
    let mut items = [];

    #debug("request to display a box from origin %?", origin);

    let bounds = Rect(origin, copy box.bounds.size);

    alt (box.kind, box.appearance.background_image, box.appearance.background_color) {
      (TextBox(subbox), _, _) {
        let run = copy subbox.run;
        assert run.is_some();
        items += [dl::display_item({
            item_type: dl::display_item_text(run.get()),
            bounds: bounds
        })];
      }
      (_, some(image), some(*)) | (_, some(image), none) {
        items += [dl::display_item({
            item_type: dl::display_item_image(~copy *image),
            bounds: bounds
        })];
      }
      (_, none, some(col)) {
        #debug("Assigning color %? to box with bounds %?", col, bounds);
        items += [dl::display_item({
            item_type: dl::display_item_solid_color(col.red, col.green, col.blue),
            bounds: bounds
        })];
      }
      (_, none, none) {
        let r = rand::rng();
        items += [dl::display_item({
            item_type: dl::display_item_solid_color(r.next() as u8,
                                                    r.next() as u8,
                                                    r.next() as u8),
            bounds: bounds
        })];
      }
    }

    #debug("layout: display items: %?", items);
    ret items;
}

fn should_convert_text_boxes_to_text_items() {
    #[test];

    let s = Scope();
    let n = s.new_node(Text("firecracker"));
    let b = n.construct_boxes();
    let subbox = alt check b.kind { TextBox(subbox) { subbox } };
    b.reflow_text(px_to_au(800), subbox);
    let di = box_to_display_items(b, Point2D(px_to_au(0), px_to_au(0)));

    alt di[0].item_type {
      dl::display_item_text(_) { }
      _ { fail }
    }
}

fn should_calculate_the_bounds_of_the_text_box() {
    #[test];

    let s = Scope();
    let n = s.new_node(Text("firecracker"));
    let b = n.construct_boxes();
    let subbox = alt check b.kind { TextBox(subbox) { subbox } };
    b.reflow_text(px_to_au(800), subbox);
    let di = box_to_display_items(b, Point2D(px_to_au(0), px_to_au(0)));

    let expected = Rect(
        Point2D(px_to_au(0), px_to_au(0)),
        Size2D(px_to_au(110), px_to_au(14))
    );

    #error("%?", di[0].bounds);
    #error("%?", expected);

    assert di[0].bounds == expected;
}
<commit_msg>Text boxes yield two display items, a bg color and a text item<commit_after>export build_display_list;

import dl = display_list;
import dom::rcu::Scope;
import dom::base::{Text, NodeScope};
import gfx::geometry::{au, au_to_px, box, px_to_au};
import gfx::renderer;
import layout::base::*;
import util::color::methods;
import util::tree;
import box_builder::box_builder_methods;
import text::text_layout_methods;
import geom::size::Size2D;
import geom::point::Point2D;
import geom::rect::Rect;

#[doc = "

Builds a display list for a box and all its children

"]
fn build_display_list(box : @Box) -> dl::display_list {
    ret build_display_list_from_origin(box, Point2D(au(0), au(0)));
}

#[doc="

Builds a display list for a box and all its children.

# Arguments

* `box` - The box to build the display list for.
* `origin` - The coordinates of upper-left corner of the box containing the
             passed-in box.

"]
fn build_display_list_from_origin(box: @Box, origin: Point2D<au>)
    -> dl::display_list {
    let box_origin = Point2D(
        px_to_au(au_to_px(origin.x) + au_to_px(box.bounds.origin.x)),
        px_to_au(au_to_px(origin.y) + au_to_px(box.bounds.origin.y)));
    #debug("Handed origin %?, box has bounds %?, starting with origin %?", origin, copy box.bounds, box_origin);

    let mut list = box_to_display_items(box, box_origin);

    for btree.each_child(box) {|c|
        #debug("Recursively building display list with origin %?", box_origin);
        list += build_display_list_from_origin(c, box_origin);
    }

    #debug("display_list: %?", list);
    ret list;
}

#[doc="

Creates a display list item for a single block.
Args:
-box: the box to build the display list for
-origin: the coordinates of upper-left corner of the passed in box.

"]
fn box_to_display_items(box: @Box, origin: Point2D<au>) -> [dl::display_item] {
    let mut items = [];

    #debug("request to display a box from origin %?", origin);

    let bounds = Rect(origin, copy box.bounds.size);

    alt (box.kind, box.appearance.background_image, box.appearance.background_color) {
      (TextBox(subbox), _, _) {
        let run = copy subbox.run;
        assert run.is_some();
        items += [
            dl::display_item({
                item_type: dl::display_item_solid_color(255u8, 255u8, 255u8),
                bounds: bounds
            }),
            dl::display_item({
                item_type: dl::display_item_text(run.get()),
                bounds: bounds
            })
        ];
      }
      (_, some(image), some(*)) | (_, some(image), none) {
        items += [dl::display_item({
            item_type: dl::display_item_image(~copy *image),
            bounds: bounds
        })];
      }
      (_, none, some(col)) {
        #debug("Assigning color %? to box with bounds %?", col, bounds);
        items += [dl::display_item({
            item_type: dl::display_item_solid_color(col.red, col.green, col.blue),
            bounds: bounds
        })];
      }
      (_, none, none) {
        let r = rand::rng();
        items += [dl::display_item({
            item_type: dl::display_item_solid_color(r.next() as u8,
                                                    r.next() as u8,
                                                    r.next() as u8),
            bounds: bounds
        })];
      }
    }

    #debug("layout: display items: %?", items);
    ret items;
}

fn should_convert_text_boxes_to_solid_color_background_items() {
    #[test];

    let s = Scope();
    let n = s.new_node(Text("firecracker"));
    let b = n.construct_boxes();
    let subbox = alt check b.kind { TextBox(subbox) { subbox } };
    b.reflow_text(px_to_au(800), subbox);
    let di = box_to_display_items(b, Point2D(px_to_au(0), px_to_au(0)));

    alt di[0].item_type {
      dl::display_item_solid_color(*) { }
      _ { fail }
    }

}

fn should_convert_text_boxes_to_text_items() {
    #[test];

    let s = Scope();
    let n = s.new_node(Text("firecracker"));
    let b = n.construct_boxes();
    let subbox = alt check b.kind { TextBox(subbox) { subbox } };
    b.reflow_text(px_to_au(800), subbox);
    let di = box_to_display_items(b, Point2D(px_to_au(0), px_to_au(0)));

    alt di[1].item_type {
      dl::display_item_text(_) { }
      _ { fail }
    }
}

fn should_calculate_the_bounds_of_the_text_box_background_color() {
    #[test];

    let s = Scope();
    let n = s.new_node(Text("firecracker"));
    let b = n.construct_boxes();
    let subbox = alt check b.kind { TextBox(subbox) { subbox } };
    b.reflow_text(px_to_au(800), subbox);
    let di = box_to_display_items(b, Point2D(px_to_au(0), px_to_au(0)));

    let expected = Rect(
        Point2D(px_to_au(0), px_to_au(0)),
        Size2D(px_to_au(110), px_to_au(14))
    );

    assert di[0].bounds == expected;
}

fn should_calculate_the_bounds_of_the_text_items() {
    #[test];

    let s = Scope();
    let n = s.new_node(Text("firecracker"));
    let b = n.construct_boxes();
    let subbox = alt check b.kind { TextBox(subbox) { subbox } };
    b.reflow_text(px_to_au(800), subbox);
    let di = box_to_display_items(b, Point2D(px_to_au(0), px_to_au(0)));

    let expected = Rect(
        Point2D(px_to_au(0), px_to_au(0)),
        Size2D(px_to_au(110), px_to_au(14))
    );

    assert di[1].bounds == expected;
}
<|endoftext|>


<commit_before>// We disable tail merging here because it can't preserve debuginfo and thus
// potentially breaks the backtraces. Also, subtle changes can decide whether
// tail merging succeeds, so the test might work today but fail tomorrow due to a
// seemingly completely unrelated change.
// Unfortunately, LLVM has no "disable" option for this, so we have to set
// "enable" to 0 instead.

// compile-flags:-g -Cllvm-args=-enable-tail-merge=0 -Cllvm-args=-opt-bisect-limit=0
// compile-flags:-Cforce-frame-pointers=yes
// ignore-pretty issue #37195
// ignore-cloudabi spawning processes is not supported
// ignore-emscripten spawning processes is not supported

// note that above `-opt-bisect-limit=0` is used to basically disable
// optimizations

use std::env;

#[path = "backtrace-debuginfo-aux.rs"] mod aux;

macro_rules! pos {
    () => ((file!(), line!()))
}

macro_rules! dump_and_die {
    ($($pos:expr),*) => ({
        // FIXME(#18285): we cannot include the current position because
        // the macro span takes over the last frame's file/line.
        if cfg!(any(target_os = "android",
                    all(target_os = "linux", target_arch = "arm"),
                    target_os = "freebsd",
                    target_os = "dragonfly",
                    target_os = "bitrig",
                    target_os = "openbsd")) {
            // skip these platforms as this support isn't implemented yet.
        } else {
            dump_filelines(&[$($pos),*]);
            panic!();
        }
    })
}

// we can't use a function as it will alter the backtrace
macro_rules! check {
    ($counter:expr; $($pos:expr),*) => ({
        if *$counter == 0 {
            dump_and_die!($($pos),*)
        } else {
            *$counter -= 1;
        }
    })
}

type Pos = (&'static str, u32);

// this goes to stdout and each line has to be occurred
// in the following backtrace to stderr with a correct order.
fn dump_filelines(filelines: &[Pos]) {
    for &(file, line) in filelines.iter().rev() {
        // extract a basename
        let basename = file.split(&['/', '\\'][..]).last().unwrap();
        println!("{}:{}", basename, line);
    }
}

#[inline(never)]
fn inner(counter: &mut i32, main_pos: Pos, outer_pos: Pos) {
    check!(counter; main_pos, outer_pos);
    check!(counter; main_pos, outer_pos);
    let inner_pos = pos!(); aux::callback(|aux_pos| {
        check!(counter; main_pos, outer_pos, inner_pos, aux_pos);
    });
    let inner_pos = pos!(); aux::callback_inlined(|aux_pos| {
        check!(counter; main_pos, outer_pos, inner_pos, aux_pos);
    });
}

// We emit the wrong location for the caller here when inlined on MSVC
#[cfg_attr(not(target_env = "msvc"), inline(always))]
#[cfg_attr(target_env = "msvc", inline(never))]
fn inner_inlined(counter: &mut i32, main_pos: Pos, outer_pos: Pos) {
    check!(counter; main_pos, outer_pos);
    check!(counter; main_pos, outer_pos);

    // Again, disable inlining for MSVC.
    #[cfg_attr(not(target_env = "msvc"), inline(always))]
    #[cfg_attr(target_env = "msvc", inline(never))]
    fn inner_further_inlined(counter: &mut i32, main_pos: Pos, outer_pos: Pos, inner_pos: Pos) {
        check!(counter; main_pos, outer_pos, inner_pos);
    }
    inner_further_inlined(counter, main_pos, outer_pos, pos!());

    let inner_pos = pos!(); aux::callback(|aux_pos| {
        check!(counter; main_pos, outer_pos, inner_pos, aux_pos);
    });
    let inner_pos = pos!(); aux::callback_inlined(|aux_pos| {
        check!(counter; main_pos, outer_pos, inner_pos, aux_pos);
    });

    // this tests a distinction between two independent calls to the inlined function.
    // (un)fortunately, LLVM somehow merges two consecutive such calls into one node.
    inner_further_inlined(counter, main_pos, outer_pos, pos!());
}

#[inline(never)]
fn outer(mut counter: i32, main_pos: Pos) {
    inner(&mut counter, main_pos, pos!());
    inner_inlined(&mut counter, main_pos, pos!());
}

fn check_trace(output: &str, error: &str) -> Result<(), String> {
    // reverse the position list so we can start with the last item (which was the first line)
    let mut remaining: Vec<&str> = output.lines().map(|s| s.trim()).rev().collect();

    if !error.contains("stack backtrace") {
        return Err(format!("no backtrace found in stderr:\n{}", error))
    }
    for line in error.lines() {
        if !remaining.is_empty() && line.contains(remaining.last().unwrap()) {
            remaining.pop();
        }
    }
    if !remaining.is_empty() {
        return Err(format!("trace does not match position list\n\
            still need to find {:?}\n\n\
            --- stdout\n{}\n\
            --- stderr\n{}",
            remaining, output, error))
    }
    Ok(())
}

fn run_test(me: &str) {
    use std::str;
    use std::process::Command;

    let mut i = 0;
    let mut errors = Vec::new();
    loop {
        let out = Command::new(me)
                          .env("RUST_BACKTRACE", "full")
                          .arg(i.to_string()).output().unwrap();
        let output = str::from_utf8(&out.stdout).unwrap();
        let error = str::from_utf8(&out.stderr).unwrap();
        if out.status.success() {
            assert!(output.contains("done."), "bad output for successful run: {}", output);
            break;
        } else {
            if let Err(e) = check_trace(output, error) {
                errors.push(e);
            }
        }
        i += 1;
    }
    if errors.len() > 0 {
        for error in errors {
            println!("---------------------------------------");
            println!("{}", error);
        }

        panic!("found some errors");
    }
}

#[inline(never)]
fn main() {
    let args: Vec<String> = env::args().collect();
    if args.len() >= 2 {
        let case = args[1].parse().unwrap();
        eprintln!("test case {}", case);
        outer(case, pos!());
        println!("done.");
    } else {
        run_test(&args[0]);
    }
}
<commit_msg>fix output test for backtrace-debuginfo.rs<commit_after>// We disable tail merging here because it can't preserve debuginfo and thus
// potentially breaks the backtraces. Also, subtle changes can decide whether
// tail merging succeeds, so the test might work today but fail tomorrow due to a
// seemingly completely unrelated change.
// Unfortunately, LLVM has no "disable" option for this, so we have to set
// "enable" to 0 instead.

// compile-flags:-g -Cllvm-args=-enable-tail-merge=0 -Cllvm-args=-opt-bisect-limit=0
// compile-flags:-Cforce-frame-pointers=yes
// ignore-pretty issue #37195
// ignore-cloudabi spawning processes is not supported
// ignore-emscripten spawning processes is not supported
// normalize-stderr-test ".*\n" -> ""

// Note that above `-opt-bisect-limit=0` is used to basically disable
// optimizations. It creates tons of output on stderr, hence we normalize
// that away entirely.

use std::env;

#[path = "backtrace-debuginfo-aux.rs"] mod aux;

macro_rules! pos {
    () => ((file!(), line!()))
}

macro_rules! dump_and_die {
    ($($pos:expr),*) => ({
        // FIXME(#18285): we cannot include the current position because
        // the macro span takes over the last frame's file/line.
        if cfg!(any(target_os = "android",
                    all(target_os = "linux", target_arch = "arm"),
                    target_os = "freebsd",
                    target_os = "dragonfly",
                    target_os = "bitrig",
                    target_os = "openbsd")) {
            // skip these platforms as this support isn't implemented yet.
        } else {
            dump_filelines(&[$($pos),*]);
            panic!();
        }
    })
}

// we can't use a function as it will alter the backtrace
macro_rules! check {
    ($counter:expr; $($pos:expr),*) => ({
        if *$counter == 0 {
            dump_and_die!($($pos),*)
        } else {
            *$counter -= 1;
        }
    })
}

type Pos = (&'static str, u32);

// this goes to stdout and each line has to be occurred
// in the following backtrace to stderr with a correct order.
fn dump_filelines(filelines: &[Pos]) {
    for &(file, line) in filelines.iter().rev() {
        // extract a basename
        let basename = file.split(&['/', '\\'][..]).last().unwrap();
        println!("{}:{}", basename, line);
    }
}

#[inline(never)]
fn inner(counter: &mut i32, main_pos: Pos, outer_pos: Pos) {
    check!(counter; main_pos, outer_pos);
    check!(counter; main_pos, outer_pos);
    let inner_pos = pos!(); aux::callback(|aux_pos| {
        check!(counter; main_pos, outer_pos, inner_pos, aux_pos);
    });
    let inner_pos = pos!(); aux::callback_inlined(|aux_pos| {
        check!(counter; main_pos, outer_pos, inner_pos, aux_pos);
    });
}

// We emit the wrong location for the caller here when inlined on MSVC
#[cfg_attr(not(target_env = "msvc"), inline(always))]
#[cfg_attr(target_env = "msvc", inline(never))]
fn inner_inlined(counter: &mut i32, main_pos: Pos, outer_pos: Pos) {
    check!(counter; main_pos, outer_pos);
    check!(counter; main_pos, outer_pos);

    // Again, disable inlining for MSVC.
    #[cfg_attr(not(target_env = "msvc"), inline(always))]
    #[cfg_attr(target_env = "msvc", inline(never))]
    fn inner_further_inlined(counter: &mut i32, main_pos: Pos, outer_pos: Pos, inner_pos: Pos) {
        check!(counter; main_pos, outer_pos, inner_pos);
    }
    inner_further_inlined(counter, main_pos, outer_pos, pos!());

    let inner_pos = pos!(); aux::callback(|aux_pos| {
        check!(counter; main_pos, outer_pos, inner_pos, aux_pos);
    });
    let inner_pos = pos!(); aux::callback_inlined(|aux_pos| {
        check!(counter; main_pos, outer_pos, inner_pos, aux_pos);
    });

    // this tests a distinction between two independent calls to the inlined function.
    // (un)fortunately, LLVM somehow merges two consecutive such calls into one node.
    inner_further_inlined(counter, main_pos, outer_pos, pos!());
}

#[inline(never)]
fn outer(mut counter: i32, main_pos: Pos) {
    inner(&mut counter, main_pos, pos!());
    inner_inlined(&mut counter, main_pos, pos!());
}

fn check_trace(output: &str, error: &str) -> Result<(), String> {
    // reverse the position list so we can start with the last item (which was the first line)
    let mut remaining: Vec<&str> = output.lines().map(|s| s.trim()).rev().collect();

    if !error.contains("stack backtrace") {
        return Err(format!("no backtrace found in stderr:\n{}", error))
    }
    for line in error.lines() {
        if !remaining.is_empty() && line.contains(remaining.last().unwrap()) {
            remaining.pop();
        }
    }
    if !remaining.is_empty() {
        return Err(format!("trace does not match position list\n\
            still need to find {:?}\n\n\
            --- stdout\n{}\n\
            --- stderr\n{}",
            remaining, output, error))
    }
    Ok(())
}

fn run_test(me: &str) {
    use std::str;
    use std::process::Command;

    let mut i = 0;
    let mut errors = Vec::new();
    loop {
        let out = Command::new(me)
                          .env("RUST_BACKTRACE", "full")
                          .arg(i.to_string()).output().unwrap();
        let output = str::from_utf8(&out.stdout).unwrap();
        let error = str::from_utf8(&out.stderr).unwrap();
        if out.status.success() {
            assert!(output.contains("done."), "bad output for successful run: {}", output);
            break;
        } else {
            if let Err(e) = check_trace(output, error) {
                errors.push(e);
            }
        }
        i += 1;
    }
    if errors.len() > 0 {
        for error in errors {
            println!("---------------------------------------");
            println!("{}", error);
        }

        panic!("found some errors");
    }
}

#[inline(never)]
fn main() {
    let args: Vec<String> = env::args().collect();
    if args.len() >= 2 {
        let case = args[1].parse().unwrap();
        eprintln!("test case {}", case);
        outer(case, pos!());
        println!("done.");
    } else {
        run_test(&args[0]);
    }
}
<|endoftext|>


<commit_before>//! `StructureWall` data description.
use crate::data::RoomName;

with_update_struct! {
    /// Describes the decay of a decaying wall.
    #[derive(serde_derive::Deserialize, Clone, Debug, PartialEq)]
    #[serde(rename_all = "camelCase")]
    pub struct WallDecayTime {
        /// Unix timestamp of when this wall will decay.
        pub timestamp: u64,
    }

    /// The update structure for a wall decay description.
    #[derive(serde_derive::Deserialize, Clone, Debug)]
    #[serde(rename_all = "camelCase")]
    pub struct WallDecayTimeUpdate { ... }
}

with_structure_fields_and_update_struct! {
    /// A wall structure - a structure that has a large amount of possible hit points.
    #[derive(Clone, Debug, PartialEq)]
    #[serde(rename_all = "camelCase")]
    pub struct StructureWall {
        /// Whether or not an attack on this structure will send an email to the owner automatically.
        #[serde(default)]
        pub notify_when_attacked: bool,
        /// If this wall is protecting a novice or respawn area, this holds the time the wall will decay at.
        pub decay_time: Option<WallDecayTime>,
    }

    /// The update structure for a wall structure.
    #[derive(Clone, Debug)]
    #[serde(rename_all = "camelCase")]
    pub struct StructureWallUpdate {
        - notify_when_attacked: bool,
        - decay_time: Option<WallDecayTime>,
    }
}

#[cfg(test)]
mod test {
    use serde::Deserialize;

    use crate::data::RoomName;

    use super::{StructureWall, WallDecayTime};

    #[test]
    fn parse_wall() {
        let json = json!({
            "_id": "584a5d89cbe27a302e4ba889",
            "hits": 7222461,
            "hitsMax": 300000000,
            "notifyWhenAttacked": true,
            "room": "W73N43",
            "type": "constructedWall",
            "x": 47,
            "y": 24
        });

        let obj = StructureWall::deserialize(json).unwrap();

        assert_eq!(
            obj,
            StructureWall {
                room: RoomName::new("W73N43").unwrap(),
                x: 47,
                y: 24,
                id: "584a5d89cbe27a302e4ba889".to_owned(),
                hits: 7222461,
                hits_max: 300000000,
                notify_when_attacked: true,
                decay_time: None,
            }
        );
    }

    #[test]
    fn parse_noob_wall() {
        let json = json!({
            "_id": "5cdf140a8e5fb05519843bc6",
            "decayTime": {
                "timestamp": 1559851447000u64
            },
            "room": "W5S35",
            "type": "constructedWall",
            "x": 29,
            "y": 0,
        });

        let obj = StructureWall::deserialize(json).unwrap();

        assert_eq!(
            obj,
            StructureWall {
                room: RoomName::new("W5S35").unwrap(),
                x: 29,
                y: 0,
                id: "5cdf140a8e5fb05519843bc6".to_owned(),
                hits: 0,
                hits_max: 0,
                notify_when_attacked: false,
                decay_time: Some(WallDecayTime {
                    timestamp: 1559851447000u64
                }),
            }
        );
    }
}
<commit_msg>Fix wall's end time to be a Timespec<commit_after>//! `StructureWall` data description.
use crate::data::{optional_timespec_seconds, timespec_seconds, RoomName};

with_update_struct! {
    /// Describes the decay of a decaying wall.
    #[derive(serde_derive::Deserialize, Clone, Debug, PartialEq)]
    #[serde(rename_all = "camelCase")]
    pub struct WallDecayTime {
        /// Unix timestamp of when this wall will decay.
        #[serde(with = "timespec_seconds")]
        pub timestamp: time::Timespec,
    }

    /// The update structure for a wall decay description.
    #[derive(serde_derive::Deserialize, Clone, Debug)]
    #[serde(rename_all = "camelCase")]
    pub struct WallDecayTimeUpdate {
        #[serde(with = "optional_timespec_seconds")]
        (no_extra_meta)
        - timestamp: time::Timespec,
    }
}

with_structure_fields_and_update_struct! {
    /// A wall structure - a structure that has a large amount of possible hit points.
    #[derive(Clone, Debug, PartialEq)]
    #[serde(rename_all = "camelCase")]
    pub struct StructureWall {
        /// Whether or not an attack on this structure will send an email to the owner automatically.
        #[serde(default)]
        pub notify_when_attacked: bool,
        /// If this wall is protecting a novice or respawn area, this holds the time the wall will decay at.
        pub decay_time: Option<WallDecayTime>,
    }

    /// The update structure for a wall structure.
    #[derive(Clone, Debug)]
    #[serde(rename_all = "camelCase")]
    pub struct StructureWallUpdate {
        - notify_when_attacked: bool,
        - decay_time: Option<WallDecayTime>,
    }
}

#[cfg(test)]
mod test {
    use serde::Deserialize;

    use crate::data::RoomName;

    use super::{StructureWall, WallDecayTime};

    #[test]
    fn parse_wall() {
        let json = json!({
            "_id": "584a5d89cbe27a302e4ba889",
            "hits": 7222461,
            "hitsMax": 300000000,
            "notifyWhenAttacked": true,
            "room": "W73N43",
            "type": "constructedWall",
            "x": 47,
            "y": 24
        });

        let obj = StructureWall::deserialize(json).unwrap();

        assert_eq!(
            obj,
            StructureWall {
                room: RoomName::new("W73N43").unwrap(),
                x: 47,
                y: 24,
                id: "584a5d89cbe27a302e4ba889".to_owned(),
                hits: 7222461,
                hits_max: 300000000,
                notify_when_attacked: true,
                decay_time: None,
            }
        );
    }

    #[test]
    fn parse_noob_wall() {
        let json = json!({
            "_id": "5cdf140a8e5fb05519843bc6",
            "decayTime": {
                "timestamp": 1559851447000u64
            },
            "room": "W5S35",
            "type": "constructedWall",
            "x": 29,
            "y": 0,
        });

        let obj = StructureWall::deserialize(json).unwrap();

        assert_eq!(
            obj,
            StructureWall {
                room: RoomName::new("W5S35").unwrap(),
                x: 29,
                y: 0,
                id: "5cdf140a8e5fb05519843bc6".to_owned(),
                hits: 0,
                hits_max: 0,
                notify_when_attacked: false,
                decay_time: Some(WallDecayTime {
                    timestamp: time::Timespec::new(1559851447000i64, 0)
                }),
            }
        );
    }
}
<|endoftext|>