<commit_before>//! Contains types used in this library
use std::convert::From;
use std::ops::{ Add, Mul };

pub use math::{ Matrix2d, Scalar, Vec2d };

/// A circle.
#[derive(Clone, Copy, Debug)]
pub struct Circle {
    center: Point,
    radius: Scalar,
}

/// The dimensions of a shape.
#[derive(Clone, Copy, Debug)]
pub struct Dimensions {
    width: Scalar,
    height: Scalar,
}

impl From<Vec2d> for Dimensions {
    fn from(v: Vec2d) -> Dimensions {
        Dimensions { width: v[0], height: v[1] }
    }
}

impl Dimensions {
    /// Convert dimenions to a vector.
    pub fn to_vec(&self) -> Vec2d {
        [self.width, self.height]
    }
}

impl Mul<Vec2d> for Dimensions {
    type Output = Dimensions;

    fn mul(self, v: Vec2d) -> Dimensions {
        Dimensions { width: self.width * v[0], height: self.height * v[1] }
    }
}

impl Mul<Dimensions> for Vec2d {
    type Output = Dimensions;

    fn mul(self, d: Dimensions) -> Dimensions {
        Dimensions { width: d.width * self[0], height: d.height * self[1] }
    }
}

impl Mul<Scalar> for Dimensions {
    type Output = Dimensions;

    fn mul(self, s: Scalar) -> Dimensions {
        Dimensions { width: self.width * s, height: self.height * s }
    }
}

impl Mul<Dimensions> for Scalar {
    type Output = Dimensions;

    fn mul(self, d: Dimensions) -> Dimensions {
        Dimensions { width: d.width * self, height: d.height * self }
    }
}

/// A point in the Cartesian plane.
#[derive(Clone, Copy, Debug)]
pub struct Point {
    x: Scalar,
    y: Scalar,
}

impl Add<Vec2d> for Point {
    type Output = Point;

    fn add(self, v: Vec2d) -> Point {
        Point { x: self.x + v[0], y: self.y + v[1] }
    }
}

impl Add<Point> for Vec2d {
    type Output = Point;

    fn add(self, p: Point) -> Point {
        Point { x: p.x + self[0], y: p.y + self[1] }
    }
}

impl Add<Scalar> for Point {
    type Output = Point;

    fn add(self, s: Scalar) -> Point {
        Point { x: self.x + s, y: self.y + s }
    }
}

impl Add<Point> for Scalar {
    type Output = Point;

    fn add(self, p: Point) -> Point {
        Point { x: p.x + self, y: p.y + self }
    }
}

impl From<Vec2d> for Point {
    fn from(v: Vec2d) -> Point {
        Point { x: v[0], y: v[1] }
    }
}

impl Point {
    /// Convert the point to a vector.
    pub fn to_vec(&self) -> Vec2d {
        [self.x, self.y]
    }
}

/// A rectangle whose top left corner is at (x, y).
#[derive(Clone, Copy, Debug)]
pub struct Rectangle {
    pos: Point,
    dim: Dimensions,
}

impl From<Circle> for Rectangle {
    /// Convert from a circle to a rectangle.
    fn from(c: Circle) -> Rectangle {
        Rectangle {
            pos: Point {
                x: c.center.x - c.radius,
                y: c.center.y - c.radius,
            },
            dim: Dimensions {
                width: 2.0 * c.radius,
                height: 2.0 * c.radius,
            },
        }
    }
}

impl From<(Point, Dimensions)> for Rectangle {
    /// Creates a rectangle from the position of its top left corner and its dimensions.
    fn from(rectangle: (Point, Dimensions)) -> Rectangle {
        let (pos, dim) = rectangle;
        Rectangle { pos: pos, dim: dim }
    }
}

impl From<[Scalar; 4]> for Rectangle {
    /// Creates a rectangle from an array.
    fn from(v: [Scalar; 4]) -> Rectangle {
        Rectangle {
            pos: Point { x: v[0], y: v[1] },
            dim: Dimensions { width: v[2], height: v[3] },
        }
    }
}

impl From<Square> for Rectangle {
    /// Convert a square into a rectangle.
    fn from(s: Square) -> Rectangle {
        Rectangle {
            pos: s.pos,
            dim: Dimensions { width: s.len, height: s.len },
        }
    }
}

impl Rectangle {
    /// Returns the position of the bottom side of the rectangle.
    pub fn bottom(&self) -> Scalar {
        self.pos.y + self.dim.height
    }

    /// Computes a rectangle with quadruple the surface area of self and with center
    /// (self.x, self.y).
    pub fn centered(&self) -> Rectangle {
        Rectangle {
            pos: Point {
                 x: self.pos.x - self.dim.width,
                 y: self.pos.y - self.dim.height,
            },
            dim: 2.0 * self.dim,
        }
    }

    /// Compute whether or not the point is inside the rectangle.
    #[inline]
    pub fn contains(&self, point: Point) -> bool {
        self.left() < point.x && point.x < self.right() &&
        self.top() < point.y && point.y < self.bottom()
    }

    /// Converts a rectangle into an array.
    pub fn into_vec(self) -> [Scalar; 4] {
        [self.pos.x, self.pos.y, self.dim.width, self.dim.height]
    }

    /// Returns the position of the left side of the rectangle.
    pub fn left(&self) -> Scalar {
        self.pos.x
    }

    /// Computes a rectangle whose perimeter forms the inside edge of margin with size m for self.
    #[inline(always)]
    pub fn margin(&self, m: Scalar) -> Rectangle {
        let w = self.dim.width - 2.0 * m;
        let h = self.dim.height - 2.0 * m;
        let (x, w)
            =   if w < 0.0 {
                    (self.pos.x + 0.5 * self.dim.width, 0.0)
                } else {
                    (self.pos.x + m, w)
                };
        let (y, h)
            =   if h < 0.0 {
                    (self.pos.y + 0.5 * self.dim.height, 0.0)
                } else {
                    (self.pos.y + m, h)
                };

        Rectangle {
            pos: Point { x: x, y: y },
            dim: Dimensions { width: w, height: h },
        }
    }

    /// Computes a rectangle translated (slid) in the direction of the vector a distance relative
    /// to the size of the rectangle. For example, self.relative([1.0, 1.0]) returns a rectangle
    /// one rectangle to the right and down from the original.
    #[inline(always)]
    pub fn relative(&self, v: Vec2d) -> Rectangle {
        Rectangle {
            pos: self.pos + (self.dim * v).to_vec(),
            dim: self.dim,
        }
    }

    /// Returns the position of the right side of the rectangle.
    pub fn right(&self) -> Scalar {
        self.pos.x + self.dim.width
    }

    /// Computes a scaled rectangle with the same position as self.
    pub fn scaled(&self, v: Vec2d) -> Rectangle {
        Rectangle {
            pos: self.pos,
            dim: self.dim * v,
        }
    }

    /// Converts a rectangle into a Vec4<Scalar> representation.
    pub fn to_vec(&self) -> [Scalar; 4] {
        [self.pos.x, self.pos.y, self.dim.width, self.dim.height]
    }

    /// Returns the position of the top side of the rectangle.
    pub fn top(&self) -> Scalar {
        self.pos.y
    }
}

/// A square.
#[derive(Clone, Copy, Debug)]
pub struct Square {
    pos: Point,
    len: Scalar,
}
<commit_msg>add Sub impl, remove impl on primitive types<commit_after>//! Contains types used in this library
use std::convert::From;
use std::ops::{ Add, Mul, Sub };

pub use math::{ Matrix2d, Scalar, Vec2d };

/// A circle.
#[derive(Clone, Copy, Debug)]
pub struct Circle {
    center: Point,
    radius: Scalar,
}

/// The dimensions of a shape.
#[derive(Clone, Copy, Debug)]
pub struct Dimensions {
    width: Scalar,
    height: Scalar,
}

impl From<Vec2d> for Dimensions {
    fn from(v: Vec2d) -> Dimensions {
        Dimensions { width: v[0], height: v[1] }
    }
}

impl Dimensions {
    /// Convert dimenions to a vector.
    pub fn to_vec(&self) -> Vec2d {
        [self.width, self.height]
    }
}

impl Mul<Vec2d> for Dimensions {
    type Output = Dimensions;

    fn mul(self, v: Vec2d) -> Dimensions {
        Dimensions { width: self.width * v[0], height: self.height * v[1] }
    }
}

impl Mul<Scalar> for Dimensions {
    type Output = Dimensions;

    fn mul(self, s: Scalar) -> Dimensions {
        Dimensions { width: self.width * s, height: self.height * s }
    }
}

/// A point in the Cartesian plane.
#[derive(Clone, Copy, Debug)]
pub struct Point {
    x: Scalar,
    y: Scalar,
}

impl Add<Scalar> for Point {
    type Output = Point;

    fn add(self, s: Scalar) -> Point {
        Point { x: self.x + s, y: self.y + s }
    }
}

impl Add<Vec2d> for Point {
    type Output = Point;

    fn add(self, v: Vec2d) -> Point {
        Point { x: self.x + v[0], y: self.y + v[1] }
    }
}

impl From<Vec2d> for Point {
    fn from(v: Vec2d) -> Point {
        Point { x: v[0], y: v[1] }
    }
}

impl Sub<Scalar> for Point {
    type Output = Point;

    fn sub(self, s: Scalar) -> Point {
        Point { x: self.x - s, y: self.y - s }
    }
}

impl Sub<Vec2d> for Point {
    type Output = Point;

    fn sub(self, v: Vec2d) -> Point {
        Point { x: self.x - v[0], y: self.y - v[1] }
    }
}

impl Point {
    /// Convert the point to a vector.
    pub fn to_vec(&self) -> Vec2d {
        [self.x, self.y]
    }
}

/// A rectangle whose top left corner is at (x, y).
#[derive(Clone, Copy, Debug)]
pub struct Rectangle {
    pos: Point,
    dim: Dimensions,
}

impl From<Circle> for Rectangle {
    /// Convert from a circle to a rectangle.
    fn from(c: Circle) -> Rectangle {
        Rectangle {
            pos: Point {
                x: c.center.x - c.radius,
                y: c.center.y - c.radius,
            },
            dim: Dimensions {
                width: 2.0 * c.radius,
                height: 2.0 * c.radius,
            },
        }
    }
}

impl From<(Point, Dimensions)> for Rectangle {
    /// Creates a rectangle from the position of its top left corner and its dimensions.
    fn from(rectangle: (Point, Dimensions)) -> Rectangle {
        let (pos, dim) = rectangle;
        Rectangle { pos: pos, dim: dim }
    }
}

impl From<[Scalar; 4]> for Rectangle {
    /// Creates a rectangle from an array.
    fn from(v: [Scalar; 4]) -> Rectangle {
        Rectangle {
            pos: Point { x: v[0], y: v[1] },
            dim: Dimensions { width: v[2], height: v[3] },
        }
    }
}

impl From<Square> for Rectangle {
    /// Convert a square into a rectangle.
    fn from(s: Square) -> Rectangle {
        Rectangle {
            pos: s.pos,
            dim: Dimensions { width: s.len, height: s.len },
        }
    }
}

impl Rectangle {
    /// Returns the position of the bottom side of the rectangle.
    pub fn bottom(&self) -> Scalar {
        self.pos.y + self.dim.height
    }

    /// Computes a rectangle with quadruple the surface area of self and with center
    /// (self.x, self.y).
    pub fn centered(&self) -> Rectangle {
        Rectangle {
            pos: Point {
                 x: self.pos.x - self.dim.width,
                 y: self.pos.y - self.dim.height,
            },
            dim: self.dim * 2.0,
        }
    }

    /// Compute whether or not the point is inside the rectangle.
    #[inline]
    pub fn contains(&self, point: Point) -> bool {
        self.left() < point.x && point.x < self.right() &&
        self.top() < point.y && point.y < self.bottom()
    }

    /// Converts a rectangle into an array.
    pub fn into_vec(self) -> [Scalar; 4] {
        [self.pos.x, self.pos.y, self.dim.width, self.dim.height]
    }

    /// Returns the position of the left side of the rectangle.
    pub fn left(&self) -> Scalar {
        self.pos.x
    }

    /// Computes a rectangle whose perimeter forms the inside edge of margin with size m for self.
    #[inline(always)]
    pub fn margin(&self, m: Scalar) -> Rectangle {
        let w = self.dim.width - 2.0 * m;
        let h = self.dim.height - 2.0 * m;
        let (x, w)
            =   if w < 0.0 {
                    (self.pos.x + 0.5 * self.dim.width, 0.0)
                } else {
                    (self.pos.x + m, w)
                };
        let (y, h)
            =   if h < 0.0 {
                    (self.pos.y + 0.5 * self.dim.height, 0.0)
                } else {
                    (self.pos.y + m, h)
                };

        Rectangle {
            pos: Point { x: x, y: y },
            dim: Dimensions { width: w, height: h },
        }
    }

    /// Computes a rectangle translated (slid) in the direction of the vector a distance relative
    /// to the size of the rectangle. For example, self.relative([1.0, 1.0]) returns a rectangle
    /// one rectangle to the right and down from the original.
    #[inline(always)]
    pub fn relative(&self, v: Vec2d) -> Rectangle {
        Rectangle {
            pos: self.pos + (self.dim * v).to_vec(),
            dim: self.dim,
        }
    }

    /// Returns the position of the right side of the rectangle.
    pub fn right(&self) -> Scalar {
        self.pos.x + self.dim.width
    }

    /// Computes a scaled rectangle with the same position as self.
    pub fn scaled(&self, v: Vec2d) -> Rectangle {
        Rectangle {
            pos: self.pos,
            dim: self.dim * v,
        }
    }

    /// Converts a rectangle into a Vec4<Scalar> representation.
    pub fn to_vec(&self) -> [Scalar; 4] {
        [self.pos.x, self.pos.y, self.dim.width, self.dim.height]
    }

    /// Returns the position of the top side of the rectangle.
    pub fn top(&self) -> Scalar {
        self.pos.y
    }
}

/// A square.
#[derive(Clone, Copy, Debug)]
pub struct Square {
    pos: Point,
    len: Scalar,
}
<|endoftext|>


<commit_before>//! An implementation of disjoint set forests for union find.

/// Data structure for efficient union find.
pub struct DisjointSetForest {
    /// Number of forest elements.
    count: usize,
    /// parent[i] is the index of the parent
    /// of the element with index i. If parent[i] == i
    /// then i is a root.
    parent: Vec<usize>,
    /// tree_size[i] is the size of the tree rooted at i.
    tree_size: Vec<usize>,
}

impl DisjointSetForest {

    /// Constructs forest of singletons with count elements.
    pub fn new(count: usize) -> DisjointSetForest {
        let mut parent = vec![0 as usize; count];
        for i in 0..count {
            parent[i] = i;
        }
        let tree_size = vec![1 as usize; count];
        DisjointSetForest {
            count: count,
            parent: parent,
            tree_size: tree_size}
    }

    /// Returns index of the root of the tree containing i.
    /// Needs mutable reference to self for path compression.
    pub fn root(&mut self, i: usize) -> usize {
        let mut j = i;
        loop {
            unsafe {
                let p = *self.parent.get_unchecked(j);
                *self.parent.get_unchecked_mut(j) = *self.parent.get_unchecked(p);
                if j == p {
                    break;
                }
                j = p;
            }
        }
        j
    }

    /// Returns true if i and j are in the same tree.
    /// Need mutable reference to self for path compression.
    pub fn find(&mut self, i: usize, j: usize) -> bool {
        self.root(i) == self.root(j)
    }

    /// Unions the trees containing i and j.
    pub fn union(&mut self, i: usize, j: usize) {
        let p = self.root(i);
        let q = self.root(j);
        if p == q {
            return;
        }
        unsafe {
            let p_size = *self.tree_size.get_unchecked(p);
            let q_size = *self.tree_size.get_unchecked(q);
            if p_size < q_size {
                *self.parent.get_unchecked_mut(p) = q;
                *self.tree_size.get_unchecked_mut(q) = p_size + q_size;
            }
            else {
                *self.parent.get_unchecked_mut(q) = p;
                *self.tree_size.get_unchecked_mut(p) = p_size + q_size;
            }
        }
    }

    /// Returns the elements of each tree.
    pub fn trees(&mut self) -> Vec<Vec<usize>> {
        use std::collections::HashMap;

        // Maps a tree root to the index of the set
        // containing its children
        let mut root_sets: HashMap<usize, usize> = HashMap::new();

        let mut sets: Vec<Vec<usize>> = vec![];
        for i in 0..self.count {
            let root = self.root(i);
            match root_sets.get(&root).map(|x| *x) {
                Some(set_idx) => {
                    sets[set_idx].push(i);
                },
                None => {
                    let idx = sets.len();
                    let set = vec![i];
                    sets.push(set);
                    root_sets.insert(root, idx);
                }
            }
        }
        sets
    }
}

#[cfg(test)]
mod test {

    use super::{
        DisjointSetForest
    };

    #[test]
    fn test_trees() {
        //    3         4
        //    |        /  \
        //    1       5    7
        //   /  \     |
        //  0    2    6
        let mut forest = DisjointSetForest {
            count: 8,
            // element:     0, 1, 2, 3, 4, 5, 6, 7
            parent:    vec![1, 3, 1, 3, 4, 4, 5, 4],
            tree_size: vec![1, 3, 1, 4, 4, 2, 1, 1]
        };

        assert_eq!(forest.trees(),
            vec![vec![0, 1, 2, 3], vec![4, 5, 6, 7]]);
    }

    #[test]
    fn test_union_find_sequence() {

        let mut forest = DisjointSetForest::new(6);
        // 0  1  2  3  4  5

        //                             0, 1, 2, 3, 4, 5
        assert_eq!(forest.parent, vec![0, 1, 2, 3, 4, 5]);

        forest.union(0, 4);
        // 0  1  2  3  5
        // |
        // 4

        //                             0, 1, 2, 3, 4, 5
        assert_eq!(forest.parent, vec![0, 1, 2, 3, 0, 5]);

        forest.union(1, 3);
        // 0  1  2  5
        // |  |
        // 4  3

        //                             0, 1, 2, 3, 4, 5
        assert_eq!(forest.parent, vec![0, 1, 2, 1, 0, 5]);

        forest.union(3, 2);
        // 0    1     5
        // |   / \
        // 4  3   2

        //                             0, 1, 2, 3, 4, 5
        assert_eq!(forest.parent, vec![0, 1, 1, 1, 0, 5]);

        forest.union(2, 4);
        //    1     5
        //  / | \
        // 0  3  2
        // |
        // 4

        //                             0, 1, 2, 3, 4, 5
        assert_eq!(forest.parent, vec![1, 1, 1, 1, 0, 5]);
    }

    // TODO: write a benchmark
}
<commit_msg>Add num_trees to DisjointSetForest<commit_after>//! An implementation of disjoint set forests for union find.

/// Data structure for efficient union find.
pub struct DisjointSetForest {
    /// Number of forest elements.
    count: usize,
    /// parent[i] is the index of the parent
    /// of the element with index i. If parent[i] == i
    /// then i is a root.
    parent: Vec<usize>,
    /// tree_size[i] is the size of the tree rooted at i.
    tree_size: Vec<usize>,
}

impl DisjointSetForest {

    /// Constructs forest of singletons with count elements.
    pub fn new(count: usize) -> DisjointSetForest {
        let mut parent = vec![0 as usize; count];
        for i in 0..count {
            parent[i] = i;
        }
        let tree_size = vec![1 as usize; count];
        DisjointSetForest {
            count: count,
            parent: parent,
            tree_size: tree_size}
    }

    /// Returns the number of trees in the forest.
    pub fn num_trees(&self) -> usize {
        self.parent
            .iter()
            .enumerate()
            .fold(0, |acc, (i, p)| acc + if i == *p { 1 } else { 0 })
    }

    /// Returns index of the root of the tree containing i.
    /// Needs mutable reference to self for path compression.
    pub fn root(&mut self, i: usize) -> usize {
        let mut j = i;
        loop {
            unsafe {
                let p = *self.parent.get_unchecked(j);
                *self.parent.get_unchecked_mut(j) = *self.parent.get_unchecked(p);
                if j == p {
                    break;
                }
                j = p;
            }
        }
        j
    }

    /// Returns true if i and j are in the same tree.
    /// Need mutable reference to self for path compression.
    pub fn find(&mut self, i: usize, j: usize) -> bool {
        self.root(i) == self.root(j)
    }

    /// Unions the trees containing i and j.
    pub fn union(&mut self, i: usize, j: usize) {
        let p = self.root(i);
        let q = self.root(j);
        if p == q {
            return;
        }
        unsafe {
            let p_size = *self.tree_size.get_unchecked(p);
            let q_size = *self.tree_size.get_unchecked(q);
            if p_size < q_size {
                *self.parent.get_unchecked_mut(p) = q;
                *self.tree_size.get_unchecked_mut(q) = p_size + q_size;
            }
            else {
                *self.parent.get_unchecked_mut(q) = p;
                *self.tree_size.get_unchecked_mut(p) = p_size + q_size;
            }
        }
    }

    /// Returns the elements of each tree.
    pub fn trees(&mut self) -> Vec<Vec<usize>> {
        use std::collections::HashMap;

        // Maps a tree root to the index of the set
        // containing its children
        let mut root_sets: HashMap<usize, usize> = HashMap::new();

        let mut sets: Vec<Vec<usize>> = vec![];
        for i in 0..self.count {
            let root = self.root(i);
            match root_sets.get(&root).map(|x| *x) {
                Some(set_idx) => {
                    sets[set_idx].push(i);
                },
                None => {
                    let idx = sets.len();
                    let set = vec![i];
                    sets.push(set);
                    root_sets.insert(root, idx);
                }
            }
        }
        sets
    }
}

#[cfg(test)]
mod test {

    use super::{
        DisjointSetForest
    };

    #[test]
    fn test_trees() {
        //    3         4
        //    |        /  \
        //    1       5    7
        //   /  \     |
        //  0    2    6
        let mut forest = DisjointSetForest {
            count: 8,
            // element:     0, 1, 2, 3, 4, 5, 6, 7
            parent:    vec![1, 3, 1, 3, 4, 4, 5, 4],
            tree_size: vec![1, 3, 1, 4, 4, 2, 1, 1]
        };

        assert_eq!(forest.trees(),
            vec![vec![0, 1, 2, 3], vec![4, 5, 6, 7]]);
    }

    #[test]
    fn test_union_find_sequence() {

        let mut forest = DisjointSetForest::new(6);
        // 0  1  2  3  4  5

        //                             0, 1, 2, 3, 4, 5
        assert_eq!(forest.parent, vec![0, 1, 2, 3, 4, 5]);
        assert_eq!(forest.num_trees(), 6);

        forest.union(0, 4);
        // 0  1  2  3  5
        // |
        // 4

        //                             0, 1, 2, 3, 4, 5
        assert_eq!(forest.parent, vec![0, 1, 2, 3, 0, 5]);
        assert_eq!(forest.num_trees(), 5);

        forest.union(1, 3);
        // 0  1  2  5
        // |  |
        // 4  3

        //                             0, 1, 2, 3, 4, 5
        assert_eq!(forest.parent, vec![0, 1, 2, 1, 0, 5]);
        assert_eq!(forest.num_trees(), 4);

        forest.union(3, 2);
        // 0    1     5
        // |   / \
        // 4  3   2

        //                             0, 1, 2, 3, 4, 5
        assert_eq!(forest.parent, vec![0, 1, 1, 1, 0, 5]);
        assert_eq!(forest.num_trees(), 3);

        forest.union(2, 4);
        //    1     5
        //  / | \
        // 0  3  2
        // |
        // 4

        //                             0, 1, 2, 3, 4, 5
        assert_eq!(forest.parent, vec![1, 1, 1, 1, 0, 5]);
        assert_eq!(forest.num_trees(), 2);
    }

    // TODO: write a benchmark
}
<|endoftext|>


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

use bluetooth_traits::{BluetoothRequest, BluetoothResponse};
use bluetooth_traits::blocklist::{Blocklist, uuid_is_blocklisted};
use dom::bindings::codegen::Bindings::BluetoothDeviceBinding::BluetoothDeviceMethods;
use dom::bindings::codegen::Bindings::BluetoothRemoteGATTServerBinding;
use dom::bindings::codegen::Bindings::BluetoothRemoteGATTServerBinding::BluetoothRemoteGATTServerMethods;
use dom::bindings::error::Error::{self, Network, Security};
use dom::bindings::error::ErrorResult;
use dom::bindings::js::{JS, MutHeap, Root};
use dom::bindings::reflector::{Reflectable, Reflector, reflect_dom_object};
use dom::bindings::str::DOMString;
use dom::bluetooth::{AsyncBluetoothListener, response_async};
use dom::bluetoothdevice::BluetoothDevice;
use dom::bluetoothremotegattservice::BluetoothRemoteGATTService;
use dom::bluetoothuuid::{BluetoothServiceUUID, BluetoothUUID};
use dom::globalscope::GlobalScope;
use dom::promise::Promise;
use ipc_channel::ipc::{self, IpcSender};
use js::jsapi::JSContext;
use std::cell::Cell;
use std::rc::Rc;

// https://webbluetoothcg.github.io/web-bluetooth/#bluetoothremotegattserver
#[dom_struct]
pub struct BluetoothRemoteGATTServer {
    reflector_: Reflector,
    device: MutHeap<JS<BluetoothDevice>>,
    connected: Cell<bool>,
}

impl BluetoothRemoteGATTServer {
    pub fn new_inherited(device: &BluetoothDevice) -> BluetoothRemoteGATTServer {
        BluetoothRemoteGATTServer {
            reflector_: Reflector::new(),
            device: MutHeap::new(device),
            connected: Cell::new(false),
        }
    }

    pub fn new(global: &GlobalScope, device: &BluetoothDevice) -> Root<BluetoothRemoteGATTServer> {
        reflect_dom_object(box BluetoothRemoteGATTServer::new_inherited(device),
                           global,
                           BluetoothRemoteGATTServerBinding::Wrap)
    }

    fn get_bluetooth_thread(&self) -> IpcSender<BluetoothRequest> {
        self.global().as_window().bluetooth_thread()
    }
}

impl BluetoothRemoteGATTServerMethods for BluetoothRemoteGATTServer {
    // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-device
    fn Device(&self) -> Root<BluetoothDevice> {
        self.device.get()
    }

    // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-connected
    fn Connected(&self) -> bool {
        self.connected.get()
    }

    #[allow(unrooted_must_root)]
    // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-connect
    fn Connect(&self) -> Rc<Promise> {
        // Step 1.
        let p = Promise::new(&self.global());
        let sender = response_async(&p, self);

        // TODO: Step 2: Implement representedDevice internal slot for BluetoothDevice.

        // TODO: Step 3: Check if the UA is currently using the Bluetooth system.

        // TODO: Step 4: Implement activeAlgorithms internal slot for BluetoothRemoteGATTServer.

        // TODO: Step 5.1 - 5.2: Implement activeAlgorithms, representedDevice internal slots
        // and the` garbage-collect the connection` algorithm.

        // Note: Steps 5.1.1 and 5.1.3 are in components/bluetooth/lib.rs in the gatt_server_connect function.
        // Steps 5.2.4 - 5.2.5  are in response function.
        self.get_bluetooth_thread().send(
            BluetoothRequest::GATTServerConnect(String::from(self.Device().Id()), sender)).unwrap();
        // Step 5: return promise.
        return p;
    }

    // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-disconnect
    fn Disconnect(&self) -> ErrorResult {
        // TODO: Step 1: Implement activeAlgorithms internal slot for BluetoothRemoteGATTServer.

        // TODO: Step 2: Check if this.connected is false here too.
        let (sender, receiver) = ipc::channel().unwrap();
        self.get_bluetooth_thread().send(
            BluetoothRequest::GATTServerDisconnect(String::from(self.Device().Id()), sender)).unwrap();
        let server = receiver.recv().unwrap();

        // TODO: Step 3: Implement the `clean up the disconnected device` algorithm.

        // TODO: Step 4: Implement representedDevice internal slot for BluetoothDevice.

        // TODO: Step 5: Implement the `garbage-collect the connection` algorithm.
        match server {
            Ok(connected) => {
                self.connected.set(connected);
                Ok(())
            },
            Err(error) => {
                Err(Error::from(error))
            },
        }
    }

    #[allow(unrooted_must_root)]
    // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-getprimaryservice
    fn GetPrimaryService(&self, service: BluetoothServiceUUID) -> Rc<Promise> {
        // TODO: Step 1: Implement the Permission API and the allowedServices BluetoothDevice internal slot.
        // Subsequent steps are relative to https://webbluetoothcg.github.io/web-bluetooth/#getgattchildren
        let p = Promise::new(&self.global());
        let p_cx = p.global().get_cx();

        // Step 1.
        let uuid = match BluetoothUUID::service(service) {
            Ok(uuid) => uuid.to_string(),
            Err(e) => {
                p.reject_error(p_cx, e);
                return p;
            }
        };

        // Step 2.
        if uuid_is_blocklisted(uuid.as_ref(), Blocklist::All) {
            p.reject_error(p_cx, Security);
            return p;
        }

        // Step 3 - 4.
        if !self.Device().Gatt().Connected() {
            p.reject_error(p_cx, Network);
            return p;
        }

        // TODO: Step 5: Implement representedDevice internal slot for BluetoothDevice.

        // Note: Steps 6 - 7 are implemented in components/bluetooth/lib.rs in get_primary_service function
        // and in handle_response function.
        let sender = response_async(&p, self);
        self.get_bluetooth_thread().send(
            BluetoothRequest::GetPrimaryService(String::from(self.Device().Id()), uuid, sender)).unwrap();
        return p;
    }

    #[allow(unrooted_must_root)]
    // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-getprimaryservices
    fn GetPrimaryServices(&self, service: Option<BluetoothServiceUUID>) -> Rc<Promise> {
        // TODO: Step 1: Implement the Permission API and the allowedServices BluetoothDevice internal slot.
        // Subsequent steps are relative to https://webbluetoothcg.github.io/web-bluetooth/#getgattchildren
        let p = Promise::new(&self.global());
        let p_cx = p.global().get_cx();

        let mut uuid: Option<String> = None;
        if let Some(s) = service {
            // Step 1.
            uuid = match BluetoothUUID::service(s) {
                Ok(uuid) => Some(uuid.to_string()),
                Err(e) => {
                    p.reject_error(p_cx, e);
                    return p;
                }
            };
            if let Some(ref uuid) = uuid {
                // Step 2.
                if uuid_is_blocklisted(uuid.as_ref(), Blocklist::All) {
                    p.reject_error(p_cx, Security);
                    return p;
                }
            }
        };

        // Step 3 - 4.
        if !self.Device().Gatt().Connected() {
            p.reject_error(p_cx, Network);
            return p;
        }

        // TODO: Step 5: Implement representedDevice internal slot for BluetoothDevice.

        // Note: Steps 6 - 7 are implemented in components/bluetooth/lib.rs in get_primary_services function
        // and in handle_response function.
        let sender = response_async(&p, self);
        self.get_bluetooth_thread().send(
            BluetoothRequest::GetPrimaryServices(String::from(self.Device().Id()), uuid, sender)).unwrap();
        return p;
    }
}

impl AsyncBluetoothListener for BluetoothRemoteGATTServer {
    fn handle_response(&self, response: BluetoothResponse, promise_cx: *mut JSContext, promise: &Rc<Promise>) {
        match response {
            // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-connect
            BluetoothResponse::GATTServerConnect(connected) => {
                // Step 5.2.4.
                self.connected.set(connected);

                // Step 5.2.5.
                promise.resolve_native(promise_cx, self);
            },
            // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-getprimaryservice
            // https://webbluetoothcg.github.io/web-bluetooth/#getgattchildren
            // Step 7.
            BluetoothResponse::GetPrimaryService(service) => {
                let context = self.device.get().get_context();
                let mut service_map = context.get_service_map().borrow_mut();
                if let Some(existing_service) = service_map.get(&service.instance_id) {
                    promise.resolve_native(promise_cx, &existing_service.get());
                }
                let bt_service = BluetoothRemoteGATTService::new(&self.global(),
                                                                 &self.device.get(),
                                                                 DOMString::from(service.uuid),
                                                                 service.is_primary,
                                                                 service.instance_id.clone());
                service_map.insert(service.instance_id, MutHeap::new(&bt_service));
                promise.resolve_native(promise_cx, &bt_service);
            },
            // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-getprimaryservices
            // https://webbluetoothcg.github.io/web-bluetooth/#getgattchildren
            // Step 7.
            BluetoothResponse::GetPrimaryServices(services_vec) => {
                let mut services = vec!();
                let context = self.device.get().get_context();
                let mut service_map = context.get_service_map().borrow_mut();
                for service in services_vec {
                    let bt_service = match service_map.get(&service.instance_id) {
                        Some(existing_service) => existing_service.get(),
                        None => {
                            BluetoothRemoteGATTService::new(&self.global(),
                                                            &self.device.get(),
                                                            DOMString::from(service.uuid),
                                                            service.is_primary,
                                                            service.instance_id.clone())
                        },
                    };
                    if !service_map.contains_key(&service.instance_id) {
                        service_map.insert(service.instance_id, MutHeap::new(&bt_service));
                    }
                    services.push(bt_service);
                }
                promise.resolve_native(promise_cx, &services);
            },
            _ => promise.reject_error(promise_cx, Error::Type("Something went wrong...".to_owned())),
        }
    }
}
<commit_msg>Add missing connection check for disconnect<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use bluetooth_traits::{BluetoothRequest, BluetoothResponse};
use bluetooth_traits::blocklist::{Blocklist, uuid_is_blocklisted};
use dom::bindings::codegen::Bindings::BluetoothDeviceBinding::BluetoothDeviceMethods;
use dom::bindings::codegen::Bindings::BluetoothRemoteGATTServerBinding;
use dom::bindings::codegen::Bindings::BluetoothRemoteGATTServerBinding::BluetoothRemoteGATTServerMethods;
use dom::bindings::error::Error::{self, Network, Security};
use dom::bindings::error::ErrorResult;
use dom::bindings::js::{JS, MutHeap, Root};
use dom::bindings::reflector::{Reflectable, Reflector, reflect_dom_object};
use dom::bindings::str::DOMString;
use dom::bluetooth::{AsyncBluetoothListener, response_async};
use dom::bluetoothdevice::BluetoothDevice;
use dom::bluetoothremotegattservice::BluetoothRemoteGATTService;
use dom::bluetoothuuid::{BluetoothServiceUUID, BluetoothUUID};
use dom::globalscope::GlobalScope;
use dom::promise::Promise;
use ipc_channel::ipc::{self, IpcSender};
use js::jsapi::JSContext;
use std::cell::Cell;
use std::rc::Rc;

// https://webbluetoothcg.github.io/web-bluetooth/#bluetoothremotegattserver
#[dom_struct]
pub struct BluetoothRemoteGATTServer {
    reflector_: Reflector,
    device: MutHeap<JS<BluetoothDevice>>,
    connected: Cell<bool>,
}

impl BluetoothRemoteGATTServer {
    pub fn new_inherited(device: &BluetoothDevice) -> BluetoothRemoteGATTServer {
        BluetoothRemoteGATTServer {
            reflector_: Reflector::new(),
            device: MutHeap::new(device),
            connected: Cell::new(false),
        }
    }

    pub fn new(global: &GlobalScope, device: &BluetoothDevice) -> Root<BluetoothRemoteGATTServer> {
        reflect_dom_object(box BluetoothRemoteGATTServer::new_inherited(device),
                           global,
                           BluetoothRemoteGATTServerBinding::Wrap)
    }

    fn get_bluetooth_thread(&self) -> IpcSender<BluetoothRequest> {
        self.global().as_window().bluetooth_thread()
    }
}

impl BluetoothRemoteGATTServerMethods for BluetoothRemoteGATTServer {
    // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-device
    fn Device(&self) -> Root<BluetoothDevice> {
        self.device.get()
    }

    // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-connected
    fn Connected(&self) -> bool {
        self.connected.get()
    }

    #[allow(unrooted_must_root)]
    // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-connect
    fn Connect(&self) -> Rc<Promise> {
        // Step 1.
        let p = Promise::new(&self.global());
        let sender = response_async(&p, self);

        // TODO: Step 2: Implement representedDevice internal slot for BluetoothDevice.

        // TODO: Step 3: Check if the UA is currently using the Bluetooth system.

        // TODO: Step 4: Implement activeAlgorithms internal slot for BluetoothRemoteGATTServer.

        // TODO: Step 5.1 - 5.2: Implement activeAlgorithms, representedDevice internal slots
        // and the` garbage-collect the connection` algorithm.

        // Note: Steps 5.1.1 and 5.1.3 are in components/bluetooth/lib.rs in the gatt_server_connect function.
        // Steps 5.2.4 - 5.2.5  are in response function.
        self.get_bluetooth_thread().send(
            BluetoothRequest::GATTServerConnect(String::from(self.Device().Id()), sender)).unwrap();
        // Step 5: return promise.
        return p;
    }

    // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-disconnect
    fn Disconnect(&self) -> ErrorResult {
        // TODO: Step 1: Implement activeAlgorithms internal slot for BluetoothRemoteGATTServer.

        // Step 2.
        if !self.Connected() {
            return Ok(());
        }
        let (sender, receiver) = ipc::channel().unwrap();
        self.get_bluetooth_thread().send(
            BluetoothRequest::GATTServerDisconnect(String::from(self.Device().Id()), sender)).unwrap();
        let server = receiver.recv().unwrap();

        // TODO: Step 3: Implement the `clean up the disconnected device` algorithm.

        // TODO: Step 4: Implement representedDevice internal slot for BluetoothDevice.

        // TODO: Step 5: Implement the `garbage-collect the connection` algorithm.
        match server {
            Ok(connected) => {
                self.connected.set(connected);
                Ok(())
            },
            Err(error) => {
                Err(Error::from(error))
            },
        }
    }

    #[allow(unrooted_must_root)]
    // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-getprimaryservice
    fn GetPrimaryService(&self, service: BluetoothServiceUUID) -> Rc<Promise> {
        // TODO: Step 1: Implement the Permission API and the allowedServices BluetoothDevice internal slot.
        // Subsequent steps are relative to https://webbluetoothcg.github.io/web-bluetooth/#getgattchildren
        let p = Promise::new(&self.global());
        let p_cx = p.global().get_cx();

        // Step 1.
        let uuid = match BluetoothUUID::service(service) {
            Ok(uuid) => uuid.to_string(),
            Err(e) => {
                p.reject_error(p_cx, e);
                return p;
            }
        };

        // Step 2.
        if uuid_is_blocklisted(uuid.as_ref(), Blocklist::All) {
            p.reject_error(p_cx, Security);
            return p;
        }

        // Step 3 - 4.
        if !self.Device().Gatt().Connected() {
            p.reject_error(p_cx, Network);
            return p;
        }

        // TODO: Step 5: Implement representedDevice internal slot for BluetoothDevice.

        // Note: Steps 6 - 7 are implemented in components/bluetooth/lib.rs in get_primary_service function
        // and in handle_response function.
        let sender = response_async(&p, self);
        self.get_bluetooth_thread().send(
            BluetoothRequest::GetPrimaryService(String::from(self.Device().Id()), uuid, sender)).unwrap();
        return p;
    }

    #[allow(unrooted_must_root)]
    // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-getprimaryservices
    fn GetPrimaryServices(&self, service: Option<BluetoothServiceUUID>) -> Rc<Promise> {
        // TODO: Step 1: Implement the Permission API and the allowedServices BluetoothDevice internal slot.
        // Subsequent steps are relative to https://webbluetoothcg.github.io/web-bluetooth/#getgattchildren
        let p = Promise::new(&self.global());
        let p_cx = p.global().get_cx();

        let mut uuid: Option<String> = None;
        if let Some(s) = service {
            // Step 1.
            uuid = match BluetoothUUID::service(s) {
                Ok(uuid) => Some(uuid.to_string()),
                Err(e) => {
                    p.reject_error(p_cx, e);
                    return p;
                }
            };
            if let Some(ref uuid) = uuid {
                // Step 2.
                if uuid_is_blocklisted(uuid.as_ref(), Blocklist::All) {
                    p.reject_error(p_cx, Security);
                    return p;
                }
            }
        };

        // Step 3 - 4.
        if !self.Device().Gatt().Connected() {
            p.reject_error(p_cx, Network);
            return p;
        }

        // TODO: Step 5: Implement representedDevice internal slot for BluetoothDevice.

        // Note: Steps 6 - 7 are implemented in components/bluetooth/lib.rs in get_primary_services function
        // and in handle_response function.
        let sender = response_async(&p, self);
        self.get_bluetooth_thread().send(
            BluetoothRequest::GetPrimaryServices(String::from(self.Device().Id()), uuid, sender)).unwrap();
        return p;
    }
}

impl AsyncBluetoothListener for BluetoothRemoteGATTServer {
    fn handle_response(&self, response: BluetoothResponse, promise_cx: *mut JSContext, promise: &Rc<Promise>) {
        match response {
            // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-connect
            BluetoothResponse::GATTServerConnect(connected) => {
                // Step 5.2.4.
                self.connected.set(connected);

                // Step 5.2.5.
                promise.resolve_native(promise_cx, self);
            },
            // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-getprimaryservice
            // https://webbluetoothcg.github.io/web-bluetooth/#getgattchildren
            // Step 7.
            BluetoothResponse::GetPrimaryService(service) => {
                let context = self.device.get().get_context();
                let mut service_map = context.get_service_map().borrow_mut();
                if let Some(existing_service) = service_map.get(&service.instance_id) {
                    promise.resolve_native(promise_cx, &existing_service.get());
                }
                let bt_service = BluetoothRemoteGATTService::new(&self.global(),
                                                                 &self.device.get(),
                                                                 DOMString::from(service.uuid),
                                                                 service.is_primary,
                                                                 service.instance_id.clone());
                service_map.insert(service.instance_id, MutHeap::new(&bt_service));
                promise.resolve_native(promise_cx, &bt_service);
            },
            // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-getprimaryservices
            // https://webbluetoothcg.github.io/web-bluetooth/#getgattchildren
            // Step 7.
            BluetoothResponse::GetPrimaryServices(services_vec) => {
                let mut services = vec!();
                let context = self.device.get().get_context();
                let mut service_map = context.get_service_map().borrow_mut();
                for service in services_vec {
                    let bt_service = match service_map.get(&service.instance_id) {
                        Some(existing_service) => existing_service.get(),
                        None => {
                            BluetoothRemoteGATTService::new(&self.global(),
                                                            &self.device.get(),
                                                            DOMString::from(service.uuid),
                                                            service.is_primary,
                                                            service.instance_id.clone())
                        },
                    };
                    if !service_map.contains_key(&service.instance_id) {
                        service_map.insert(service.instance_id, MutHeap::new(&bt_service));
                    }
                    services.push(bt_service);
                }
                promise.resolve_native(promise_cx, &services);
            },
            _ => promise.reject_error(promise_cx, Error::Type("Something went wrong...".to_owned())),
        }
    }
}
<|endoftext|>


<commit_before>//
//   Copyright 2016 Andrew Hunter
//
//   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::rc::*;

use super::component::*;
use super::super::tree::*;

struct FunctionComponent;

impl Component for FunctionComponent {
}

impl Drop for FunctionComponent {
    fn drop(&mut self) {
    }
}

impl ComponentFactory for Fn(&TreeChange) -> TreeChange {
    ///
    /// Creates a component that consumes from a particular tree and publishes to a different tree
    ///
    fn create(&self, consumer: ConsumerRef, publisher: PublisherRef) -> ComponentRef {
        let mut our_consumer    = consumer;
        let mut our_publisher   = publisher;
        let action              = Box::new(self.clone());

        our_consumer.subscribe(TreeAddress::Here, TreeExtent::SubTree, Box::new(move |change| {
            //let change_result = action(change);
            //our_publisher.publish(change_result);
        }));

        return Rc::new(FunctionComponent);
    }
}<commit_msg>Here's a factory function that works (kind of, there are mutability problems)<commit_after>//
//   Copyright 2016 Andrew Hunter
//
//   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::rc::*;

use super::component::*;
use super::super::tree::*;

struct FunctionComponent;

impl Component for FunctionComponent {
}

impl Drop for FunctionComponent {
    fn drop(&mut self) {
    }
}

impl FunctionComponent {
    fn register<F: Fn(&TreeChange) -> TreeChange + 'static>(&self, consumer: ConsumerRef, publisher: PublisherRef, func: F) {
        let mut our_consumer    = consumer;
        let mut our_publisher   = publisher;
        let mut action          = Box::new(func);

        our_consumer.subscribe(TreeAddress::Here, TreeExtent::SubTree, Box::new(move |change| {
            let change_result = action(change);
            //our_publisher.publish(change_result);
        }));
    }
}

impl ComponentFactory for Fn(&TreeChange) -> TreeChange + 'static {
    ///
    /// Creates a component that consumes from a particular tree and publishes to a different tree
    ///
    fn create(&self, consumer: ConsumerRef, publisher: PublisherRef) -> ComponentRef {
        let mut our_consumer    = consumer;
        let mut our_publisher   = publisher;
        let action              = Box::new(self.clone());

        our_consumer.subscribe(TreeAddress::Here, TreeExtent::SubTree, Box::new(move |change| {
            //let change_result = action(change);
            //our_publisher.publish(change_result);
        }));

        return Rc::new(FunctionComponent);
    }
}<|endoftext|>


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

use crate::glyph_rasterizer::{FontInstance, GlyphFormat, GlyphKey, GlyphRasterizer};
use crate::internal_types::FastHashMap;
use crate::render_backend::{FrameId, FrameStamp};
use crate::render_task::RenderTaskCache;
use crate::resource_cache::ResourceClassCache;
use std::sync::Arc;
use crate::texture_cache::{EvictionNotice, TextureCache};
use crate::texture_cache::TextureCacheHandle;

#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
#[derive(Clone, Debug)]
pub struct CachedGlyphInfo {
    pub format: GlyphFormat,
    pub texture_cache_handle: TextureCacheHandle,
}

#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub enum GlyphCacheEntry {
    // A glyph that has been successfully rasterized.
    Cached(CachedGlyphInfo),
    // A glyph that should not be rasterized (i.e. a space).
    Blank,
    // A glyph that has been submitted to the font backend for rasterization,
    // but is still pending a result.
    #[allow(dead_code)]
    Pending,
}

impl GlyphCacheEntry {
    fn get_allocated_size(&self, texture_cache: &TextureCache, _: &RenderTaskCache)
                          -> Option<usize> {
        match *self {
            GlyphCacheEntry::Cached(ref glyph) => {
                texture_cache.get_allocated_size(&glyph.texture_cache_handle)
            }
            GlyphCacheEntry::Pending => Some(0),
            // If the cache only has blank glyphs left, just get rid of it.
            GlyphCacheEntry::Blank => None,
        }
    }

    fn mark_unused(&self, texture_cache: &mut TextureCache) {
        if let GlyphCacheEntry::Cached(ref glyph) = *self {
            texture_cache.mark_unused(&glyph.texture_cache_handle);
        }
    }}

#[allow(dead_code)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
#[derive(Clone)]
pub enum CachedGlyphData {
    Memory(Arc<Vec<u8>>),
    Gpu,
}

#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
#[derive(Default)]
pub struct GlyphKeyCacheInfo {
    eviction_notice: EvictionNotice,
    last_frame_used: FrameId,
    bytes_used: usize,
}

pub type GlyphKeyCache = ResourceClassCache<GlyphKey, GlyphCacheEntry, GlyphKeyCacheInfo>;

impl GlyphKeyCache {
    const DIRTY: usize = !0;

    pub fn eviction_notice(&self) -> &EvictionNotice {
        &self.user_data.eviction_notice
    }

    fn clear_glyphs(&mut self, texture_cache: &mut TextureCache) {
        for (_, entry) in self.iter() {
            entry.mark_unused(texture_cache);
        }
        self.clear();
        self.user_data.bytes_used = 0;
    }

    pub fn add_glyph(&mut self, key: GlyphKey, value: GlyphCacheEntry) {
        self.insert(key, value);
        self.user_data.bytes_used = Self::DIRTY;
    }

    fn clear_evicted(
        &mut self,
        texture_cache: &TextureCache,
        render_task_cache: &RenderTaskCache,
    ) {
        if self.eviction_notice().check() || self.user_data.bytes_used == Self::DIRTY {
            // If there are evictions, filter out any glyphs evicted from the
            // texture cache from the glyph key cache.
            let mut usage = 0;
            self.retain(|_, entry| {
                let size = entry.get_allocated_size(texture_cache, render_task_cache);
                usage += size.unwrap_or(0);
                size.is_some()
            });
            self.user_data.bytes_used = usage;
        }
    }
}

#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct GlyphCache {
    glyph_key_caches: FastHashMap<FontInstance, GlyphKeyCache>,
    current_frame: FrameId,
    bytes_used: usize,
    max_bytes_used: usize,
}

impl GlyphCache {
    /// The default space usage threshold, in bytes, after which to start pruning away old fonts.
    pub const DEFAULT_MAX_BYTES_USED: usize = 5 * 1024 * 1024;

    pub fn new(max_bytes_used: usize) -> Self {
        GlyphCache {
            glyph_key_caches: FastHashMap::default(),
            current_frame: Default::default(),
            bytes_used: 0,
            max_bytes_used,
        }
    }

    pub fn get_glyph_key_cache_for_font_mut(&mut self, font: FontInstance) -> &mut GlyphKeyCache {
        let cache = self.glyph_key_caches
                        .entry(font)
                        .or_insert_with(GlyphKeyCache::new);
        cache.user_data.last_frame_used = self.current_frame;
        cache
    }

    pub fn get_glyph_key_cache_for_font(&self, font: &FontInstance) -> &GlyphKeyCache {
        self.glyph_key_caches
            .get(font)
            .expect("BUG: Unable to find glyph key cache!")
    }

    pub fn clear(&mut self) {
        for (_, glyph_key_cache) in &mut self.glyph_key_caches {
            glyph_key_cache.clear()
        }
        // We use this in on_memory_pressure where retaining memory allocations
        // isn't desirable, so we completely remove the hash map instead of clearing it.
        self.glyph_key_caches = FastHashMap::default();
    }

    pub fn clear_fonts<F>(&mut self, texture_cache: &mut TextureCache, key_fun: F)
    where
        for<'r> F: Fn(&'r &FontInstance) -> bool,
    {
        self.glyph_key_caches.retain(|k, cache| {
            let should_clear = key_fun(&k);
            if !should_clear {
                return true;
            }

            cache.clear_glyphs(texture_cache);
            false
        })
    }

    /// Clear out evicted entries from glyph key caches.
    fn clear_evicted(
        &mut self,
        texture_cache: &TextureCache,
        render_task_cache: &RenderTaskCache,
    ) {
        let mut usage = 0;
        for cache in self.glyph_key_caches.values_mut() {
            // Scan for any glyph key caches that have evictions.
            cache.clear_evicted(texture_cache, render_task_cache);
            usage += cache.user_data.bytes_used;
        }
        self.bytes_used = usage;
    }

    /// If possible, remove entirely any empty glyph key caches.
    fn clear_empty_caches(&mut self, glyph_rasterizer: &mut GlyphRasterizer) {
        self.glyph_key_caches.retain(|key, cache| {
            // Discard the glyph key cache if it has no valid glyphs.
            if cache.is_empty() {
                glyph_rasterizer.delete_font_instance(key);
                false
            } else {
                true
            }
        });
    }

    /// Check the total space usage of the glyph cache. If it exceeds the maximum usage threshold,
    /// then start clearing the oldest glyphs until below the threshold.
    fn prune_excess_usage(&mut self, texture_cache: &mut TextureCache) {
        if self.bytes_used < self.max_bytes_used {
            return;
        }
        // Usage is above the threshold. Get a last-recently-used ordered list of caches to clear.
        let mut caches: Vec<_> = self.glyph_key_caches.values_mut().collect();
        caches.sort_unstable_by(|a, b| {
            a.user_data.last_frame_used.cmp(&b.user_data.last_frame_used)
        });
        // Clear out the oldest caches until below the threshold.
        for cache in caches {
            self.bytes_used -= cache.user_data.bytes_used;
            cache.clear_glyphs(texture_cache);
            if self.bytes_used < self.max_bytes_used {
                break;
            }
        }
    }

    pub fn begin_frame(&mut self,
                       stamp: FrameStamp,
                       texture_cache: &mut TextureCache,
                       render_task_cache: &RenderTaskCache,
                       glyph_rasterizer: &mut GlyphRasterizer) {
        self.current_frame = stamp.frame_id();
        self.clear_evicted(texture_cache, render_task_cache);
        self.prune_excess_usage(texture_cache);
        // Clearing evicted glyphs and pruning excess usage might have produced empty caches,
        // so get rid of them if possible.
        self.clear_empty_caches(glyph_rasterizer);
    }
}
<commit_msg>Bug 1569950 - only partially clear WR glyph caches if it is not necessary to fully clear. r=kvark<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use crate::glyph_rasterizer::{FontInstance, GlyphFormat, GlyphKey, GlyphRasterizer};
use crate::internal_types::FastHashMap;
use crate::render_backend::{FrameId, FrameStamp};
use crate::render_task::RenderTaskCache;
use crate::resource_cache::ResourceClassCache;
use std::sync::Arc;
use crate::texture_cache::{EvictionNotice, TextureCache};
use crate::texture_cache::TextureCacheHandle;

#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
#[derive(Clone, Debug)]
pub struct CachedGlyphInfo {
    pub format: GlyphFormat,
    pub texture_cache_handle: TextureCacheHandle,
}

#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub enum GlyphCacheEntry {
    // A glyph that has been successfully rasterized.
    Cached(CachedGlyphInfo),
    // A glyph that should not be rasterized (i.e. a space).
    Blank,
    // A glyph that has been submitted to the font backend for rasterization,
    // but is still pending a result.
    #[allow(dead_code)]
    Pending,
}

impl GlyphCacheEntry {
    fn get_allocated_size(&self, texture_cache: &TextureCache, _: &RenderTaskCache)
                          -> Option<usize> {
        match *self {
            GlyphCacheEntry::Cached(ref glyph) => {
                texture_cache.get_allocated_size(&glyph.texture_cache_handle)
            }
            GlyphCacheEntry::Pending => Some(0),
            // If the cache only has blank glyphs left, just get rid of it.
            GlyphCacheEntry::Blank => None,
        }
    }

    fn mark_unused(&self, texture_cache: &mut TextureCache) {
        if let GlyphCacheEntry::Cached(ref glyph) = *self {
            texture_cache.mark_unused(&glyph.texture_cache_handle);
        }
    }}

#[allow(dead_code)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
#[derive(Clone)]
pub enum CachedGlyphData {
    Memory(Arc<Vec<u8>>),
    Gpu,
}

#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
#[derive(Default)]
pub struct GlyphKeyCacheInfo {
    eviction_notice: EvictionNotice,
    last_frame_used: FrameId,
    bytes_used: usize,
}

pub type GlyphKeyCache = ResourceClassCache<GlyphKey, GlyphCacheEntry, GlyphKeyCacheInfo>;

impl GlyphKeyCache {
    const DIRTY: usize = !0;

    pub fn eviction_notice(&self) -> &EvictionNotice {
        &self.user_data.eviction_notice
    }

    fn clear_glyphs(&mut self, texture_cache: &mut TextureCache) {
        for (_, entry) in self.iter() {
            entry.mark_unused(texture_cache);
        }
        self.clear();
        self.user_data.bytes_used = 0;
    }

    fn prune_glyphs(
        &mut self,
        excess_bytes_used: usize,
        texture_cache: &mut TextureCache,
        render_task_cache: &RenderTaskCache,
    ) -> usize {
        let mut pruned = 0;
        self.retain(|_, entry| {
            if pruned <= excess_bytes_used {
                match entry.get_allocated_size(texture_cache, render_task_cache) {
                    Some(size) => {
                        pruned += size;
                        entry.mark_unused(texture_cache);
                        false
                    }
                    None => true,
                }
            } else {
                true
            }
        });
        self.user_data.bytes_used -= pruned;
        pruned
    }

    pub fn add_glyph(&mut self, key: GlyphKey, value: GlyphCacheEntry) {
        self.insert(key, value);
        self.user_data.bytes_used = Self::DIRTY;
    }

    fn clear_evicted(
        &mut self,
        texture_cache: &TextureCache,
        render_task_cache: &RenderTaskCache,
    ) {
        if self.eviction_notice().check() || self.user_data.bytes_used == Self::DIRTY {
            // If there are evictions, filter out any glyphs evicted from the
            // texture cache from the glyph key cache.
            let mut usage = 0;
            self.retain(|_, entry| {
                let size = entry.get_allocated_size(texture_cache, render_task_cache);
                usage += size.unwrap_or(0);
                size.is_some()
            });
            self.user_data.bytes_used = usage;
        }
    }
}

#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct GlyphCache {
    glyph_key_caches: FastHashMap<FontInstance, GlyphKeyCache>,
    current_frame: FrameId,
    bytes_used: usize,
    max_bytes_used: usize,
}

impl GlyphCache {
    /// The default space usage threshold, in bytes, after which to start pruning away old fonts.
    pub const DEFAULT_MAX_BYTES_USED: usize = 6 * 1024 * 1024;

    pub fn new(max_bytes_used: usize) -> Self {
        GlyphCache {
            glyph_key_caches: FastHashMap::default(),
            current_frame: Default::default(),
            bytes_used: 0,
            max_bytes_used,
        }
    }

    pub fn get_glyph_key_cache_for_font_mut(&mut self, font: FontInstance) -> &mut GlyphKeyCache {
        let cache = self.glyph_key_caches
                        .entry(font)
                        .or_insert_with(GlyphKeyCache::new);
        cache.user_data.last_frame_used = self.current_frame;
        cache
    }

    pub fn get_glyph_key_cache_for_font(&self, font: &FontInstance) -> &GlyphKeyCache {
        self.glyph_key_caches
            .get(font)
            .expect("BUG: Unable to find glyph key cache!")
    }

    pub fn clear(&mut self) {
        for (_, glyph_key_cache) in &mut self.glyph_key_caches {
            glyph_key_cache.clear()
        }
        // We use this in on_memory_pressure where retaining memory allocations
        // isn't desirable, so we completely remove the hash map instead of clearing it.
        self.glyph_key_caches = FastHashMap::default();
    }

    pub fn clear_fonts<F>(&mut self, texture_cache: &mut TextureCache, key_fun: F)
    where
        for<'r> F: Fn(&'r &FontInstance) -> bool,
    {
        self.glyph_key_caches.retain(|k, cache| {
            let should_clear = key_fun(&k);
            if !should_clear {
                return true;
            }

            cache.clear_glyphs(texture_cache);
            false
        })
    }

    /// Clear out evicted entries from glyph key caches.
    fn clear_evicted(
        &mut self,
        texture_cache: &TextureCache,
        render_task_cache: &RenderTaskCache,
    ) {
        let mut usage = 0;
        for cache in self.glyph_key_caches.values_mut() {
            // Scan for any glyph key caches that have evictions.
            cache.clear_evicted(texture_cache, render_task_cache);
            usage += cache.user_data.bytes_used;
        }
        self.bytes_used = usage;
    }

    /// If possible, remove entirely any empty glyph key caches.
    fn clear_empty_caches(&mut self, glyph_rasterizer: &mut GlyphRasterizer) {
        self.glyph_key_caches.retain(|key, cache| {
            // Discard the glyph key cache if it has no valid glyphs.
            if cache.is_empty() {
                glyph_rasterizer.delete_font_instance(key);
                false
            } else {
                true
            }
        });
    }

    /// Check the total space usage of the glyph cache. If it exceeds the maximum usage threshold,
    /// then start clearing the oldest glyphs until below the threshold.
    fn prune_excess_usage(
        &mut self,
        texture_cache: &mut TextureCache,
        render_task_cache: &RenderTaskCache,
    ) {
        if self.bytes_used < self.max_bytes_used {
            return;
        }
        // Usage is above the threshold. Get a last-recently-used ordered list of caches to clear.
        let mut caches: Vec<_> = self.glyph_key_caches.values_mut().collect();
        caches.sort_unstable_by(|a, b| {
            a.user_data.last_frame_used.cmp(&b.user_data.last_frame_used)
        });
        // Clear out the oldest caches until below the threshold.
        for cache in caches {
            if self.bytes_used < self.max_bytes_used {
                break;
            }
            let excess = self.bytes_used - self.max_bytes_used;
            if excess >= cache.user_data.bytes_used {
                // If the excess is greater than the cache's size, just clear the whole thing.
                cache.clear_glyphs(texture_cache);
                self.bytes_used -= cache.user_data.bytes_used;
            } else {
                // Otherwise, just clear as little of the cache as needs to remove the excess
                // and avoid rematerialization costs.
                self.bytes_used -= cache.prune_glyphs(excess, texture_cache, render_task_cache);
            }
        }
    }

    pub fn begin_frame(
        &mut self,
        stamp: FrameStamp,
        texture_cache: &mut TextureCache,
        render_task_cache: &RenderTaskCache,
        glyph_rasterizer: &mut GlyphRasterizer,
    ) {
        self.current_frame = stamp.frame_id();
        self.clear_evicted(texture_cache, render_task_cache);
        self.prune_excess_usage(texture_cache, render_task_cache);
        // Clearing evicted glyphs and pruning excess usage might have produced empty caches,
        // so get rid of them if possible.
        self.clear_empty_caches(glyph_rasterizer);
    }
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use super::FileGroup;
use super::WatchmanFile;
use crate::compiler_state::{ProjectSet, SourceSet};
use crate::config::{Config, SchemaLocation};
use std::cmp::Reverse;
use std::collections::hash_map::Entry;
use std::collections::{HashMap, HashSet};
use std::ffi::OsStr;
use std::path::{Component, PathBuf};

/// The watchman query returns a list of files, but for the compiler we
/// need to categorize these files into multiple groups of files like
/// schema files, extensions and sources by their source set name.
///
/// See `FileGroup` for all groups of files.
pub fn categorize_files(
    config: &Config,
    files: &[WatchmanFile],
) -> HashMap<FileGroup, Vec<WatchmanFile>> {
    let categorizer = FileCategorizer::from_config(config);
    let mut categorized = HashMap::new();
    for file in files {
        categorized
            .entry(categorizer.categorize(&file.name))
            .or_insert_with(Vec::new)
            .push(file.clone());
    }
    categorized
}

/// The FileCategorizer is created from a Config and categorizes files found by
/// Watchman into what kind of files they are, such as source files of a
/// specific source file group or generated files from some project.
pub struct FileCategorizer {
    extensions_mapping: PathMapping<ProjectSet>,
    default_generated_dir: &'static OsStr,
    generated_dir_paths: HashSet<PathBuf>,
    source_mapping: PathMapping<SourceSet>,
    schema_file_mapping: HashMap<PathBuf, ProjectSet>,
    schema_dir_mapping: PathMapping<ProjectSet>,
}

impl FileCategorizer {
    pub fn from_config(config: &Config) -> Self {
        let mut source_mapping = vec![];
        for (path, source_set) in &config.sources {
            source_mapping.push((path.clone(), source_set.clone()));
        }

        source_mapping.sort_by_key(|item| Reverse(item.0.clone()));

        let mut extensions_map: HashMap<PathBuf, ProjectSet> = Default::default();
        for (&project_name, project_config) in &config.projects {
            for extension_dir in &project_config.extensions {
                match extensions_map.entry(extension_dir.clone()) {
                    Entry::Vacant(entry) => {
                        entry.insert(ProjectSet::ProjectName(project_name));
                    }
                    Entry::Occupied(mut entry) => {
                        entry.get_mut().insert(project_name);
                    }
                }
            }
        }
        let mut extensions_mapping = vec![];
        for (path, project_set) in extensions_map {
            extensions_mapping.push((path, project_set));
        }

        let mut schema_file_mapping: HashMap<PathBuf, ProjectSet> = Default::default();
        for (&project_name, project_config) in &config.projects {
            if let SchemaLocation::File(schema_file) = &project_config.schema_location {
                match schema_file_mapping.entry(schema_file.clone()) {
                    Entry::Vacant(entry) => {
                        entry.insert(ProjectSet::ProjectName(project_name));
                    }
                    Entry::Occupied(mut entry) => {
                        entry.get_mut().insert(project_name);
                    }
                }
            }
        }

        let mut schema_dir_mapping_map: HashMap<PathBuf, ProjectSet> = Default::default();
        for (&project_name, project_config) in &config.projects {
            if let SchemaLocation::Directory(directory) = &project_config.schema_location {
                match schema_dir_mapping_map.entry(directory.clone()) {
                    Entry::Vacant(entry) => {
                        entry.insert(ProjectSet::ProjectName(project_name));
                    }
                    Entry::Occupied(mut entry) => {
                        entry.get_mut().insert(project_name);
                    }
                }
            }
        }

        let mut schema_dir_mapping = vec![];
        for (path, project_set) in schema_dir_mapping_map {
            schema_dir_mapping.push((path, project_set));
        }

        let default_generated_dir = OsStr::new("__generated__");
        let generated_dir_paths: HashSet<PathBuf> = config
            .projects
            .iter()
            .filter_map(|(_, project_config)| project_config.output.clone())
            .collect();

        Self {
            extensions_mapping: PathMapping(extensions_mapping),
            default_generated_dir,
            generated_dir_paths,
            schema_file_mapping,
            schema_dir_mapping: PathMapping(schema_dir_mapping),
            source_mapping: PathMapping(source_mapping),
        }
    }

    pub fn categorize(&self, path: &PathBuf) -> FileGroup {
        let extension = path
            .extension()
            .unwrap_or_else(|| panic!("Got unexpected path without extension: `{:?}`.", path));
        if extension == "js" {
            if self.in_generated_dir(path) {
                FileGroup::Generated
            } else {
                FileGroup::Source {
                    source_set: self.source_mapping.get(path),
                }
            }
        } else if extension == "graphql" {
            if let Some(project_set) = self.schema_file_mapping.get(path) {
                FileGroup::Schema {
                    project_set: project_set.clone(),
                }
            } else if let Some(project_set) = self.extensions_mapping.find(path) {
                FileGroup::Extension { project_set }
            } else if let Some(project_set) = self.schema_dir_mapping.find(path) {
                FileGroup::Schema { project_set }
            } else {
                panic!(
                    "Expected *.graphql file `{:?}` to be either a schema or extension.",
                    path
                )
            }
        } else {
            panic!(
                "Received file {:?} from watchman with unexpected extension.",
                path
            )
        }
    }

    fn in_generated_dir(&self, path: &PathBuf) -> bool {
        self.in_absolute_generated_dir(path) || self.in_relative_generated_dir(path)
    }

    fn in_absolute_generated_dir(&self, path: &PathBuf) -> bool {
        self.generated_dir_paths
            .iter()
            .any(|generated_dir_path| path.starts_with(generated_dir_path))
    }

    fn in_relative_generated_dir(&self, path: &PathBuf) -> bool {
        path.components().any(|comp| match comp {
            Component::Normal(comp) => comp == self.default_generated_dir,
            _ => false,
        })
    }
}

struct PathMapping<T: Clone>(Vec<(PathBuf, T)>);
impl<T: Clone> PathMapping<T> {
    fn get(&self, path: &PathBuf) -> T {
        self.find(path).unwrap_or_else(|| {
            panic!("Path `{:?}` not in of the the expected directories.", path);
        })
    }
    fn find(&self, path: &PathBuf) -> Option<T> {
        self.0.iter().find_map(|(prefix, item)| {
            if path.starts_with(prefix) {
                Some(item.clone())
            } else {
                None
            }
        })
    }
}

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

    #[test]
    fn test_categorize() {
        let config = Config::from_string_for_test(
            r#"
                {
                    "sources": {
                        "src/js": "public",
                        "src/js/internal": "internal",
                        "src/vendor": "public",
                        "src/custom": "with_custom_generated_dir"
                    },
                    "projects": {
                        "public": {
                            "schema": "graphql/public.graphql"
                        },
                        "internal": {
                            "schema": "graphql/__generated__/internal.graphql"
                        },
                        "with_custom_generated_dir": {
                            "schema": "graphql/__generated__/custom.graphql",
                            "output": "graphql/custom-generated"
                        }
                    }
                }
            "#,
        )
        .unwrap();
        let categorizer = FileCategorizer::from_config(&config);

        assert_eq!(
            categorizer.categorize(&"src/js/a.js".into()),
            FileGroup::Source {
                source_set: SourceSet::SourceSetName("public".intern()),
            },
        );
        assert_eq!(
            categorizer.categorize(&"src/js/nested/b.js".into()),
            FileGroup::Source {
                source_set: SourceSet::SourceSetName("public".intern()),
            },
        );
        assert_eq!(
            categorizer.categorize(&"src/js/internal/nested/c.js".into()),
            FileGroup::Source {
                source_set: SourceSet::SourceSetName("internal".intern()),
            },
        );
        assert_eq!(
            // When custom output dir is provided, path is correctly categorized
            // even if it has same dirname in path as custom output folder.
            // Path is only categorized as generated if it matches the absolute path
            // of the provided custom output.
            categorizer.categorize(&"src/custom/custom-generated/c.js".into()),
            FileGroup::Source {
                source_set: SourceSet::SourceSetName("with_custom_generated_dir".intern()),
            },
        );
        assert_eq!(
            categorizer.categorize(&"src/js/internal/nested/__generated__/c.js".into()),
            FileGroup::Generated,
        );
        assert_eq!(
            categorizer.categorize(&"graphql/custom-generated/c.js".into()),
            FileGroup::Generated,
        );
        assert_eq!(
            categorizer.categorize(&"graphql/public.graphql".into()),
            FileGroup::Schema {
                project_set: ProjectSet::ProjectName("public".intern())
            },
        );
        assert_eq!(
            categorizer.categorize(&"graphql/__generated__/internal.graphql".into()),
            FileGroup::Schema {
                project_set: ProjectSet::ProjectName("internal".intern())
            },
        );
    }
}
<commit_msg>sort without clone()ing<commit_after>/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use super::FileGroup;
use super::WatchmanFile;
use crate::compiler_state::{ProjectSet, SourceSet};
use crate::config::{Config, SchemaLocation};
use std::collections::hash_map::Entry;
use std::collections::{HashMap, HashSet};
use std::ffi::OsStr;
use std::path::{Component, PathBuf};

/// The watchman query returns a list of files, but for the compiler we
/// need to categorize these files into multiple groups of files like
/// schema files, extensions and sources by their source set name.
///
/// See `FileGroup` for all groups of files.
pub fn categorize_files(
    config: &Config,
    files: &[WatchmanFile],
) -> HashMap<FileGroup, Vec<WatchmanFile>> {
    let categorizer = FileCategorizer::from_config(config);
    let mut categorized = HashMap::new();
    for file in files {
        categorized
            .entry(categorizer.categorize(&file.name))
            .or_insert_with(Vec::new)
            .push(file.clone());
    }
    categorized
}

/// The FileCategorizer is created from a Config and categorizes files found by
/// Watchman into what kind of files they are, such as source files of a
/// specific source file group or generated files from some project.
pub struct FileCategorizer {
    extensions_mapping: PathMapping<ProjectSet>,
    default_generated_dir: &'static OsStr,
    generated_dir_paths: HashSet<PathBuf>,
    source_mapping: PathMapping<SourceSet>,
    schema_file_mapping: HashMap<PathBuf, ProjectSet>,
    schema_dir_mapping: PathMapping<ProjectSet>,
}

impl FileCategorizer {
    pub fn from_config(config: &Config) -> Self {
        let mut source_mapping = vec![];
        for (path, source_set) in &config.sources {
            source_mapping.push((path.clone(), source_set.clone()));
        }
        // Sort so that more specific paths come first, i.e.
        // - foo/bar -> A
        // - foo -> B
        // which ensures we categorize foo/bar/x.js as A.
        source_mapping.sort_by(|(path_a, _), (path_b, _)| path_b.cmp(path_a));

        let mut extensions_map: HashMap<PathBuf, ProjectSet> = Default::default();
        for (&project_name, project_config) in &config.projects {
            for extension_dir in &project_config.extensions {
                match extensions_map.entry(extension_dir.clone()) {
                    Entry::Vacant(entry) => {
                        entry.insert(ProjectSet::ProjectName(project_name));
                    }
                    Entry::Occupied(mut entry) => {
                        entry.get_mut().insert(project_name);
                    }
                }
            }
        }
        let mut extensions_mapping = vec![];
        for (path, project_set) in extensions_map {
            extensions_mapping.push((path, project_set));
        }

        let mut schema_file_mapping: HashMap<PathBuf, ProjectSet> = Default::default();
        for (&project_name, project_config) in &config.projects {
            if let SchemaLocation::File(schema_file) = &project_config.schema_location {
                match schema_file_mapping.entry(schema_file.clone()) {
                    Entry::Vacant(entry) => {
                        entry.insert(ProjectSet::ProjectName(project_name));
                    }
                    Entry::Occupied(mut entry) => {
                        entry.get_mut().insert(project_name);
                    }
                }
            }
        }

        let mut schema_dir_mapping_map: HashMap<PathBuf, ProjectSet> = Default::default();
        for (&project_name, project_config) in &config.projects {
            if let SchemaLocation::Directory(directory) = &project_config.schema_location {
                match schema_dir_mapping_map.entry(directory.clone()) {
                    Entry::Vacant(entry) => {
                        entry.insert(ProjectSet::ProjectName(project_name));
                    }
                    Entry::Occupied(mut entry) => {
                        entry.get_mut().insert(project_name);
                    }
                }
            }
        }

        let mut schema_dir_mapping = vec![];
        for (path, project_set) in schema_dir_mapping_map {
            schema_dir_mapping.push((path, project_set));
        }

        let default_generated_dir = OsStr::new("__generated__");
        let generated_dir_paths: HashSet<PathBuf> = config
            .projects
            .iter()
            .filter_map(|(_, project_config)| project_config.output.clone())
            .collect();

        Self {
            extensions_mapping: PathMapping(extensions_mapping),
            default_generated_dir,
            generated_dir_paths,
            schema_file_mapping,
            schema_dir_mapping: PathMapping(schema_dir_mapping),
            source_mapping: PathMapping(source_mapping),
        }
    }

    pub fn categorize(&self, path: &PathBuf) -> FileGroup {
        let extension = path
            .extension()
            .unwrap_or_else(|| panic!("Got unexpected path without extension: `{:?}`.", path));
        if extension == "js" {
            if self.in_generated_dir(path) {
                FileGroup::Generated
            } else {
                FileGroup::Source {
                    source_set: self.source_mapping.get(path),
                }
            }
        } else if extension == "graphql" {
            if let Some(project_set) = self.schema_file_mapping.get(path) {
                FileGroup::Schema {
                    project_set: project_set.clone(),
                }
            } else if let Some(project_set) = self.extensions_mapping.find(path) {
                FileGroup::Extension { project_set }
            } else if let Some(project_set) = self.schema_dir_mapping.find(path) {
                FileGroup::Schema { project_set }
            } else {
                panic!(
                    "Expected *.graphql file `{:?}` to be either a schema or extension.",
                    path
                )
            }
        } else {
            panic!(
                "Received file {:?} from watchman with unexpected extension.",
                path
            )
        }
    }

    fn in_generated_dir(&self, path: &PathBuf) -> bool {
        self.in_absolute_generated_dir(path) || self.in_relative_generated_dir(path)
    }

    fn in_absolute_generated_dir(&self, path: &PathBuf) -> bool {
        self.generated_dir_paths
            .iter()
            .any(|generated_dir_path| path.starts_with(generated_dir_path))
    }

    fn in_relative_generated_dir(&self, path: &PathBuf) -> bool {
        path.components().any(|comp| match comp {
            Component::Normal(comp) => comp == self.default_generated_dir,
            _ => false,
        })
    }
}

struct PathMapping<T: Clone>(Vec<(PathBuf, T)>);
impl<T: Clone> PathMapping<T> {
    fn get(&self, path: &PathBuf) -> T {
        self.find(path).unwrap_or_else(|| {
            panic!("Path `{:?}` not in of the the expected directories.", path);
        })
    }
    fn find(&self, path: &PathBuf) -> Option<T> {
        self.0.iter().find_map(|(prefix, item)| {
            if path.starts_with(prefix) {
                Some(item.clone())
            } else {
                None
            }
        })
    }
}

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

    #[test]
    fn test_categorize() {
        let config = Config::from_string_for_test(
            r#"
                {
                    "sources": {
                        "src/js": "public",
                        "src/js/internal": "internal",
                        "src/vendor": "public",
                        "src/custom": "with_custom_generated_dir"
                    },
                    "projects": {
                        "public": {
                            "schema": "graphql/public.graphql"
                        },
                        "internal": {
                            "schema": "graphql/__generated__/internal.graphql"
                        },
                        "with_custom_generated_dir": {
                            "schema": "graphql/__generated__/custom.graphql",
                            "output": "graphql/custom-generated"
                        }
                    }
                }
            "#,
        )
        .unwrap();
        let categorizer = FileCategorizer::from_config(&config);

        assert_eq!(
            categorizer.categorize(&"src/js/a.js".into()),
            FileGroup::Source {
                source_set: SourceSet::SourceSetName("public".intern()),
            },
        );
        assert_eq!(
            categorizer.categorize(&"src/js/nested/b.js".into()),
            FileGroup::Source {
                source_set: SourceSet::SourceSetName("public".intern()),
            },
        );
        assert_eq!(
            categorizer.categorize(&"src/js/internal/nested/c.js".into()),
            FileGroup::Source {
                source_set: SourceSet::SourceSetName("internal".intern()),
            },
        );
        assert_eq!(
            // When custom output dir is provided, path is correctly categorized
            // even if it has same dirname in path as custom output folder.
            // Path is only categorized as generated if it matches the absolute path
            // of the provided custom output.
            categorizer.categorize(&"src/custom/custom-generated/c.js".into()),
            FileGroup::Source {
                source_set: SourceSet::SourceSetName("with_custom_generated_dir".intern()),
            },
        );
        assert_eq!(
            categorizer.categorize(&"src/js/internal/nested/__generated__/c.js".into()),
            FileGroup::Generated,
        );
        assert_eq!(
            categorizer.categorize(&"graphql/custom-generated/c.js".into()),
            FileGroup::Generated,
        );
        assert_eq!(
            categorizer.categorize(&"graphql/public.graphql".into()),
            FileGroup::Schema {
                project_set: ProjectSet::ProjectName("public".intern())
            },
        );
        assert_eq!(
            categorizer.categorize(&"graphql/__generated__/internal.graphql".into()),
            FileGroup::Schema {
                project_set: ProjectSet::ProjectName("internal".intern())
            },
        );
    }
}
<|endoftext|>