<commit_before>// Copyright (c) 2015-2019 William (B.J.) Snow Orvis
//
// 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.

//! A Seed application that runs and renders various fractal curves.

use fractal_lib::SelectedFractal;
use std::str::FromStr;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::{HtmlCanvasElement, HtmlElement, HtmlInputElement, PointerEvent};

mod chaosgame;
mod escapetime;
mod fractaldata;
mod turtle;

use fractaldata::{FractalConfig, SelectedFractalExt};

pub trait FractalAnimation {
    fn draw_one_frame(&mut self) -> bool;
    fn pixel_to_coordinate(&self, x: f64, y: f64) -> [f64; 2];
    fn zoom(&mut self, _x1: f64, _y1: f64, _x2: f64, _y2: f64) -> bool;
}

#[derive(PartialEq)]
enum FractalAnimationStatus {
    NotStarted,
    Animating,
    Paused,
    Done,
}

// (Lines like the one below ignore selected Clippy rules
//  - it's useful when you want to check your code with `cargo make verify`
// but some rules are too "annoying" or are not applicable for your case.)
// #![allow(clippy::wildcard_imports)]
use seed::{prelude::*, *};

/// Initializes the Model at app launch
fn init(_url: Url, _orders: &mut impl Orders<Msg>) -> Model {
    Model {
        canvas: ElRef::default(),
        selected_fractal: SelectedFractal::BarnsleyFern,
        current_config: FractalConfig::NoConfig,
        current_animation: None,
        current_animation_status: FractalAnimationStatus::NotStarted,
        cursor_coords: [0, 0],
        fractal_coords: None,
    }
}

struct Model {
    /// The Canvas element that will be drawn. Stored here because a reference to it is needed
    /// during updates to kick off new animations.
    canvas: ElRef<HtmlCanvasElement>,
    /// The currently selected fractal. Used to show configuration about the fractal.
    selected_fractal: SelectedFractal,
    /// The configuration for the currently selected fractal. It needs to match the kind of fractl
    /// selected.
    current_config: FractalConfig,
    /// The currently ongoing fractal animation. This object does the work of rendering either each
    /// frame, or the whole fractal at once (if not drawn using an animation).
    current_animation: Option<Box<dyn FractalAnimation>>,
    /// Whether the current animation is animating, paused, or done.
    current_animation_status: FractalAnimationStatus,
    cursor_coords: [i32; 2],
    fractal_coords: Option<[f64; 2]>,
}

enum Msg {
    /// Indiactes that we should render the next frame of the animation
    AnimationFrameRequested,
    /// Indicates that a configuration field changed. It specifies the configuration field name, as
    /// well as the new value (currently they can only be integers)
    ConfigChanged(String, u64),
    /// Indicates which fractal was selected for configuration and eventually running.
    FractalSelected(String),
    /// Whether to start the animation for the currently selected fractal and configuration.
    RunClicked,
    /// Whether to pause the currently running animation.
    PauseClicked,
    /// Whether to resume the currentl running animation.
    ResumeClicked,
    CursorCoordsCanged(i32, i32),
}

/// Handles events that send `Msg`s, resulting in updates to the Model.
fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
    match msg {
        Msg::FractalSelected(selected) => {
            log::debug!("selected {}", selected);
            model.selected_fractal =
                SelectedFractal::from_str(selected.as_ref()).expect("unknown selected fractal");
            model.current_config = model.selected_fractal.default_config();
            log::debug!("config {:?}", model.current_config);
            model.current_animation_status = FractalAnimationStatus::NotStarted;
        }
        Msg::ConfigChanged(input, new_value) => {
            model.current_config.apply_change(input, new_value);
            model.current_animation_status = FractalAnimationStatus::NotStarted;
        }
        Msg::RunClicked => {
            log::debug!("clicked run");
            // Initialize a new animation
            let canvas = model.canvas.get().expect("get canvas element failed");
            model.current_animation = Some(
                model
                    .selected_fractal
                    .build_animation(&canvas, &model.current_config),
            );
            // If it's not currently animating, start the animation
            match model.current_animation_status {
                FractalAnimationStatus::Animating => {}
                _ => {
                    // There might already be an animation running. We don't want to double-up on the
                    // number of AnimationFrameRequested messages if that's the case.
                    orders.after_next_render(|_| Msg::AnimationFrameRequested);
                    model.current_animation_status = FractalAnimationStatus::Animating;
                }
            }
        }
        Msg::PauseClicked => {
            log::debug!("clicked pause");
            model.current_animation_status = FractalAnimationStatus::Paused;
        }
        Msg::ResumeClicked => {
            log::debug!("clicked pause");
            model.current_animation_status = FractalAnimationStatus::Animating;
            orders.after_next_render(|_| Msg::AnimationFrameRequested);
        }
        Msg::AnimationFrameRequested => {
            log::debug!("animation frame requested");
            match &mut model.current_animation {
                None => {}
                Some(animation) => {
                    // Animate until it says its done
                    if animation.draw_one_frame() {
                        // Request another frame only if the animation is still animating.
                        match model.current_animation_status {
                            FractalAnimationStatus::Animating => {
                                orders.after_next_render(|_| Msg::AnimationFrameRequested);
                            }
                            _ => {}
                        }
                    } else {
                        model.current_animation_status = FractalAnimationStatus::Done
                    }
                }
            }
        }
        Msg::CursorCoordsCanged(x, y) => {
            model.cursor_coords = [x, y];
            if let Some(animation) = &model.current_animation {
                let fractal_coords = animation.pixel_to_coordinate(x.into(), y.into());
                model.fractal_coords = Some(fractal_coords);
            }
        }
    }
}

/// Renders/re-renders the HTML side of the UI
///
/// Note that updates to the canvas are handled by the currently ongoing animation.
fn view(model: &Model) -> Node<Msg> {
    log::debug!("view");
    div![
        div![
            style! {St::Float => "left"},
            canvas![
                el_ref(&model.canvas),
                attrs! { At::Id => "fractal-canvas", At::Width => "800", At::Height => "600"},
                style! {St::BackgroundColor => "white"},
                //  update the pointer location
                ev(Ev::PointerMove, |event| {
                    let pointer_event = event.dyn_into::<PointerEvent>().unwrap();
                    let target = pointer_event
                        .target()
                        .unwrap()
                        .dyn_into::<HtmlElement>()
                        .unwrap();

                    Msg::CursorCoordsCanged(
                        pointer_event.client_x() - target.offset_left(),
                        pointer_event.client_y() - target.offset_top(),
                    )
                }),
            ],
            div![
                attrs! {At::Id => "coords"},
                format!("Canvas coords: {:?}", model.cursor_coords)
            ],
            div![
                attrs! {At::Id => "fractal-coords"},
                IF!(not(model.fractal_coords.is_none()) => format!(
                    "Fractal coords: X: {}, Y: {}",
                    model.fractal_coords.unwrap()[0], model.fractal_coords.unwrap()[1]
                )),
                IF!(model.fractal_coords.is_none() => "Fractal coorrrds: No fractal being rendered")
            ],
        ],
        div![
            style! {St::Float => "left"},
            view_menu(model.current_animation_status == FractalAnimationStatus::Animating),
            view_config(&model.current_config),
            IF!(
                model.current_animation_status == FractalAnimationStatus::Animating => button![
                "Pause", ev(Ev::Click, |_| Msg::PauseClicked)]
            ),
            IF!(
                model.current_animation_status == FractalAnimationStatus::Paused => button![
                "Resume", ev(Ev::Click, |_| Msg::ResumeClicked)]
            ),
            IF!(
                model.current_animation_status != FractalAnimationStatus::Animating => div![
                    button!["Start a new fractal", ev(Ev::Click, |_| Msg::RunClicked)]
                ]
            ),
        ]
    ]
}

/// Renders/re-renders the menu for selecting which fractal animation to configure and run next.
///
/// The menu can be disabled -- while an animation is running, it causes seed to re-compute the
/// view (the animation uses the seed event loop to schedule the next animation frame after the
/// next render), which causes the dropdown menu beecome unusable.
fn view_menu(disable: bool) -> Node<Msg> {
    log::debug!("menu disabled: {}", disable);
    select![
        attrs! { At::Id => "fractal-type" },
        // the presence of `disabled`, not its value, determines if an element is disabled.
        IF!(disable => attrs!{ At::Disabled => disable }),
        SelectedFractal::by_category()
            .iter()
            .map(|(category, fractals)| {
                optgroup![
                    attrs! {At::Label => category.display_name()},
                    fractals.iter().map(|fractal| {
                        option![
                            attrs! {At::Value => <&'static str>::from(fractal)},
                            fractal.name()
                        ]
                    })
                ]
            }),
        input_ev(Ev::Input, Msg::FractalSelected),
    ]
}

/// Event callback that runs HTML form validations on the target form input element.
///
/// It returns a `Msg::ConfigChanged` if the modified field passes validations, or `None` if it
/// does not.
fn validate_input(event: web_sys::Event) -> Option<Msg> {
    let target = event
        .target()
        .unwrap()
        .dyn_into::<HtmlInputElement>()
        .unwrap();
    target.check_validity();
    if target.report_validity() {
        if let Ok(value) = target.value().parse::<u64>() {
            return Some(Msg::ConfigChanged(target.id(), value));
        }
    }
    None
}

/// Renders/re-renders the configuration for the specified FractalConfig.
///
/// It renders different fields based on the "kind" of fractal, and sets up HTML form validations
/// to ensure that the model is only updated if the configuration is valid.
fn view_config(config: &FractalConfig) -> Node<Msg> {
    div![
        attrs! {At::Id => "config"},
        match config {
            FractalConfig::NoConfig => div!["No configuration for this fractal"],
            FractalConfig::TurtleCurveConfig { iteration } => div![div![
                label![attrs! {At::For => "iteration"}, "Iterations"],
                input![
                    attrs! {
                        At::Id => "iteration",
                        At::Type => "number",
                        At::Required => "true",
                        At::Value => iteration,
                        At::Min => 0,
                    },
                    ev(Ev::Input, validate_input),
                ]
            ]],
            FractalConfig::EscapeTimeConfig {
                max_iterations,
                power,
            } => div![
                div![
                    label![attrs! {At::For => "max_iterations"}, "Max Iterations"],
                    input![
                        attrs! {
                            At::Id => "max_iterations",
                            At::Type => "number",
                            At::Required => "true",
                            At::Value => max_iterations,
                            At::Min => 1,
                        },
                        ev(Ev::Input, validate_input),
                    ],
                ],
                div![
                    label![attrs! {At::For => "power"}, "Power"],
                    input![
                        attrs! {
                            At::Id => "power",
                            At::Type => "number",
                            At::Required => "true",
                            At::Value => power,
                            At::Min => 1,
                        },
                        ev(Ev::Input, validate_input),
                    ],
                ],
            ],
        },
        format!("{:?}", config),
    ]
}

/// The start function for the WASM. It initializes seed with the app's init, update, and view.
#[wasm_bindgen(start)]
pub fn start() {
    console_error_panic_hook::set_once();
    console_log::init_with_level(log::Level::Debug).unwrap();
    log::debug!("Start App");
    // Mount the `app` to the element with the `id` "app".
    App::start("app", init, update, view);
}
<commit_msg>Re-add ability to zoom<commit_after>// Copyright (c) 2015-2019 William (B.J.) Snow Orvis
//
// 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.

//! A Seed application that runs and renders various fractal curves.

use fractal_lib::SelectedFractal;
use std::str::FromStr;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::{HtmlCanvasElement, HtmlElement, HtmlInputElement, PointerEvent};

mod chaosgame;
mod escapetime;
mod fractaldata;
mod turtle;

use fractaldata::{FractalConfig, SelectedFractalExt};

pub trait FractalAnimation {
    fn draw_one_frame(&mut self) -> bool;
    fn pixel_to_coordinate(&self, x: f64, y: f64) -> [f64; 2];
    fn zoom(&mut self, _x1: f64, _y1: f64, _x2: f64, _y2: f64) -> bool;
}

#[derive(PartialEq)]
enum FractalAnimationStatus {
    NotStarted,
    Animating,
    Paused,
    Done,
}

// (Lines like the one below ignore selected Clippy rules
//  - it's useful when you want to check your code with `cargo make verify`
// but some rules are too "annoying" or are not applicable for your case.)
// #![allow(clippy::wildcard_imports)]
use seed::{prelude::*, *};

/// Initializes the Model at app launch
fn init(_url: Url, _orders: &mut impl Orders<Msg>) -> Model {
    Model {
        canvas: ElRef::default(),
        selected_fractal: SelectedFractal::BarnsleyFern,
        current_config: FractalConfig::NoConfig,
        current_animation: None,
        current_animation_status: FractalAnimationStatus::NotStarted,
        cursor_coords: [0, 0],
        fractal_coords: None,
        zoom_start_coords: None,
    }
}

struct Model {
    /// The Canvas element that will be drawn. Stored here because a reference to it is needed
    /// during updates to kick off new animations.
    canvas: ElRef<HtmlCanvasElement>,
    /// The currently selected fractal. Used to show configuration about the fractal.
    selected_fractal: SelectedFractal,
    /// The configuration for the currently selected fractal. It needs to match the kind of fractl
    /// selected.
    current_config: FractalConfig,
    /// The currently ongoing fractal animation. This object does the work of rendering either each
    /// frame, or the whole fractal at once (if not drawn using an animation).
    current_animation: Option<Box<dyn FractalAnimation>>,
    /// Whether the current animation is animating, paused, or done.
    current_animation_status: FractalAnimationStatus,
    cursor_coords: [i32; 2],
    fractal_coords: Option<[f64; 2]>,
    zoom_start_coords: Option<[i32; 2]>,
}

enum Msg {
    /// Indiactes that we should render the next frame of the animation
    AnimationFrameRequested,
    /// Indicates that a configuration field changed. It specifies the configuration field name, as
    /// well as the new value (currently they can only be integers)
    ConfigChanged(String, u64),
    /// Indicates which fractal was selected for configuration and eventually running.
    FractalSelected(String),
    /// Whether to start the animation for the currently selected fractal and configuration.
    RunClicked,
    /// Whether to pause the currently running animation.
    PauseClicked,
    /// Whether to resume the currentl running animation.
    ResumeClicked,
    CursorCoordsCanged(i32, i32),
    CursorDown,
    CursorUp,
}

/// Handles events that send `Msg`s, resulting in updates to the Model.
fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
    match msg {
        Msg::FractalSelected(selected) => {
            log::debug!("selected {}", selected);
            model.selected_fractal =
                SelectedFractal::from_str(selected.as_ref()).expect("unknown selected fractal");
            model.current_config = model.selected_fractal.default_config();
            log::debug!("config {:?}", model.current_config);
            model.current_animation_status = FractalAnimationStatus::NotStarted;
        }
        Msg::ConfigChanged(input, new_value) => {
            model.current_config.apply_change(input, new_value);
            model.current_animation_status = FractalAnimationStatus::NotStarted;
        }
        Msg::RunClicked => {
            log::debug!("clicked run");
            // Initialize a new animation
            let canvas = model.canvas.get().expect("get canvas element failed");
            model.current_animation = Some(
                model
                    .selected_fractal
                    .build_animation(&canvas, &model.current_config),
            );
            // If it's not currently animating, start the animation
            match model.current_animation_status {
                FractalAnimationStatus::Animating => {}
                _ => {
                    // There might already be an animation running. We don't want to double-up on
                    // the number of AnimationFrameRequested messages if that's the case.
                    orders.after_next_render(|_| Msg::AnimationFrameRequested);
                    model.current_animation_status = FractalAnimationStatus::Animating;
                }
            }
        }
        Msg::PauseClicked => {
            log::debug!("clicked pause");
            model.current_animation_status = FractalAnimationStatus::Paused;
        }
        Msg::ResumeClicked => {
            log::debug!("clicked pause");
            model.current_animation_status = FractalAnimationStatus::Animating;
            orders.after_next_render(|_| Msg::AnimationFrameRequested);
        }
        Msg::AnimationFrameRequested => {
            log::debug!("animation frame requested");
            match &mut model.current_animation {
                None => {}
                Some(animation) => {
                    // Animate until it says its done
                    if animation.draw_one_frame() {
                        // Request another frame only if the animation is still animating.
                        match model.current_animation_status {
                            FractalAnimationStatus::Animating => {
                                orders.after_next_render(|_| Msg::AnimationFrameRequested);
                            }
                            _ => {}
                        }
                    } else {
                        model.current_animation_status = FractalAnimationStatus::Done
                    }
                }
            }
        }
        Msg::CursorCoordsCanged(x, y) => {
            model.cursor_coords = [x, y];
            if let Some(animation) = &model.current_animation {
                let fractal_coords = animation.pixel_to_coordinate(x.into(), y.into());
                model.fractal_coords = Some(fractal_coords);
            }
        }
        Msg::CursorDown => {
            // Track where the cursor started being down, to begin selecting an area to zoom into.
            model.zoom_start_coords = Some(model.cursor_coords);
            log::debug!("Mouse down: {:?}", model.zoom_start_coords);
        }
        Msg::CursorUp => match model.zoom_start_coords {
            None => {}
            Some(zoom_start_coords) => {
                // If the cursor has been held down and was just raised up, compute the zoom and
                // resume the animation, if zoom is supported.
                let zoom_end_coords = model.cursor_coords;
                log::debug!("Mouse up: {:?}", zoom_end_coords);
                match &mut model.current_animation {
                    None => {}
                    Some(animation) => {
                        if animation.zoom(
                            f64::from(zoom_start_coords[0]),
                            f64::from(zoom_start_coords[1]),
                            f64::from(zoom_end_coords[0]),
                            f64::from(zoom_end_coords[1]),
                        ) {
                            // After `zoom()` is called, try to render the next frame. `zoom()`
                            // should prepare the animation.
                            orders.after_next_render(|_| Msg::AnimationFrameRequested);
                            model.current_animation_status = FractalAnimationStatus::Animating;
                        }
                    }
                }
            }
        },
    }
}

/// Renders/re-renders the HTML side of the UI
///
/// Note that updates to the canvas are handled by the currently ongoing animation.
fn view(model: &Model) -> Node<Msg> {
    log::debug!("view");
    div![
        div![
            style! {St::Float => "left"},
            canvas![
                el_ref(&model.canvas),
                attrs! { At::Id => "fractal-canvas", At::Width => "800", At::Height => "600"},
                style! {St::BackgroundColor => "white"},
                //  update the pointer location
                ev(Ev::PointerMove, |event| {
                    let pointer_event = event.dyn_into::<PointerEvent>().unwrap();
                    let target = pointer_event
                        .target()
                        .unwrap()
                        .dyn_into::<HtmlElement>()
                        .unwrap();

                    Msg::CursorCoordsCanged(
                        pointer_event.client_x() - target.offset_left(),
                        pointer_event.client_y() - target.offset_top(),
                    )
                }),
                ev(Ev::PointerDown, |_| Msg::CursorDown),
                ev(Ev::PointerUp, |_| Msg::CursorUp),
            ],
            div![
                attrs! {At::Id => "coords"},
                format!("Canvas coords: {:?}", model.cursor_coords)
            ],
            div![
                attrs! {At::Id => "fractal-coords"},
                IF!(not(model.fractal_coords.is_none()) => format!(
                    "Fractal coords: X: {}, Y: {}",
                    model.fractal_coords.unwrap()[0], model.fractal_coords.unwrap()[1]
                )),
                IF!(model.fractal_coords.is_none() => "Fractal coorrrds: No fractal being rendered")
            ],
        ],
        div![
            style! {St::Float => "left"},
            view_menu(model.current_animation_status == FractalAnimationStatus::Animating),
            view_config(&model.current_config),
            IF!(
                model.current_animation_status == FractalAnimationStatus::Animating => button![
                "Pause", ev(Ev::Click, |_| Msg::PauseClicked)]
            ),
            IF!(
                model.current_animation_status == FractalAnimationStatus::Paused => button![
                "Resume", ev(Ev::Click, |_| Msg::ResumeClicked)]
            ),
            IF!(
                model.current_animation_status != FractalAnimationStatus::Animating => div![
                    button!["Start a new fractal", ev(Ev::Click, |_| Msg::RunClicked)]
                ]
            ),
        ]
    ]
}

/// Renders/re-renders the menu for selecting which fractal animation to configure and run next.
///
/// The menu can be disabled -- while an animation is running, it causes seed to re-compute the
/// view (the animation uses the seed event loop to schedule the next animation frame after the
/// next render), which causes the dropdown menu beecome unusable.
fn view_menu(disable: bool) -> Node<Msg> {
    log::debug!("menu disabled: {}", disable);
    select![
        attrs! { At::Id => "fractal-type" },
        // the presence of `disabled`, not its value, determines if an element is disabled.
        IF!(disable => attrs!{ At::Disabled => disable }),
        SelectedFractal::by_category()
            .iter()
            .map(|(category, fractals)| {
                optgroup![
                    attrs! {At::Label => category.display_name()},
                    fractals.iter().map(|fractal| {
                        option![
                            attrs! {At::Value => <&'static str>::from(fractal)},
                            fractal.name()
                        ]
                    })
                ]
            }),
        input_ev(Ev::Input, Msg::FractalSelected),
    ]
}

/// Event callback that runs HTML form validations on the target form input element.
///
/// It returns a `Msg::ConfigChanged` if the modified field passes validations, or `None` if it
/// does not.
fn validate_input(event: web_sys::Event) -> Option<Msg> {
    let target = event
        .target()
        .unwrap()
        .dyn_into::<HtmlInputElement>()
        .unwrap();
    target.check_validity();
    if target.report_validity() {
        if let Ok(value) = target.value().parse::<u64>() {
            return Some(Msg::ConfigChanged(target.id(), value));
        }
    }
    None
}

/// Renders/re-renders the configuration for the specified FractalConfig.
///
/// It renders different fields based on the "kind" of fractal, and sets up HTML form validations
/// to ensure that the model is only updated if the configuration is valid.
fn view_config(config: &FractalConfig) -> Node<Msg> {
    div![
        attrs! {At::Id => "config"},
        match config {
            FractalConfig::NoConfig => div!["No configuration for this fractal"],
            FractalConfig::TurtleCurveConfig { iteration } => div![div![
                label![attrs! {At::For => "iteration"}, "Iterations"],
                input![
                    attrs! {
                        At::Id => "iteration",
                        At::Type => "number",
                        At::Required => "true",
                        At::Value => iteration,
                        At::Min => 0,
                    },
                    ev(Ev::Input, validate_input),
                ]
            ]],
            FractalConfig::EscapeTimeConfig {
                max_iterations,
                power,
            } => div![
                div![
                    label![attrs! {At::For => "max_iterations"}, "Max Iterations"],
                    input![
                        attrs! {
                            At::Id => "max_iterations",
                            At::Type => "number",
                            At::Required => "true",
                            At::Value => max_iterations,
                            At::Min => 1,
                        },
                        ev(Ev::Input, validate_input),
                    ],
                ],
                div![
                    label![attrs! {At::For => "power"}, "Power"],
                    input![
                        attrs! {
                            At::Id => "power",
                            At::Type => "number",
                            At::Required => "true",
                            At::Value => power,
                            At::Min => 1,
                        },
                        ev(Ev::Input, validate_input),
                    ],
                ],
            ],
        },
        format!("{:?}", config),
    ]
}

/// The start function for the WASM. It initializes seed with the app's init, update, and view.
#[wasm_bindgen(start)]
pub fn start() {
    console_error_panic_hook::set_once();
    console_log::init_with_level(log::Level::Debug).unwrap();
    log::debug!("Start App");
    // Mount the `app` to the element with the `id` "app".
    App::start("app", init, update, view);
}
<|endoftext|>


<commit_before>use rustc_data_structures::fx::FxHashSet;
use std::fs;
use std::hash::{Hash, Hasher};
use std::path::Path;

use rustc_errors::Handler;

#[cfg(test)]
mod tests;

macro_rules! try_something {
    ($e:expr, $diag:expr, $out:expr) => {{
        match $e {
            Ok(c) => c,
            Err(e) => {
                $diag.struct_err(&e.to_string()).emit();
                return $out;
            }
        }
    }};
}

#[derive(Debug, Clone, Eq)]
pub struct CssPath {
    pub name: String,
    pub children: FxHashSet<CssPath>,
}

// This PartialEq implementation IS NOT COMMUTATIVE!!!
//
// The order is very important: the second object must have all first's rules.
// However, the first doesn't require to have all second's rules.
impl PartialEq for CssPath {
    fn eq(&self, other: &CssPath) -> bool {
        if self.name != other.name {
            false
        } else {
            for child in &self.children {
                if !other.children.iter().any(|c| child == c) {
                    return false;
                }
            }
            true
        }
    }
}

impl Hash for CssPath {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.name.hash(state);
        for x in &self.children {
            x.hash(state);
        }
    }
}

impl CssPath {
    fn new(name: String) -> CssPath {
        CssPath { name, children: FxHashSet::default() }
    }
}

/// All variants contain the position they occur.
#[derive(Debug, Clone, Copy)]
enum Events {
    StartLineComment(usize),
    StartComment(usize),
    EndComment(usize),
    InBlock(usize),
    OutBlock(usize),
}

impl Events {
    fn get_pos(&self) -> usize {
        match *self {
            Events::StartLineComment(p)
            | Events::StartComment(p)
            | Events::EndComment(p)
            | Events::InBlock(p)
            | Events::OutBlock(p) => p,
        }
    }

    fn is_comment(&self) -> bool {
        match *self {
            Events::StartLineComment(_) | Events::StartComment(_) | Events::EndComment(_) => true,
            _ => false,
        }
    }
}

fn previous_is_line_comment(events: &[Events]) -> bool {
    if let Some(&Events::StartLineComment(_)) = events.last() { true } else { false }
}

fn is_line_comment(pos: usize, v: &[u8], events: &[Events]) -> bool {
    if let Some(&Events::StartComment(_)) = events.last() {
        return false;
    }
    v[pos + 1] == b'/'
}

fn load_css_events(v: &[u8]) -> Vec<Events> {
    let mut pos = 0;
    let mut events = Vec::with_capacity(100);

    while pos + 1 < v.len() {
        match v[pos] {
            b'/' if v[pos + 1] == b'*' => {
                events.push(Events::StartComment(pos));
                pos += 1;
            }
            b'/' if is_line_comment(pos, v, &events) => {
                events.push(Events::StartLineComment(pos));
                pos += 1;
            }
            b'\n' if previous_is_line_comment(&events) => {
                events.push(Events::EndComment(pos));
            }
            b'*' if v[pos + 1] == b'/' => {
                events.push(Events::EndComment(pos + 2));
                pos += 1;
            }
            b'{' if !previous_is_line_comment(&events) => {
                if let Some(&Events::StartComment(_)) = events.last() {
                    pos += 1;
                    continue;
                }
                events.push(Events::InBlock(pos + 1));
            }
            b'}' if !previous_is_line_comment(&events) => {
                if let Some(&Events::StartComment(_)) = events.last() {
                    pos += 1;
                    continue;
                }
                events.push(Events::OutBlock(pos + 1));
            }
            _ => {}
        }
        pos += 1;
    }
    events
}

fn get_useful_next(events: &[Events], pos: &mut usize) -> Option<Events> {
    while *pos < events.len() {
        if !events[*pos].is_comment() {
            return Some(events[*pos]);
        }
        *pos += 1;
    }
    None
}

fn get_previous_positions(events: &[Events], mut pos: usize) -> Vec<usize> {
    let mut ret = Vec::with_capacity(3);

    ret.push(events[pos].get_pos());
    if pos > 0 {
        pos -= 1;
    }
    loop {
        if pos < 1 || !events[pos].is_comment() {
            let x = events[pos].get_pos();
            if *ret.last().unwrap() != x {
                ret.push(x);
            } else {
                ret.push(0);
            }
            break;
        }
        ret.push(events[pos].get_pos());
        pos -= 1;
    }
    if ret.len() & 1 != 0 && events[pos].is_comment() {
        ret.push(0);
    }
    ret.iter().rev().cloned().collect()
}

fn build_rule(v: &[u8], positions: &[usize]) -> String {
    minifier::css::minify(
        &positions
            .chunks(2)
            .map(|x| ::std::str::from_utf8(&v[x[0]..x[1]]).unwrap_or(""))
            .collect::<String>()
            .trim()
            .replace("\n", " ")
            .replace("/", "")
            .replace("\t", " ")
            .replace("{", "")
            .replace("}", "")
            .split(' ')
            .filter(|s| !s.is_empty())
            .collect::<Vec<&str>>()
            .join(" "),
    )
    .unwrap_or_else(|_| String::new())
}

fn inner(v: &[u8], events: &[Events], pos: &mut usize) -> FxHashSet<CssPath> {
    let mut paths = Vec::with_capacity(50);

    while *pos < events.len() {
        if let Some(Events::OutBlock(_)) = get_useful_next(events, pos) {
            *pos += 1;
            break;
        }
        if let Some(Events::InBlock(_)) = get_useful_next(events, pos) {
            paths.push(CssPath::new(build_rule(v, &get_previous_positions(events, *pos))));
            *pos += 1;
        }
        while let Some(Events::InBlock(_)) = get_useful_next(events, pos) {
            if let Some(ref mut path) = paths.last_mut() {
                for entry in inner(v, events, pos).iter() {
                    path.children.insert(entry.clone());
                }
            }
        }
        if let Some(Events::OutBlock(_)) = get_useful_next(events, pos) {
            *pos += 1;
        }
    }
    paths.iter().cloned().collect()
}

pub fn load_css_paths(v: &[u8]) -> CssPath {
    let events = load_css_events(v);
    let mut pos = 0;

    let mut parent = CssPath::new("parent".to_owned());
    parent.children = inner(v, &events, &mut pos);
    parent
}

pub fn get_differences(against: &CssPath, other: &CssPath, v: &mut Vec<String>) {
    if against.name == other.name {
        for child in &against.children {
            let mut found = false;
            let mut found_working = false;
            let mut tmp = Vec::new();

            for other_child in &other.children {
                if child.name == other_child.name {
                    if child != other_child {
                        get_differences(child, other_child, &mut tmp);
                    } else {
                        found_working = true;
                    }
                    found = true;
                    break;
                }
            }
            if !found {
                v.push(format!("  Missing \"{}\" rule", child.name));
            } else if !found_working {
                v.extend(tmp.iter().cloned());
            }
        }
    }
}

pub fn test_theme_against<P: AsRef<Path>>(
    f: &P,
    against: &CssPath,
    diag: &Handler,
) -> (bool, Vec<String>) {
    let data = try_something!(fs::read(f), diag, (false, vec![]));

    let paths = load_css_paths(&data);
    let mut ret = vec![];
    get_differences(against, &paths, &mut ret);
    (true, ret)
}
<commit_msg>Expand and remove try_something macro.<commit_after>use rustc_data_structures::fx::FxHashSet;
use std::fs;
use std::hash::{Hash, Hasher};
use std::path::Path;

use rustc_errors::Handler;

#[cfg(test)]
mod tests;

#[derive(Debug, Clone, Eq)]
pub struct CssPath {
    pub name: String,
    pub children: FxHashSet<CssPath>,
}

// This PartialEq implementation IS NOT COMMUTATIVE!!!
//
// The order is very important: the second object must have all first's rules.
// However, the first doesn't require to have all second's rules.
impl PartialEq for CssPath {
    fn eq(&self, other: &CssPath) -> bool {
        if self.name != other.name {
            false
        } else {
            for child in &self.children {
                if !other.children.iter().any(|c| child == c) {
                    return false;
                }
            }
            true
        }
    }
}

impl Hash for CssPath {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.name.hash(state);
        for x in &self.children {
            x.hash(state);
        }
    }
}

impl CssPath {
    fn new(name: String) -> CssPath {
        CssPath { name, children: FxHashSet::default() }
    }
}

/// All variants contain the position they occur.
#[derive(Debug, Clone, Copy)]
enum Events {
    StartLineComment(usize),
    StartComment(usize),
    EndComment(usize),
    InBlock(usize),
    OutBlock(usize),
}

impl Events {
    fn get_pos(&self) -> usize {
        match *self {
            Events::StartLineComment(p)
            | Events::StartComment(p)
            | Events::EndComment(p)
            | Events::InBlock(p)
            | Events::OutBlock(p) => p,
        }
    }

    fn is_comment(&self) -> bool {
        match *self {
            Events::StartLineComment(_) | Events::StartComment(_) | Events::EndComment(_) => true,
            _ => false,
        }
    }
}

fn previous_is_line_comment(events: &[Events]) -> bool {
    if let Some(&Events::StartLineComment(_)) = events.last() { true } else { false }
}

fn is_line_comment(pos: usize, v: &[u8], events: &[Events]) -> bool {
    if let Some(&Events::StartComment(_)) = events.last() {
        return false;
    }
    v[pos + 1] == b'/'
}

fn load_css_events(v: &[u8]) -> Vec<Events> {
    let mut pos = 0;
    let mut events = Vec::with_capacity(100);

    while pos + 1 < v.len() {
        match v[pos] {
            b'/' if v[pos + 1] == b'*' => {
                events.push(Events::StartComment(pos));
                pos += 1;
            }
            b'/' if is_line_comment(pos, v, &events) => {
                events.push(Events::StartLineComment(pos));
                pos += 1;
            }
            b'\n' if previous_is_line_comment(&events) => {
                events.push(Events::EndComment(pos));
            }
            b'*' if v[pos + 1] == b'/' => {
                events.push(Events::EndComment(pos + 2));
                pos += 1;
            }
            b'{' if !previous_is_line_comment(&events) => {
                if let Some(&Events::StartComment(_)) = events.last() {
                    pos += 1;
                    continue;
                }
                events.push(Events::InBlock(pos + 1));
            }
            b'}' if !previous_is_line_comment(&events) => {
                if let Some(&Events::StartComment(_)) = events.last() {
                    pos += 1;
                    continue;
                }
                events.push(Events::OutBlock(pos + 1));
            }
            _ => {}
        }
        pos += 1;
    }
    events
}

fn get_useful_next(events: &[Events], pos: &mut usize) -> Option<Events> {
    while *pos < events.len() {
        if !events[*pos].is_comment() {
            return Some(events[*pos]);
        }
        *pos += 1;
    }
    None
}

fn get_previous_positions(events: &[Events], mut pos: usize) -> Vec<usize> {
    let mut ret = Vec::with_capacity(3);

    ret.push(events[pos].get_pos());
    if pos > 0 {
        pos -= 1;
    }
    loop {
        if pos < 1 || !events[pos].is_comment() {
            let x = events[pos].get_pos();
            if *ret.last().unwrap() != x {
                ret.push(x);
            } else {
                ret.push(0);
            }
            break;
        }
        ret.push(events[pos].get_pos());
        pos -= 1;
    }
    if ret.len() & 1 != 0 && events[pos].is_comment() {
        ret.push(0);
    }
    ret.iter().rev().cloned().collect()
}

fn build_rule(v: &[u8], positions: &[usize]) -> String {
    minifier::css::minify(
        &positions
            .chunks(2)
            .map(|x| ::std::str::from_utf8(&v[x[0]..x[1]]).unwrap_or(""))
            .collect::<String>()
            .trim()
            .replace("\n", " ")
            .replace("/", "")
            .replace("\t", " ")
            .replace("{", "")
            .replace("}", "")
            .split(' ')
            .filter(|s| !s.is_empty())
            .collect::<Vec<&str>>()
            .join(" "),
    )
    .unwrap_or_else(|_| String::new())
}

fn inner(v: &[u8], events: &[Events], pos: &mut usize) -> FxHashSet<CssPath> {
    let mut paths = Vec::with_capacity(50);

    while *pos < events.len() {
        if let Some(Events::OutBlock(_)) = get_useful_next(events, pos) {
            *pos += 1;
            break;
        }
        if let Some(Events::InBlock(_)) = get_useful_next(events, pos) {
            paths.push(CssPath::new(build_rule(v, &get_previous_positions(events, *pos))));
            *pos += 1;
        }
        while let Some(Events::InBlock(_)) = get_useful_next(events, pos) {
            if let Some(ref mut path) = paths.last_mut() {
                for entry in inner(v, events, pos).iter() {
                    path.children.insert(entry.clone());
                }
            }
        }
        if let Some(Events::OutBlock(_)) = get_useful_next(events, pos) {
            *pos += 1;
        }
    }
    paths.iter().cloned().collect()
}

pub fn load_css_paths(v: &[u8]) -> CssPath {
    let events = load_css_events(v);
    let mut pos = 0;

    let mut parent = CssPath::new("parent".to_owned());
    parent.children = inner(v, &events, &mut pos);
    parent
}

pub fn get_differences(against: &CssPath, other: &CssPath, v: &mut Vec<String>) {
    if against.name == other.name {
        for child in &against.children {
            let mut found = false;
            let mut found_working = false;
            let mut tmp = Vec::new();

            for other_child in &other.children {
                if child.name == other_child.name {
                    if child != other_child {
                        get_differences(child, other_child, &mut tmp);
                    } else {
                        found_working = true;
                    }
                    found = true;
                    break;
                }
            }
            if !found {
                v.push(format!("  Missing \"{}\" rule", child.name));
            } else if !found_working {
                v.extend(tmp.iter().cloned());
            }
        }
    }
}

pub fn test_theme_against<P: AsRef<Path>>(
    f: &P,
    against: &CssPath,
    diag: &Handler,
) -> (bool, Vec<String>) {
    let data = match fs::read(f) {
        Ok(c) => c,
        Err(e) => {
            diag.struct_err(&e.to_string()).emit();
            return (false, vec![]);
        }
    };

    let paths = load_css_paths(&data);
    let mut ret = vec![];
    get_differences(against, &paths, &mut ret);
    (true, ret)
}
<|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/. */

#![allow(unsafe_code)]

use app_units::Au;
use cssparser::RGBA;
use gecko_bindings::structs::{NS_RADIUS_CLOSEST_SIDE, NS_RADIUS_FARTHEST_SIDE};
use gecko_bindings::structs::nsStyleCoord;
use gecko_bindings::sugar::ns_style_coord::{CoordData, CoordDataMut, CoordDataValue};
use std::cmp::max;
use values::computed::{LengthOrNumber, LengthOrPercentage, LengthOrPercentageOrAuto};
use values::computed::{LengthOrPercentageOrNone, Angle};
use values::computed::basic_shape::ShapeRadius;

pub trait StyleCoordHelpers {
    fn set<T: GeckoStyleCoordConvertible>(&mut self, val: T);
}

impl StyleCoordHelpers for nsStyleCoord {
    #[inline]
    fn set<T: GeckoStyleCoordConvertible>(&mut self, val: T) {
        val.to_gecko_style_coord(self);
    }
}

pub trait GeckoStyleCoordConvertible : Sized {
    fn to_gecko_style_coord<T: CoordDataMut>(&self, coord: &mut T);
    fn from_gecko_style_coord<T: CoordData>(coord: &T) -> Option<Self>;
}

impl GeckoStyleCoordConvertible for LengthOrPercentage {
    fn to_gecko_style_coord<T: CoordDataMut>(&self, coord: &mut T) {
        let value = match *self {
            LengthOrPercentage::Length(au) => CoordDataValue::Coord(au.0),
            LengthOrPercentage::Percentage(p) => CoordDataValue::Percent(p),
            LengthOrPercentage::Calc(calc) => CoordDataValue::Calc(calc.into()),
        };
        coord.set_value(value);
    }

    fn from_gecko_style_coord<T: CoordData>(coord: &T) -> Option<Self> {
        match coord.as_value() {
            CoordDataValue::Coord(coord) => Some(LengthOrPercentage::Length(Au(coord))),
            CoordDataValue::Percent(p) => Some(LengthOrPercentage::Percentage(p)),
            CoordDataValue::Calc(calc) => Some(LengthOrPercentage::Calc(calc.into())),
            _ => None,
        }
    }
}

impl GeckoStyleCoordConvertible for Au {
    fn to_gecko_style_coord<T: CoordDataMut>(&self, coord: &mut T) {
        coord.set_value(CoordDataValue::Coord(self.0));
    }

    fn from_gecko_style_coord<T: CoordData>(coord: &T) -> Option<Self> {
        match coord.as_value() {
            CoordDataValue::Coord(coord) => Some(Au(coord)),
            _ => None,
        }
    }
}

impl GeckoStyleCoordConvertible for LengthOrPercentageOrAuto {
    fn to_gecko_style_coord<T: CoordDataMut>(&self, coord: &mut T) {
        let value = match *self {
            LengthOrPercentageOrAuto::Length(au) => CoordDataValue::Coord(au.0),
            LengthOrPercentageOrAuto::Percentage(p) => CoordDataValue::Percent(p),
            LengthOrPercentageOrAuto::Auto => CoordDataValue::Auto,
            LengthOrPercentageOrAuto::Calc(calc) => CoordDataValue::Calc(calc.into()),
        };
        coord.set_value(value);
    }

    fn from_gecko_style_coord<T: CoordData>(coord: &T) -> Option<Self> {
        match coord.as_value() {
            CoordDataValue::Coord(coord) => Some(LengthOrPercentageOrAuto::Length(Au(coord))),
            CoordDataValue::Percent(p) => Some(LengthOrPercentageOrAuto::Percentage(p)),
            CoordDataValue::Auto => Some(LengthOrPercentageOrAuto::Auto),
            CoordDataValue::Calc(calc) => Some(LengthOrPercentageOrAuto::Calc(calc.into())),
            _ => None,
        }
    }
}

impl GeckoStyleCoordConvertible for LengthOrPercentageOrNone {
    fn to_gecko_style_coord<T: CoordDataMut>(&self, coord: &mut T) {
        let value = match *self {
            LengthOrPercentageOrNone::Length(au) => CoordDataValue::Coord(au.0),
            LengthOrPercentageOrNone::Percentage(p) => CoordDataValue::Percent(p),
            LengthOrPercentageOrNone::None => CoordDataValue::None,
            LengthOrPercentageOrNone::Calc(calc) => CoordDataValue::Calc(calc.into()),
        };
        coord.set_value(value);
    }

    fn from_gecko_style_coord<T: CoordData>(coord: &T) -> Option<Self> {
        match coord.as_value() {
            CoordDataValue::Coord(coord) => Some(LengthOrPercentageOrNone::Length(Au(coord))),
            CoordDataValue::Percent(p) => Some(LengthOrPercentageOrNone::Percentage(p)),
            CoordDataValue::None => Some(LengthOrPercentageOrNone::None),
            CoordDataValue::Calc(calc) => Some(LengthOrPercentageOrNone::Calc(calc.into())),
            _ => None,
        }
    }
}

impl GeckoStyleCoordConvertible for LengthOrNumber {
    fn to_gecko_style_coord<T: CoordDataMut>(&self, coord: &mut T) {
        let value = match *self {
            LengthOrNumber::Length(au) => CoordDataValue::Coord(au.0),
            LengthOrNumber::Number(number) => CoordDataValue::Factor(number),
        };
        coord.set_value(value);
    }

    fn from_gecko_style_coord<T: CoordData>(coord: &T) -> Option<Self> {
        match coord.as_value() {
            CoordDataValue::Coord(coord) => Some(LengthOrNumber::Length(Au(coord))),
            CoordDataValue::Factor(f) => Some(LengthOrNumber::Number(f)),
            _ => None,
        }
    }
}

impl GeckoStyleCoordConvertible for ShapeRadius {
    fn to_gecko_style_coord<T: CoordDataMut>(&self, coord: &mut T) {
        match *self {
            ShapeRadius::ClosestSide => {
                coord.set_value(CoordDataValue::Enumerated(NS_RADIUS_CLOSEST_SIDE))
            }
            ShapeRadius::FarthestSide => {
                coord.set_value(CoordDataValue::Enumerated(NS_RADIUS_FARTHEST_SIDE))
            }
            ShapeRadius::Length(lop) => lop.to_gecko_style_coord(coord),
        }
    }

    fn from_gecko_style_coord<T: CoordData>(coord: &T) -> Option<Self> {
        match coord.as_value() {
            CoordDataValue::Enumerated(NS_RADIUS_CLOSEST_SIDE) => Some(ShapeRadius::ClosestSide),
            CoordDataValue::Enumerated(NS_RADIUS_FARTHEST_SIDE) => Some(ShapeRadius::FarthestSide),
            _ => LengthOrPercentage::from_gecko_style_coord(coord).map(ShapeRadius::Length),
        }
    }
}

impl<T: GeckoStyleCoordConvertible> GeckoStyleCoordConvertible for Option<T> {
    fn to_gecko_style_coord<U: CoordDataMut>(&self, coord: &mut U) {
        if let Some(ref me) = *self {
            me.to_gecko_style_coord(coord);
        } else {
            coord.set_value(CoordDataValue::None);
        }
    }

    fn from_gecko_style_coord<U: CoordData>(coord: &U) -> Option<Self> {
        Some(T::from_gecko_style_coord(coord))
    }
}

impl GeckoStyleCoordConvertible for Angle {
    fn to_gecko_style_coord<T: CoordDataMut>(&self, coord: &mut T) {
        coord.set_value(CoordDataValue::Radian(self.radians()))
    }

    fn from_gecko_style_coord<T: CoordData>(coord: &T) -> Option<Self> {
        if let CoordDataValue::Radian(r) = coord.as_value() {
            Some(Angle::from_radians(r))
            // XXXManishearth should this handle Degree too?
        } else {
            None
        }
    }
}

pub fn convert_rgba_to_nscolor(rgba: &RGBA) -> u32 {
    (((rgba.alpha * 255.0).round() as u32) << 24) |
    (((rgba.blue  * 255.0).round() as u32) << 16) |
    (((rgba.green * 255.0).round() as u32) << 8) |
     ((rgba.red   * 255.0).round() as u32)
}

pub fn convert_nscolor_to_rgba(color: u32) -> RGBA {
    RGBA {
        red:    ((color        & 0xff) as f32) / 255.0,
        green: (((color >>  8) & 0xff) as f32) / 255.0,
        blue:  (((color >> 16) & 0xff) as f32) / 255.0,
        alpha: (((color >> 24) & 0xff) as f32) / 255.0,
    }
}

#[inline]
pub fn round_border_to_device_pixels(width: Au, au_per_device_px: Au) -> Au {
    // Round width down to the nearest device pixel, but any non-zero value that
    // would round down to zero is clamped to 1 device pixel.  Used for storing
    // computed values of border-*-width and outline-width.
    if width == Au(0) {
        Au(0)
    } else {
        max(au_per_device_px, Au(width.0 / au_per_device_px.0 * au_per_device_px.0))
    }
}
<commit_msg>Add impl GeckoStyleCoordConvertible for Either<A, B><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/. */

#![allow(unsafe_code)]

use app_units::Au;
use cssparser::RGBA;
use gecko_bindings::structs::{NS_RADIUS_CLOSEST_SIDE, NS_RADIUS_FARTHEST_SIDE};
use gecko_bindings::structs::nsStyleCoord;
use gecko_bindings::sugar::ns_style_coord::{CoordData, CoordDataMut, CoordDataValue};
use std::cmp::max;
use values::Either;
use values::computed::{LengthOrNumber, LengthOrPercentage, LengthOrPercentageOrAuto};
use values::computed::{LengthOrPercentageOrNone, Angle};
use values::computed::basic_shape::ShapeRadius;

pub trait StyleCoordHelpers {
    fn set<T: GeckoStyleCoordConvertible>(&mut self, val: T);
}

impl StyleCoordHelpers for nsStyleCoord {
    #[inline]
    fn set<T: GeckoStyleCoordConvertible>(&mut self, val: T) {
        val.to_gecko_style_coord(self);
    }
}

pub trait GeckoStyleCoordConvertible : Sized {
    fn to_gecko_style_coord<T: CoordDataMut>(&self, coord: &mut T);
    fn from_gecko_style_coord<T: CoordData>(coord: &T) -> Option<Self>;
}

impl<A: GeckoStyleCoordConvertible, B: GeckoStyleCoordConvertible> GeckoStyleCoordConvertible for Either<A, B> {
    fn to_gecko_style_coord<T: CoordDataMut>(&self, coord: &mut T) {
        match *self {
            Either::First(ref v) => v.to_gecko_style_coord(coord),
            Either::Second(ref v) => v.to_gecko_style_coord(coord),
        }
    }

    fn from_gecko_style_coord<T: CoordData>(coord: &T) -> Option<Self> {
        A::from_gecko_style_coord(coord)
          .map(Either::First)
          .or_else(|| B::from_gecko_style_coord(coord).map(Either::Second))
    }
}

impl GeckoStyleCoordConvertible for LengthOrPercentage {
    fn to_gecko_style_coord<T: CoordDataMut>(&self, coord: &mut T) {
        let value = match *self {
            LengthOrPercentage::Length(au) => CoordDataValue::Coord(au.0),
            LengthOrPercentage::Percentage(p) => CoordDataValue::Percent(p),
            LengthOrPercentage::Calc(calc) => CoordDataValue::Calc(calc.into()),
        };
        coord.set_value(value);
    }

    fn from_gecko_style_coord<T: CoordData>(coord: &T) -> Option<Self> {
        match coord.as_value() {
            CoordDataValue::Coord(coord) => Some(LengthOrPercentage::Length(Au(coord))),
            CoordDataValue::Percent(p) => Some(LengthOrPercentage::Percentage(p)),
            CoordDataValue::Calc(calc) => Some(LengthOrPercentage::Calc(calc.into())),
            _ => None,
        }
    }
}

impl GeckoStyleCoordConvertible for Au {
    fn to_gecko_style_coord<T: CoordDataMut>(&self, coord: &mut T) {
        coord.set_value(CoordDataValue::Coord(self.0));
    }

    fn from_gecko_style_coord<T: CoordData>(coord: &T) -> Option<Self> {
        match coord.as_value() {
            CoordDataValue::Coord(coord) => Some(Au(coord)),
            _ => None,
        }
    }
}

impl GeckoStyleCoordConvertible for LengthOrPercentageOrAuto {
    fn to_gecko_style_coord<T: CoordDataMut>(&self, coord: &mut T) {
        let value = match *self {
            LengthOrPercentageOrAuto::Length(au) => CoordDataValue::Coord(au.0),
            LengthOrPercentageOrAuto::Percentage(p) => CoordDataValue::Percent(p),
            LengthOrPercentageOrAuto::Auto => CoordDataValue::Auto,
            LengthOrPercentageOrAuto::Calc(calc) => CoordDataValue::Calc(calc.into()),
        };
        coord.set_value(value);
    }

    fn from_gecko_style_coord<T: CoordData>(coord: &T) -> Option<Self> {
        match coord.as_value() {
            CoordDataValue::Coord(coord) => Some(LengthOrPercentageOrAuto::Length(Au(coord))),
            CoordDataValue::Percent(p) => Some(LengthOrPercentageOrAuto::Percentage(p)),
            CoordDataValue::Auto => Some(LengthOrPercentageOrAuto::Auto),
            CoordDataValue::Calc(calc) => Some(LengthOrPercentageOrAuto::Calc(calc.into())),
            _ => None,
        }
    }
}

impl GeckoStyleCoordConvertible for LengthOrPercentageOrNone {
    fn to_gecko_style_coord<T: CoordDataMut>(&self, coord: &mut T) {
        let value = match *self {
            LengthOrPercentageOrNone::Length(au) => CoordDataValue::Coord(au.0),
            LengthOrPercentageOrNone::Percentage(p) => CoordDataValue::Percent(p),
            LengthOrPercentageOrNone::None => CoordDataValue::None,
            LengthOrPercentageOrNone::Calc(calc) => CoordDataValue::Calc(calc.into()),
        };
        coord.set_value(value);
    }

    fn from_gecko_style_coord<T: CoordData>(coord: &T) -> Option<Self> {
        match coord.as_value() {
            CoordDataValue::Coord(coord) => Some(LengthOrPercentageOrNone::Length(Au(coord))),
            CoordDataValue::Percent(p) => Some(LengthOrPercentageOrNone::Percentage(p)),
            CoordDataValue::None => Some(LengthOrPercentageOrNone::None),
            CoordDataValue::Calc(calc) => Some(LengthOrPercentageOrNone::Calc(calc.into())),
            _ => None,
        }
    }
}

impl GeckoStyleCoordConvertible for LengthOrNumber {
    fn to_gecko_style_coord<T: CoordDataMut>(&self, coord: &mut T) {
        let value = match *self {
            LengthOrNumber::Length(au) => CoordDataValue::Coord(au.0),
            LengthOrNumber::Number(number) => CoordDataValue::Factor(number),
        };
        coord.set_value(value);
    }

    fn from_gecko_style_coord<T: CoordData>(coord: &T) -> Option<Self> {
        match coord.as_value() {
            CoordDataValue::Coord(coord) => Some(LengthOrNumber::Length(Au(coord))),
            CoordDataValue::Factor(f) => Some(LengthOrNumber::Number(f)),
            _ => None,
        }
    }
}

impl GeckoStyleCoordConvertible for ShapeRadius {
    fn to_gecko_style_coord<T: CoordDataMut>(&self, coord: &mut T) {
        match *self {
            ShapeRadius::ClosestSide => {
                coord.set_value(CoordDataValue::Enumerated(NS_RADIUS_CLOSEST_SIDE))
            }
            ShapeRadius::FarthestSide => {
                coord.set_value(CoordDataValue::Enumerated(NS_RADIUS_FARTHEST_SIDE))
            }
            ShapeRadius::Length(lop) => lop.to_gecko_style_coord(coord),
        }
    }

    fn from_gecko_style_coord<T: CoordData>(coord: &T) -> Option<Self> {
        match coord.as_value() {
            CoordDataValue::Enumerated(NS_RADIUS_CLOSEST_SIDE) => Some(ShapeRadius::ClosestSide),
            CoordDataValue::Enumerated(NS_RADIUS_FARTHEST_SIDE) => Some(ShapeRadius::FarthestSide),
            _ => LengthOrPercentage::from_gecko_style_coord(coord).map(ShapeRadius::Length),
        }
    }
}

impl<T: GeckoStyleCoordConvertible> GeckoStyleCoordConvertible for Option<T> {
    fn to_gecko_style_coord<U: CoordDataMut>(&self, coord: &mut U) {
        if let Some(ref me) = *self {
            me.to_gecko_style_coord(coord);
        } else {
            coord.set_value(CoordDataValue::None);
        }
    }

    fn from_gecko_style_coord<U: CoordData>(coord: &U) -> Option<Self> {
        Some(T::from_gecko_style_coord(coord))
    }
}

impl GeckoStyleCoordConvertible for Angle {
    fn to_gecko_style_coord<T: CoordDataMut>(&self, coord: &mut T) {
        coord.set_value(CoordDataValue::Radian(self.radians()))
    }

    fn from_gecko_style_coord<T: CoordData>(coord: &T) -> Option<Self> {
        if let CoordDataValue::Radian(r) = coord.as_value() {
            Some(Angle::from_radians(r))
            // XXXManishearth should this handle Degree too?
        } else {
            None
        }
    }
}

pub fn convert_rgba_to_nscolor(rgba: &RGBA) -> u32 {
    (((rgba.alpha * 255.0).round() as u32) << 24) |
    (((rgba.blue  * 255.0).round() as u32) << 16) |
    (((rgba.green * 255.0).round() as u32) << 8) |
     ((rgba.red   * 255.0).round() as u32)
}

pub fn convert_nscolor_to_rgba(color: u32) -> RGBA {
    RGBA {
        red:    ((color        & 0xff) as f32) / 255.0,
        green: (((color >>  8) & 0xff) as f32) / 255.0,
        blue:  (((color >> 16) & 0xff) as f32) / 255.0,
        alpha: (((color >> 24) & 0xff) as f32) / 255.0,
    }
}

#[inline]
pub fn round_border_to_device_pixels(width: Au, au_per_device_px: Au) -> Au {
    // Round width down to the nearest device pixel, but any non-zero value that
    // would round down to zero is clamped to 1 device pixel.  Used for storing
    // computed values of border-*-width and outline-width.
    if width == Au(0) {
        Au(0)
    } else {
        max(au_per_device_px, Au(width.0 / au_per_device_px.0 * au_per_device_px.0))
    }
}
<|endoftext|>


<commit_before>use tests::*;
use {Loader, LoadError};

use futures::Future;
use tokio_core::reactor::Core;

#[test]
fn assert_kinds() {
    fn _assert_send<T: Send>() {}
    fn _assert_sync<T: Sync>() {}
    fn _assert_clone<T: Clone>() {}
    _assert_send::<cached::Loader<u32, u32, u32>>();
    _assert_sync::<cached::Loader<u32, u32, u32>>();
    _assert_clone::<cached::Loader<u32, u32, u32>>();
}

#[test]
fn smoke() {
    let loader = Loader::new(Batcher::new(2)).cached();
    let v1 = loader.load(1);
    let v2 = loader.load(2);
    let v3 = loader.load(3);
    assert_eq!((10, 20), v1.join(v2).wait().unwrap());
    assert_eq!(30, v3.wait().unwrap());

    let many = loader.load_many(vec![10, 20, 30]);
    assert_eq!(vec![100, 200, 300], many.wait().unwrap());

    let loader_ref = &loader;
    {
        let v1 = loader_ref.load(1);
        let v2 = loader_ref.load(2);
        assert_eq!((10, 20), v1.join(v2).wait().unwrap());
    }
    {
        let v1 = loader_ref.load(3).map(|v| loader_ref.load(v).wait().unwrap());
        let v2 = loader_ref.load(4).map(|v| loader_ref.load(v).wait().unwrap());
        assert_eq!((300, 400), v1.join(v2).wait().unwrap());
    }
}

#[test]
fn nested_load() {
    let loader = Loader::new(Batcher::new(2)).cached();
    let v1 = loader.load(3).map(|v| loader.load(v).wait().unwrap());
    let v2 = loader.load(4).map(|v| loader.load(v).wait().unwrap());
    assert_eq!((300, 400), v1.join(v2).wait().unwrap());
}

#[test]
fn nested_load_many() {
    let loader = Loader::new(Batcher::new(2)).cached();
    let v1 = loader.load(3).map(|v| loader.load_many(vec![v, v + 1, v + 2]).wait().unwrap());
    let v2 = loader.load(4).map(|v| loader.load_many(vec![v, v + 1, v + 2]).wait().unwrap());
    let expected = (vec![300, 310, 320], vec![400, 410, 420]);
    assert_eq!(expected, v1.join(v2).wait().unwrap());
}

#[test]
fn test_batch_fn_error() {
    let loader = Loader::<i32, i32, MyError>::new(BadBatcher).cached();
    let v1 = loader.load(1).wait();
    assert_eq!(LoadError::BatchFn(MyError::Unknown), v1.err().unwrap());
}

#[test]
fn test_result_val() {
    let loader = Loader::<i32, Result<i32, ValueError>, MyError>::new(BadBatcher).cached();
    let v1 = loader.load_many(vec![1, 2]).wait();
    assert_eq!(vec![Err(ValueError::NotEven), Ok(20)], v1.unwrap());
}

#[test]
fn test_batch_call_seq() {
    // batch size = 2, value will be (batch_fn call seq,  v * 10)
    let loader = Loader::<i32, (usize, i32), ()>::new(Batcher::new(2)).cached();
    let v1 = loader.load(1);
    let v2 = loader.load(2);
    let v3 = loader.load(3);
    let v4 = loader.load(4);
    let v5 = loader.load(1);
    let v6 = loader.load(2);

    //v1 and v2 should be in first batch
    assert_eq!((1, 10), v1.wait().unwrap());
    assert_eq!((1, 20), v2.wait().unwrap());
    //v3 and v4 should be in sencod batch
    assert_eq!((2, 30), v3.wait().unwrap());
    assert_eq!((2, 40), v4.wait().unwrap());
    //v5 and v6 should be using cache of first batch
    assert_eq!((1, 10), v5.wait().unwrap());
    assert_eq!((1, 20), v6.wait().unwrap());
}

#[test]
fn pass_to_thread() {
    use std::thread;

    let loader = Loader::new(Batcher::new(4)).cached();

    let l = loader.clone();
    let h1 = thread::spawn(move || {
        let v1 = l.load(1);
        let v2 = l.load(2);
        assert_eq!((10, 20), v1.join(v2).wait().unwrap());
    });

    let l2 = loader.clone();
    let h2 = thread::spawn(move || {
        let v1 = l2.load(1);
        let v2 = l2.load(2);
        assert_eq!((10, 20), v1.join(v2).wait().unwrap());
    });

    let _ = h1.join();
    let _ = h2.join();
}

#[test]
fn test_run_by_core() {
    let mut core = Core::new().unwrap();
    let loader = Loader::new(Batcher::new(10)).cached();
    let v1 = loader.load(3).and_then(|v| loader.load_many(vec![v, v + 1, v + 2]));
    let v2 = loader.load(4).and_then(|v| loader.load_many(vec![v, v + 1, v + 2]));
    let all = v1.join(v2);
    let output = core.run(all).unwrap();
    let expected = (vec![300, 310, 320], vec![400, 410, 420]);
    assert_eq!(expected, output);
}

#[test]
fn test_clear() {
    // batch size = 1, value will be (batch_fn call seq,  v * 10)
    let loader = Loader::<i32, (usize, i32), ()>::new(Batcher::new(1)).cached();
    let v1 = loader.load(1);
    let v2 = loader.load(1);

    loader.clear(&1);
    let v3 = loader.load(1);

    assert_eq!((1, 10), v1.wait().unwrap());
    assert_eq!((1, 10), v2.wait().unwrap());
    assert_eq!((2, 10), v3.wait().unwrap());
}

#[test]
fn test_clear_all() {
    // batch size = 2, value will be (batch_fn call seq,  v * 10)
    let loader = Loader::<i32, (usize, i32), ()>::new(Batcher::new(2)).cached();
    let v1 = loader.load(1);
    let v2 = loader.load(2);

    loader.clear_all();
    let v3 = loader.load(1);
    let v4 = loader.load(2);

    assert_eq!((1, 10), v1.wait().unwrap());
    assert_eq!((1, 20), v2.wait().unwrap());
    assert_eq!((2, 10), v3.wait().unwrap());
    assert_eq!((2, 20), v4.wait().unwrap());
}

#[test]
fn test_prime() {
    // batch size = 2, value will be (batch_fn call seq,  v * 10)
    let loader = Loader::<i32, (usize, i32), ()>::new(Batcher::new(1)).cached();
    loader.prime(1, (0, 101));
    let v1 = loader.load(1);
    let v2 = loader.load(2);
    loader.prime(2, (0, 201)); // should have no effect as key 2 are loaded alredy
    let v3 = loader.load(2);

    assert_eq!((0, 101), v1.wait().unwrap());
    assert_eq!((1, 20), v2.wait().unwrap());
    assert_eq!((1, 20), v3.wait().unwrap());
}<commit_msg>fix stable build<commit_after>use tests::*;
use {Loader, LoadError};
use cached;

use futures::Future;
use tokio_core::reactor::Core;

#[test]
fn assert_kinds() {
    fn _assert_send<T: Send>() {}
    fn _assert_sync<T: Sync>() {}
    fn _assert_clone<T: Clone>() {}
    _assert_send::<cached::Loader<u32, u32, u32>>();
    _assert_sync::<cached::Loader<u32, u32, u32>>();
    _assert_clone::<cached::Loader<u32, u32, u32>>();
}

#[test]
fn smoke() {
    let loader = Loader::new(Batcher::new(2)).cached();
    let v1 = loader.load(1);
    let v2 = loader.load(2);
    let v3 = loader.load(3);
    assert_eq!((10, 20), v1.join(v2).wait().unwrap());
    assert_eq!(30, v3.wait().unwrap());

    let many = loader.load_many(vec![10, 20, 30]);
    assert_eq!(vec![100, 200, 300], many.wait().unwrap());

    let loader_ref = &loader;
    {
        let v1 = loader_ref.load(1);
        let v2 = loader_ref.load(2);
        assert_eq!((10, 20), v1.join(v2).wait().unwrap());
    }
    {
        let v1 = loader_ref.load(3).map(|v| loader_ref.load(v).wait().unwrap());
        let v2 = loader_ref.load(4).map(|v| loader_ref.load(v).wait().unwrap());
        assert_eq!((300, 400), v1.join(v2).wait().unwrap());
    }
}

#[test]
fn nested_load() {
    let loader = Loader::new(Batcher::new(2)).cached();
    let v1 = loader.load(3).map(|v| loader.load(v).wait().unwrap());
    let v2 = loader.load(4).map(|v| loader.load(v).wait().unwrap());
    assert_eq!((300, 400), v1.join(v2).wait().unwrap());
}

#[test]
fn nested_load_many() {
    let loader = Loader::new(Batcher::new(2)).cached();
    let v1 = loader.load(3).map(|v| loader.load_many(vec![v, v + 1, v + 2]).wait().unwrap());
    let v2 = loader.load(4).map(|v| loader.load_many(vec![v, v + 1, v + 2]).wait().unwrap());
    let expected = (vec![300, 310, 320], vec![400, 410, 420]);
    assert_eq!(expected, v1.join(v2).wait().unwrap());
}

#[test]
fn test_batch_fn_error() {
    let loader = Loader::<i32, i32, MyError>::new(BadBatcher).cached();
    let v1 = loader.load(1).wait();
    assert_eq!(LoadError::BatchFn(MyError::Unknown), v1.err().unwrap());
}

#[test]
fn test_result_val() {
    let loader = Loader::<i32, Result<i32, ValueError>, MyError>::new(BadBatcher).cached();
    let v1 = loader.load_many(vec![1, 2]).wait();
    assert_eq!(vec![Err(ValueError::NotEven), Ok(20)], v1.unwrap());
}

#[test]
fn test_batch_call_seq() {
    // batch size = 2, value will be (batch_fn call seq,  v * 10)
    let loader = Loader::<i32, (usize, i32), ()>::new(Batcher::new(2)).cached();
    let v1 = loader.load(1);
    let v2 = loader.load(2);
    let v3 = loader.load(3);
    let v4 = loader.load(4);
    let v5 = loader.load(1);
    let v6 = loader.load(2);

    //v1 and v2 should be in first batch
    assert_eq!((1, 10), v1.wait().unwrap());
    assert_eq!((1, 20), v2.wait().unwrap());
    //v3 and v4 should be in sencod batch
    assert_eq!((2, 30), v3.wait().unwrap());
    assert_eq!((2, 40), v4.wait().unwrap());
    //v5 and v6 should be using cache of first batch
    assert_eq!((1, 10), v5.wait().unwrap());
    assert_eq!((1, 20), v6.wait().unwrap());
}

#[test]
fn pass_to_thread() {
    use std::thread;

    let loader = Loader::new(Batcher::new(4)).cached();

    let l = loader.clone();
    let h1 = thread::spawn(move || {
        let v1 = l.load(1);
        let v2 = l.load(2);
        assert_eq!((10, 20), v1.join(v2).wait().unwrap());
    });

    let l2 = loader.clone();
    let h2 = thread::spawn(move || {
        let v1 = l2.load(1);
        let v2 = l2.load(2);
        assert_eq!((10, 20), v1.join(v2).wait().unwrap());
    });

    let _ = h1.join();
    let _ = h2.join();
}

#[test]
fn test_run_by_core() {
    let mut core = Core::new().unwrap();
    let loader = Loader::new(Batcher::new(10)).cached();
    let v1 = loader.load(3).and_then(|v| loader.load_many(vec![v, v + 1, v + 2]));
    let v2 = loader.load(4).and_then(|v| loader.load_many(vec![v, v + 1, v + 2]));
    let all = v1.join(v2);
    let output = core.run(all).unwrap();
    let expected = (vec![300, 310, 320], vec![400, 410, 420]);
    assert_eq!(expected, output);
}

#[test]
fn test_clear() {
    // batch size = 1, value will be (batch_fn call seq,  v * 10)
    let loader = Loader::<i32, (usize, i32), ()>::new(Batcher::new(1)).cached();
    let v1 = loader.load(1);
    let v2 = loader.load(1);

    loader.clear(&1);
    let v3 = loader.load(1);

    assert_eq!((1, 10), v1.wait().unwrap());
    assert_eq!((1, 10), v2.wait().unwrap());
    assert_eq!((2, 10), v3.wait().unwrap());
}

#[test]
fn test_clear_all() {
    // batch size = 2, value will be (batch_fn call seq,  v * 10)
    let loader = Loader::<i32, (usize, i32), ()>::new(Batcher::new(2)).cached();
    let v1 = loader.load(1);
    let v2 = loader.load(2);

    loader.clear_all();
    let v3 = loader.load(1);
    let v4 = loader.load(2);

    assert_eq!((1, 10), v1.wait().unwrap());
    assert_eq!((1, 20), v2.wait().unwrap());
    assert_eq!((2, 10), v3.wait().unwrap());
    assert_eq!((2, 20), v4.wait().unwrap());
}

#[test]
fn test_prime() {
    // batch size = 2, value will be (batch_fn call seq,  v * 10)
    let loader = Loader::<i32, (usize, i32), ()>::new(Batcher::new(1)).cached();
    loader.prime(1, (0, 101));
    let v1 = loader.load(1);
    let v2 = loader.load(2);
    loader.prime(2, (0, 201)); // should have no effect as key 2 are loaded alredy
    let v3 = loader.load(2);

    assert_eq!((0, 101), v1.wait().unwrap());
    assert_eq!((1, 20), v2.wait().unwrap());
    assert_eq!((1, 20), v3.wait().unwrap());
}<|endoftext|>


<commit_before>// Copyright 2013 The Gfx-rs Developers.
//
// 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.

/// A handle to a data element holding a type `T`
pub struct Handle<T> {
    /// The index of the handle in the array of elements
    priv index: u16,
    /// The generation of the index, used for checking if the handle is invalid
    priv age: u16,
}

struct ElementIndex {
    /// The index of the element
    index: Option<u16>,
    /// The generation of the index
    age: u16,
}

impl ElementIndex {
    /// Create a new, uninitialised element index
    fn new() -> ElementIndex {
        ElementIndex { index: None, age: 0 }
    }
}

/// A resource manager
///
/// Performance is optimised towards batch operations on the stored data, at
/// the slight expense of handle lookups and element deletions.
///
/// ~~~
///                             +---+
///                             |   |  Handle
///                             +-.-+
///                               |
/// +- ResourceManager -----------|---------------------------------------+
/// |                             |                                       |
/// |   +---+---+---+---+---+---+-V-+---+---+---+ - - - +---+             |
/// |   | i | i |   | i | i | i | i |   |   | i |       |   |  indices    |
/// |   +-.-+-.-+---+-.-+-.-+-.-+-.-+---+---+-.-+ - - - +---+             |
/// |     |   |       |   |   |   |           |                           |
/// |     |   |   +---|---+   |   |           |                           |
/// |     |   |   |   |       |   |           |                           |
/// |     |   |   |   |   +---|---|-----------+                           |
/// |     |   |   |   |   |   |   |                                       |
/// |   +-V-+-V-+-V-+-V-+-V-+-V-+-V-+---+---+---+ - - - +---+             |
/// |   | T | T | T | T | T | T | T |   |   |   |       |   |  elements   |
/// |   +---+---+---+---+---+---+---+---+---+---+ - - - +---+             |
/// |                                                                     |
/// +---------------------------------------------------------------------+
/// ~~~
pub struct ResourceManager<T> {
    /// A sparse array of indices pointing to elements
    priv indices: ~[ElementIndex],  // Should be a fixed vector
    /// A packed array of elements.
    priv elements: ~[T],            // Should be a fixed vector
    /// The number of elements currently in storage
    priv len: u16,
}

impl<T> ResourceManager<T> {
    pub fn new() -> ResourceManager<T> {
        ResourceManager {
            indices: ~[],
            elements: ~[],
            len: 0,
        }
    }

    pub fn len(&self) -> u16 {
        self.len
    }

    pub fn is_empty(&self) -> bool {
        self.len == 0
    }

    /// Adds the element to the manager and returns a handle to it
    pub fn add(&mut self, _element: T) -> Handle<T> {
        // Check if adding the new element does not exeed the capacity of the
        // array of elements
        // At the first hole, set ElementIndex.index to Some(self.len)
        // Increment self.len
        // Increment the repective ElementIndex.age
        fail!("Not yet implemented.");
    }

    /// Work with the entry associated with a handle
    pub fn with<U>(&self, handle: Handle<T>, f: &fn(Option<&T>) -> U) -> U {
        match self.indices[handle.index as uint].index {
            Some(i) if handle.age >= self.indices[handle.index as uint].age => {
                f(Some(&self.elements[i as uint]))
            }
            _ => f(None),
        }
    }

    /// Work with the entry associated with a handle, allowing for mutation
    pub fn with_mut<U>(&mut self, handle: Handle<T>, f: &fn(Option<&mut T>) -> U) -> U {
        match self.indices[handle.index as uint].index {
            Some(i) if handle.age >= self.indices[handle.index as uint].age => {
                f(Some(&mut self.elements[i as uint]))
            }
            _ => f(None),
        }
    }

    /// Returns an iterator over the stored elements. This is fast, and
    /// therefore useful for batch operations over the elements.
    pub fn iter<'a>(&'a self) -> ElementIterator<'a, T> {
        ElementIterator { manager: self, index: 0 }
    }

    /// Returns an iterator over the stored elements, allowing for their
    /// modification. This is fast, and therefore useful for batch operations
    /// over the elements.
    pub fn mut_iter<'a>(&'a mut self) -> MutElementIterator<'a, T> {
        MutElementIterator { manager: self, index: 0 }
    }

    // Remove the
    pub fn remove(&mut self, _handle: Handle<T>) {
        // Check if the age is correct (fail or log if not?)
        // Set ElementIndex.index to None
        // Increment the respective ElementIndex.age
        // Repack array from the removed element up to the last element
        // Update the element indicies
        fail!("Not yet implemented.");
    }
}

// Element Iterators

/// An iterator over the elements stored in a `ResourceManager`
pub struct ElementIterator<'self, T> {
    priv manager: &'self ResourceManager<T>,
    priv index: uint,
}

impl<'self, T> Iterator<&'self T> for ElementIterator<'self, T> {
    #[inline]
    fn next(&mut self) -> Option<&'self T> {
        if self.index < self.manager.len as uint {
            let old = self.index;
            self.index += 1;
            Some(&self.manager.elements[old])
        } else {
            None
        }
    }

    #[inline]
    fn size_hint(&self) -> (uint, Option<uint>) {
        let l = self.manager.len as uint;
        (l, Some(l))
    }
}

/// An iterator for modifying the elements stored in a `ResourceManager`
pub struct MutElementIterator<'self, T> {
    priv manager: &'self mut ResourceManager<T>,
    priv index: uint,
}

impl<'self, T> Iterator<&'self mut T> for MutElementIterator<'self, T> {
    #[inline]
    fn next(&mut self) -> Option<&'self mut T> {
        if self.index < self.manager.len as uint {
            let old = self.index;
            self.index += 1;
            Some(&mut self.manager.elements[old])
        } else {
            None
        }
    }

    #[inline]
    fn size_hint(&self) -> (uint, Option<uint>) {
        let l = self.manager.len as uint;
        (l, Some(l))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_add() {
        let mut hm = ResourceManager::<int>::new();
        let h = hm.add(1);
        do hm.with(h) |x| {
            assert!(x.is_some());
        };
    }

    #[test]
    #[should_fail]
    fn test_add_overflow() {}

    #[test]
    fn test_with() {
        let mut hm = ResourceManager::<int>::new();
        let h = hm.add(1);
        do hm.with(h) |x| {
            let x = x.unwrap();
            assert_eq!(*x, 1);
        };
    }

    #[test]
    fn test_with_mut() {
        let mut hm = ResourceManager::<int>::new();
        let h = hm.add(1);
        do hm.with_mut(h) |x| {
            x.map(|x| *x = 12);
        };
        do hm.with(h) |x| {
            let x = x.unwrap();
            assert_eq!(*x, 12);
        };
    }

    #[test]
    fn test_remove_handle() {
        let mut hm = ResourceManager::<int>::new();
        let h = hm.add(1);
        hm.remove(h);
        assert!(hm.is_empty())
    }

    #[test]
    #[should_fail]
    fn test_remove_invalid_handle() {}
}
<commit_msg>Complete the initial implementation of the ResourceManager<commit_after>// Copyright 2013 The Gfx-rs Developers.
//
// 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.

use std::vec;
use std::u16;
use std::unstable::intrinsics;

/// A handle to a data element holding a type `T`
#[deriving(Clone, Eq)]
pub struct Handle<T> {
    /// The index of the handle in the array of elements
    priv index: u16,
    /// The generation of the index, used for checking if the handle is invalid
    priv count: u16,
}

struct ElementIndex {
    /// The index of the element
    index: Option<u16>,
    /// The generation of the index
    count: u16,
}

/// A resource manager
///
/// Performance is optimised towards batch operations on the stored data, at
/// the expense of direct handle lookups and element addition and removal.
///
pub struct ResourceManager<T> {
    //
    //                             +---+
    //                             |   |  Handle
    //                             +-.-+
    //                               |
    // +- ResourceManager -----------|---------------------------------------+
    // |                             |                                       |
    // |   +---+---+---+---+---+---+-V-+---+---+---+ - - - +---+             |
    // |   | i | i |   | i | i | i | i |   |   | i |       |   |  indices    |
    // |   +-.-+-.-+---+-.-+-.-+-.-+-.-+---+---+-.-+ - - - +---+             |
    // |     |   |       |   |   |   |           |                           |
    // |     |   |   +---|---+   |   |           |                           |
    // |     |   |   |   |       |   |           |                           |
    // |     |   |   |   |   +---|---|-----------+                           |
    // |     |   |   |   |   |   |   |                                       |
    // |   +-V-+-V-+-V-+-V-+-V-+-V-+-V-+---+---+---+ - - - +---+             |
    // |   | T | T | T | T | T | T | T |   |   |   |       |   |  elements   |
    // |   +---+---+---+---+---+---+---+---+---+---+ - - - +---+             |
    // |                                                                     |
    // +---------------------------------------------------------------------+
    //

    /// A sparse array of indices pointing to elements
    priv indices: ~[ElementIndex],  // TODO: Should be a fixed vector

    /// A packed array of elements.
    ///
    /// # Safety note
    ///
    /// The tail of this vector (after len) is unitialised memory.
    ///
    priv elements: ~[T],            // TODO: Should be a fixed vector

    /// The number of elements currently in storage
    priv len: u16,
}

impl<T> ResourceManager<T> {
    pub fn new() -> ResourceManager<T> {
        ResourceManager::new_sized(u16::max_value)
    }

    pub fn new_sized(len: u16) -> ResourceManager<T> {
        let len = len as uint;
        ResourceManager {
            indices: vec::from_fn(len, |_| ElementIndex { index: None, count: 0 }),
            elements: vec::from_fn(len, |_| unsafe { intrinsics::uninit() }),
            len: 0,
        }
    }

    pub fn capacity(&self) -> u16 {
        self.elements.len() as u16
    }

    pub fn len(&self) -> u16 {
        self.len
    }

    pub fn is_empty(&self) -> bool {
        self.len == 0
    }

    /// Adds the element to the manager and returns a handle to it
    pub fn add(&mut self, element: T) -> Handle<T> {
        let ei = self.indices.mut_iter().position(|i| i.index == None)
                     .expect("Could not find a free hole to store the element index.");
        self.elements[self.len as uint] = element;
        self.indices[ei].index = Some(self.len);
        self.len = self.len.checked_add(&1).unwrap();
        Handle { index: ei as u16, count: self.indices[ei].count }
    }

    /// Work with the entry associated with a handle
    pub fn with<U>(&self, handle: Handle<T>, f: &fn(Option<&T>) -> U) -> U {
        match self.indices[handle.index as uint].index {
            Some(i) if handle.count >= self.indices[handle.index as uint].count => {
                f(Some(&self.elements[i as uint]))
            }
            _ => f(None),
        }
    }

    /// Work with the entry associated with a handle, allowing for mutation
    pub fn with_mut<U>(&mut self, handle: Handle<T>, f: &fn(Option<&mut T>) -> U) -> U {
        match self.indices[handle.index as uint].index {
            Some(i) if handle.count >= self.indices[handle.index as uint].count => {
                f(Some(&mut self.elements[i as uint]))
            }
            _ => f(None),
        }
    }

    /// Remove the element associated with the handle
    pub fn remove(&mut self, handle: Handle<T>) {
        let hi = handle.index as uint;

        // TODO: improve these error messages
        assert!(handle.count >= self.indices[hi].count,
               "The element associated with this handle has already been \
                removed. (handle.count: {}, current.count: {})",
                handle.count, self.indices[hi].count);
        let ei = self.indices[hi].index.unwrap();

        // Remove the reference to the element from the indices and clear
        // it from the vector of elements
        self.indices[hi].index = None;
        self.indices[hi].count = self.indices[hi].count.checked_add(&1)
                                     .expect("The maximum age of the element was reached.");
        unsafe { self.elements[ei as uint] = intrinsics::uninit(); }

        // Swap the last element into the hole and update its index
        let lasti = self.len.checked_sub(&1).unwrap();
        self.elements.swap(ei as uint, lasti as uint);
        for elem_index in self.indices.mut_iter() {
            for i in elem_index.index.mut_iter() {
                if *i == lasti { *i = ei };
            }
        }
        // We reuse `lasti` so that we don't have to perform a checked
        // subtraction two times
        self.len = lasti;
    }

    /// Returns an iterator over the stored elements. This is fast, and
    /// therefore useful for batch operations over the elements.
    #[inline]
    pub fn iter<'a>(&'a self) -> ElementIterator<'a, T> {
        ElementIterator {
            iter: self.elements.slice_to(self.len as uint).iter()
        }
    }

    /// Returns a reversed iterator over the stored elements.
    #[inline]
    pub fn rev_iter<'a>(&'a self) -> ElementRevIterator<'a, T> {
        ElementRevIterator {
            iter: self.elements.slice_to(self.len as uint).rev_iter()
        }
    }

    /// Returns an iterator over the stored elements, allowing for their
    /// modification. This is fast, and therefore useful for batch operations
    /// over the elements.
    #[inline]
    pub fn mut_iter<'a>(&'a mut self) -> ElementMutIterator<'a, T> {
        ElementMutIterator {
            iter: self.elements.mut_slice_to(self.len as uint).mut_iter()
        }
    }

    /// Returns a reversed iterator over the stored elements, allowing for
    /// their modification.
    #[inline]
    fn mut_rev_iter<'a>(&'a mut self) -> ElementMutRevIterator<'a, T> {
        ElementMutRevIterator {
            iter: self.elements.mut_slice_to(self.len as uint).mut_rev_iter()
        }
    }
}

// Element Iterators

/// An iterator over the elements stored in a `ResourceManager`
pub struct ElementIterator<'self, T> {
    priv iter: vec::VecIterator<'self, T>,
}

impl<'self, T> Iterator<&'self T> for ElementIterator<'self, T> {
    #[inline] fn next(&mut self) -> Option<&'self T> { self.iter.next() }
    #[inline] fn size_hint(&self) -> (uint, Option<uint>) { self.iter.size_hint() }
}

/// A reversed iterator over the elements stored in a `ResourceManager`
pub struct ElementRevIterator<'self, T> {
    priv iter: vec::RevIterator<'self, T>,
}

impl<'self, T> Iterator<&'self T> for ElementRevIterator<'self, T> {
    #[inline] fn next(&mut self) -> Option<&'self T> { self.iter.next() }
    #[inline] fn size_hint(&self) -> (uint, Option<uint>) { self.iter.size_hint() }
}

/// An iterator for modifying the elements stored in a `ResourceManager`
pub struct ElementMutIterator<'self, T> {
    priv iter: vec::VecMutIterator<'self, T>,
}

impl<'self, T> Iterator<&'self mut T> for ElementMutIterator<'self, T> {
    #[inline] fn next(&mut self) -> Option<&'self mut T> { self.iter.next() }
    #[inline] fn size_hint(&self) -> (uint, Option<uint>) { self.iter.size_hint() }
}

/// A reversed iterator for modifying the elements stored in a `ResourceManager`
pub struct ElementMutRevIterator<'self, T> {
    priv iter: vec::MutRevIterator<'self, T>,
}

impl<'self, T> Iterator<&'self mut T> for ElementMutRevIterator<'self, T> {
    #[inline] fn next(&mut self) -> Option<&'self mut T> { self.iter.next() }
    #[inline] fn size_hint(&self) -> (uint, Option<uint>) { self.iter.size_hint() }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::u16;

    #[test]
    fn test_add() {
        let mut hm = ResourceManager::<int>::new();
        let h = hm.add(1);
        do hm.with(h) |x| {
            assert!(x.is_some());
        };
    }

    #[test]
    fn test_capacity() {
        assert_eq!(ResourceManager::<int>::new().capacity(), u16::max_value);
        assert_eq!(ResourceManager::<int>::new_sized(5).capacity(), 5);
    }

    #[test]
    fn test_len() {
        let mut hm = ResourceManager::<int>::new();
        let _ = hm.add(1);
        let _ = hm.add(1);
        let _ = hm.add(1);
        assert_eq!(hm.len(), 3);
    }

    #[test]
    fn test_is_empty() {
        let mut hm = ResourceManager::<int>::new();
        assert!(hm.is_empty());
        let _ = hm.add(1);
        assert!(!hm.is_empty());
    }

    #[test]
    #[should_fail]
    fn test_overflow() {
        let mut hm = ResourceManager::<int>::new_sized(4);
        let _ = hm.add(1);
        let _ = hm.add(2);
        let _ = hm.add(3);
        let _ = hm.add(4);
        let _ = hm.add(5);
    }

    #[test]
    fn test_with() {
        let mut hm = ResourceManager::<int>::new();
        let h = hm.add(1);
        do hm.with(h) |x| {
            let x = x.unwrap();
            assert_eq!(*x, 1);
        };
    }

    #[test]
    fn test_with_mut() {
        let mut hm = ResourceManager::<int>::new();
        let h = hm.add(1);
        do hm.with_mut(h) |x| {
            x.map(|x| *x = 12);
        };
        do hm.with(h) |x| {
            let x = x.unwrap();
            assert_eq!(*x, 12);
        };
    }

    #[test]
    fn test_remove_handle() {
        let mut hm = ResourceManager::<int>::new();
        let _ = hm.add(1);
        let h = hm.add(2);
        let _ = hm.add(3);
        hm.remove(h);
        let mut it = hm.iter();
        assert_eq!(*it.next().unwrap(), 1);
        assert_eq!(*it.next().unwrap(), 3);
        assert_eq!(it.next(), None);
    }

    #[test]
    #[should_fail]
    fn test_remove_invalid_handle() {
        let mut hm = ResourceManager::<int>::new();
        let _ = hm.add(1);
        let h = hm.add(2);
        let _ = hm.add(3);
        hm.remove(h);
        hm.remove(h);
    }

    #[test]
    fn test_iter() {
        let mut hm = ResourceManager::<int>::new();
        let _ = hm.add(1);
        let _ = hm.add(2);
        let _ = hm.add(3);
        let _ = hm.add(4);
        let _ = hm.add(5);
        let v = hm.iter().map(|x| *x).to_owned_vec();
        assert_eq!(v, ~[1, 2, 3, 4, 5]);
    }

    #[test]
    fn test_rev_iter() {
        let mut hm = ResourceManager::<int>::new();
        let _ = hm.add(1);
        let _ = hm.add(2);
        let _ = hm.add(3);
        let _ = hm.add(4);
        let _ = hm.add(5);
        let v = hm.rev_iter().map(|x| *x).to_owned_vec();
        assert_eq!(v, ~[5, 4, 3, 2, 1]);
    }

    #[test]
    fn test_mut_iter() {
        let mut hm = ResourceManager::<int>::new();
        let _ = hm.add(1);
        let _ = hm.add(2);
        let _ = hm.add(3);
        let _ = hm.add(4);
        let _ = hm.add(5);
        for x in hm.mut_iter() { *x *= 2; }
        let v = hm.iter().map(|x| *x).to_owned_vec();
        assert_eq!(v, ~[2, 4, 6, 8, 10]);
    }

    #[test]
    fn test_mut_rev_iter() {
        let mut hm = ResourceManager::<int>::new();
        let _ = hm.add(1);
        let _ = hm.add(2);
        let _ = hm.add(3);
        let _ = hm.add(4);
        let _ = hm.add(5);
        for x in hm.mut_rev_iter() { *x *= 2; }
        let v = hm.iter().map(|x| *x).to_owned_vec();
        assert_eq!(v, ~[2, 4, 6, 8, 10]);
    }
}
<|endoftext|>