<commit_before>use std::{
    cmp::{max, Reverse},
    fmt,
    io::{self, Read, Write},
    time::{Duration, SystemTime},
};

use byteorder::{ReadBytesExt as _, WriteBytesExt as _};
use rustc_hash::FxHashMap;
use shakmaty::{uci::Uci, Outcome};
use smallvec::{smallvec, SmallVec};

use crate::{
    api::PersonalQueryFilter,
    model::{
        read_uci, read_uint, write_uci, write_uint, ByMode, BySpeed, GameId, LichessGroup, Mode,
        PreparedMove, PreparedResponse, Speed, Stats,
    },
};

const MAX_PERSONAL_GAMES: u64 = 15; // 4 bits

#[derive(Debug, Eq, PartialEq)]
enum Header {
    Group {
        mode: Mode,
        speed: Speed,
        num_games: usize,
    },
    End,
}

impl Header {
    fn read<R: Read>(reader: &mut R) -> io::Result<Header> {
        let n = reader.read_u8()?;
        Ok(Header::Group {
            speed: match n & 7 {
                0 => return Ok(Header::End),
                1 => Speed::UltraBullet,
                2 => Speed::Bullet,
                3 => Speed::Blitz,
                4 => Speed::Rapid,
                5 => Speed::Classical,
                6 => Speed::Correspondence,
                _ => return Err(io::ErrorKind::InvalidData.into()),
            },
            mode: Mode::from_rated((n >> 3) & 1 == 1),
            num_games: usize::from(n >> 4),
        })
    }

    fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
        writer.write_u8(match *self {
            Header::End => 0,
            Header::Group {
                mode,
                speed,
                num_games,
            } => {
                (match speed {
                    Speed::UltraBullet => 1,
                    Speed::Bullet => 2,
                    Speed::Blitz => 3,
                    Speed::Rapid => 4,
                    Speed::Classical => 5,
                    Speed::Correspondence => 6,
                }) | ((mode.is_rated() as u8) << 3)
                    | ((num_games as u8) << 4)
            }
        })
    }
}

#[derive(Default, Debug)]
pub struct PersonalEntry {
    sub_entries: FxHashMap<Uci, BySpeed<ByMode<LichessGroup>>>,
    max_game_idx: u64,
}

impl PersonalEntry {
    pub const SIZE_HINT: usize = 14;

    pub fn new_single(
        uci: Uci,
        speed: Speed,
        mode: Mode,
        game_id: GameId,
        outcome: Outcome,
        opponent_rating: u16,
    ) -> PersonalEntry {
        let mut sub_entry: BySpeed<ByMode<LichessGroup>> = Default::default();
        *sub_entry.by_speed_mut(speed).by_mode_mut(mode) = LichessGroup {
            stats: Stats::new_single(outcome, opponent_rating),
            games: smallvec![(0, game_id)],
        };
        let mut sub_entries = FxHashMap::with_capacity_and_hasher(1, Default::default());
        sub_entries.insert(uci, sub_entry);

        PersonalEntry {
            sub_entries,
            max_game_idx: 0,
        }
    }

    pub fn extend_from_reader<R: Read>(&mut self, reader: &mut R) -> io::Result<()> {
        loop {
            let uci = match read_uci(reader) {
                Ok(uci) => uci,
                Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => return Ok(()),
                Err(err) => return Err(err),
            };

            let sub_entry = self.sub_entries.entry(uci).or_default();

            let base_game_idx = self.max_game_idx + 1;

            while let Header::Group {
                speed,
                mode,
                num_games,
            } = Header::read(reader)?
            {
                let stats = Stats::read(reader)?;
                let mut games = SmallVec::with_capacity(num_games);
                for _ in 0..num_games {
                    let game_idx = base_game_idx + read_uint(reader)?;
                    self.max_game_idx = max(self.max_game_idx, game_idx);
                    let game = GameId::read(reader)?;
                    games.push((game_idx, game));
                }
                let group = sub_entry.by_speed_mut(speed).by_mode_mut(mode);
                *group += LichessGroup { stats, games };
            }
        }
    }

    pub fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
        let discarded_game_idx = self.max_game_idx.saturating_sub(MAX_PERSONAL_GAMES);

        for (uci, sub_entry) in &self.sub_entries {
            write_uci(writer, uci)?;

            sub_entry.as_ref().try_map(|speed, by_mode| {
                by_mode.as_ref().try_map(|mode, group| {
                    let num_games = if group.games.len() == 1 {
                        1
                    } else {
                        group
                            .games
                            .iter()
                            .filter(|(game_idx, _)| *game_idx > discarded_game_idx)
                            .count()
                    };

                    if num_games > 0 || !group.stats.is_empty() {
                        Header::Group {
                            speed,
                            mode,
                            num_games,
                        }
                        .write(writer)?;

                        group.stats.write(writer)?;

                        for (game_idx, game) in &group.games {
                            if *game_idx > discarded_game_idx || group.games.len() == 1 {
                                write_uint(writer, *game_idx)?;
                                game.write(writer)?;
                            }
                        }
                    }

                    Ok::<_, io::Error>(())
                })
            })?;

            Header::End.write(writer)?;
        }

        Ok(())
    }

    pub fn prepare(self, filter: &PersonalQueryFilter) -> PreparedResponse {
        let mut total = Stats::default();
        let mut moves = Vec::with_capacity(self.sub_entries.len());
        let mut recent_games: Vec<(u64, Uci, GameId)> = Vec::new();

        for (uci, sub_entry) in self.sub_entries {
            let mut latest_game: Option<(u64, GameId)> = None;
            let mut stats = Stats::default();

            for speed in Speed::ALL {
                if filter
                    .speeds
                    .as_ref()
                    .map_or(true, |speeds| speeds.contains(&speed))
                {
                    for mode in Mode::ALL {
                        if filter
                            .modes
                            .as_ref()
                            .map_or(true, |modes| modes.contains(&mode))
                        {
                            let group = sub_entry.by_speed(speed).by_mode(mode);
                            stats += group.stats.to_owned();

                            for (idx, game) in group.games.iter().copied() {
                                if latest_game.map_or(true, |(latest_idx, _game)| latest_idx < idx)
                                {
                                    latest_game = Some((idx, game));
                                }
                            }

                            recent_games.extend(
                                group
                                    .games
                                    .iter()
                                    .copied()
                                    .map(|(idx, game)| (idx, uci.to_owned(), game)),
                            );
                        }
                    }
                }
            }

            if !stats.is_empty() || latest_game.is_some() {
                moves.push(PreparedMove {
                    uci,
                    stats: stats.clone(),
                    average_rating: None,
                    average_opponent_rating: stats.average_rating(),
                    game: latest_game.filter(|_| stats.is_single()).map(|(_, id)| id),
                });

                total += stats;
            }
        }

        moves.sort_by_key(|row| Reverse(row.stats.total()));
        recent_games.sort_by_key(|(idx, _, _)| Reverse(*idx));

        PreparedResponse {
            total,
            moves,
            recent_games: recent_games
                .into_iter()
                .map(|(_, uci, game)| (uci, game))
                .take(MAX_PERSONAL_GAMES as usize)
                .collect(),
            top_games: Vec::new(),
        }
    }
}

#[derive(Debug)]
pub struct PersonalStatus {
    pub latest_created_at: u64,
    pub revisit_ongoing_created_at: Option<u64>,
    pub indexed_at: SystemTime,
    pub revisited_at: SystemTime,
}

impl Default for PersonalStatus {
    fn default() -> PersonalStatus {
        PersonalStatus {
            latest_created_at: 0,
            revisit_ongoing_created_at: None,
            indexed_at: SystemTime::UNIX_EPOCH,
            revisited_at: SystemTime::UNIX_EPOCH,
        }
    }
}

impl PersonalStatus {
    pub const SIZE_HINT: usize = 3 * 8;

    pub fn maybe_revisit_ongoing(&mut self) -> Option<IndexRun> {
        if SystemTime::now()
            .duration_since(self.revisited_at)
            .unwrap_or_default()
            > Duration::from_secs(24 * 60 * 60)
        {
            self.revisit_ongoing_created_at
                .map(|since| IndexRun::Revisit { since })
        } else {
            None
        }
    }

    pub fn maybe_index(&self) -> Option<IndexRun> {
        SystemTime::now()
            .duration_since(self.indexed_at)
            .map_or(false, |cooldown| cooldown > Duration::from_secs(60))
            .then(|| IndexRun::Index {
                after: self.latest_created_at,
            })
    }

    pub fn finish_run(&mut self, run: IndexRun) {
        self.indexed_at = SystemTime::now();
        if matches!(run, IndexRun::Revisit { .. }) {
            self.revisited_at = self.indexed_at;
        }
    }

    pub fn read<R: Read>(reader: &mut R) -> io::Result<PersonalStatus> {
        Ok(PersonalStatus {
            latest_created_at: read_uint(reader)?,
            revisit_ongoing_created_at: Some(read_uint(reader)?).filter(|t| *t != 0),
            indexed_at: SystemTime::UNIX_EPOCH + Duration::from_secs(read_uint(reader)?),
            revisited_at: match read_uint(reader) {
                Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => SystemTime::UNIX_EPOCH, // bc
                Err(err) => return Err(err),
                Ok(secs) => SystemTime::UNIX_EPOCH + Duration::from_secs(secs),
            },
        })
    }

    pub fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
        write_uint(writer, self.latest_created_at)?;
        write_uint(writer, self.revisit_ongoing_created_at.unwrap_or(0))?;
        write_uint(
            writer,
            self.indexed_at
                .duration_since(SystemTime::UNIX_EPOCH)
                .expect("duration since unix epoch")
                .as_secs(),
        )?;
        write_uint(
            writer,
            self.revisited_at
                .duration_since(SystemTime::UNIX_EPOCH)
                .expect("duration since unix epoch")
                .as_secs(),
        )
    }
}

#[derive(Debug)]
pub enum IndexRun {
    Index { after: u64 },
    Revisit { since: u64 },
}

impl IndexRun {
    pub fn since(&self) -> u64 {
        match *self {
            IndexRun::Index { after } => {
                // Plus 1 millisecond, as an optimization to avoid overlap.
                // Might miss games if the index run happens between games
                // created in the same millisecond.
                after.saturating_add(1)
            }
            IndexRun::Revisit { since } => since,
        }
    }
}

impl fmt::Display for IndexRun {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            IndexRun::Index { after } => write!(f, "created_at > {}", after),
            IndexRun::Revisit { since } => write!(f, "created_at >= {}", since),
        }
    }
}

#[cfg(test)]
mod tests {
    use std::io::Cursor;

    use shakmaty::{Color, Square};

    use super::*;

    #[test]
    fn test_header_roundtrip() {
        let headers = [
            Header::Group {
                mode: Mode::Rated,
                speed: Speed::Correspondence,
                num_games: 15,
            },
            Header::End,
        ];

        let mut writer = Cursor::new(Vec::new());
        for header in &headers {
            header.write(&mut writer).unwrap();
        }

        let mut reader = Cursor::new(writer.into_inner());
        for header in headers {
            assert_eq!(Header::read(&mut reader).unwrap(), header);
        }
    }

    #[test]
    fn test_merge_personal() {
        let uci = Uci::Normal {
            from: Square::E2,
            to: Square::E4,
            promotion: None,
        };

        let a = PersonalEntry::new_single(
            uci.clone(),
            Speed::Bullet,
            Mode::Rated,
            "12345678".parse().unwrap(),
            Outcome::Decisive {
                winner: Color::White,
            },
            1600,
        );

        let b = PersonalEntry::new_single(
            uci.clone(),
            Speed::Bullet,
            Mode::Rated,
            "87654321".parse().unwrap(),
            Outcome::Decisive {
                winner: Color::Black,
            },
            1800,
        );

        let mut cursor = Cursor::new(Vec::new());
        a.write(&mut cursor).unwrap();
        assert_eq!(
            cursor.position() as usize,
            PersonalEntry::SIZE_HINT,
            "optimized for single entries"
        );

        let mut deserialized = PersonalEntry::default();
        deserialized
            .extend_from_reader(&mut Cursor::new(cursor.into_inner()))
            .unwrap();

        let mut cursor = Cursor::new(Vec::new());
        b.write(&mut cursor).unwrap();
        deserialized
            .extend_from_reader(&mut Cursor::new(cursor.into_inner()))
            .unwrap();

        assert_eq!(deserialized.sub_entries.len(), 1);
        let group = deserialized
            .sub_entries
            .get(&uci)
            .unwrap()
            .by_speed(Speed::Bullet)
            .by_mode(Mode::Rated);
        assert_eq!(group.stats.white, 1);
        assert_eq!(group.stats.draws, 0);
        assert_eq!(group.stats.black, 1);
        assert_eq!(group.stats.average_rating(), Some(1700));
        assert_eq!(group.games.len(), 2);
    }
}
<commit_msg>apply same tweaks to personal game retention<commit_after>use std::{
    cmp::{max, min, Reverse},
    fmt,
    io::{self, Read, Write},
    time::{Duration, SystemTime},
};

use byteorder::{ReadBytesExt as _, WriteBytesExt as _};
use rustc_hash::FxHashMap;
use shakmaty::{uci::Uci, Outcome};
use smallvec::{smallvec, SmallVec};

use crate::{
    api::PersonalQueryFilter,
    model::{
        read_uci, read_uint, write_uci, write_uint, ByMode, BySpeed, GameId, LichessGroup, Mode,
        PreparedMove, PreparedResponse, Speed, Stats,
    },
};

const MAX_PERSONAL_GAMES: usize = 8; // must fit into 4 bits

#[derive(Debug, Eq, PartialEq)]
enum Header {
    Group {
        mode: Mode,
        speed: Speed,
        num_games: usize,
    },
    End,
}

impl Header {
    fn read<R: Read>(reader: &mut R) -> io::Result<Header> {
        let n = reader.read_u8()?;
        Ok(Header::Group {
            speed: match n & 7 {
                0 => return Ok(Header::End),
                1 => Speed::UltraBullet,
                2 => Speed::Bullet,
                3 => Speed::Blitz,
                4 => Speed::Rapid,
                5 => Speed::Classical,
                6 => Speed::Correspondence,
                _ => return Err(io::ErrorKind::InvalidData.into()),
            },
            mode: Mode::from_rated((n >> 3) & 1 == 1),
            num_games: usize::from(n >> 4),
        })
    }

    fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
        writer.write_u8(match *self {
            Header::End => 0,
            Header::Group {
                mode,
                speed,
                num_games,
            } => {
                (match speed {
                    Speed::UltraBullet => 1,
                    Speed::Bullet => 2,
                    Speed::Blitz => 3,
                    Speed::Rapid => 4,
                    Speed::Classical => 5,
                    Speed::Correspondence => 6,
                }) | ((mode.is_rated() as u8) << 3)
                    | ((num_games as u8) << 4)
            }
        })
    }
}

#[derive(Default, Debug)]
pub struct PersonalEntry {
    sub_entries: FxHashMap<Uci, BySpeed<ByMode<LichessGroup>>>,
    max_game_idx: Option<u64>,
}

impl PersonalEntry {
    pub const SIZE_HINT: usize = 14;

    pub fn new_single(
        uci: Uci,
        speed: Speed,
        mode: Mode,
        game_id: GameId,
        outcome: Outcome,
        opponent_rating: u16,
    ) -> PersonalEntry {
        let mut sub_entry: BySpeed<ByMode<LichessGroup>> = Default::default();
        *sub_entry.by_speed_mut(speed).by_mode_mut(mode) = LichessGroup {
            stats: Stats::new_single(outcome, opponent_rating),
            games: smallvec![(0, game_id)],
        };
        let mut sub_entries = FxHashMap::with_capacity_and_hasher(1, Default::default());
        sub_entries.insert(uci, sub_entry);

        PersonalEntry {
            sub_entries,
            max_game_idx: Some(0),
        }
    }

    pub fn extend_from_reader<R: Read>(&mut self, reader: &mut R) -> io::Result<()> {
        loop {
            let uci = match read_uci(reader) {
                Ok(uci) => uci,
                Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => return Ok(()),
                Err(err) => return Err(err),
            };

            let sub_entry = self.sub_entries.entry(uci).or_default();

            let base_game_idx = self.max_game_idx.map_or(0, |idx| idx + 1);

            while let Header::Group {
                speed,
                mode,
                num_games,
            } = Header::read(reader)?
            {
                let stats = Stats::read(reader)?;
                let mut games = SmallVec::with_capacity(num_games);
                for _ in 0..num_games {
                    let game_idx = base_game_idx + read_uint(reader)?;
                    self.max_game_idx = Some(max(self.max_game_idx.unwrap_or(0), game_idx));
                    let game = GameId::read(reader)?;
                    games.push((game_idx, game));
                }
                let group = sub_entry.by_speed_mut(speed).by_mode_mut(mode);
                *group += LichessGroup { stats, games };
            }
        }
    }

    pub fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
        for (uci, sub_entry) in &self.sub_entries {
            write_uci(writer, uci)?;

            sub_entry.as_ref().try_map(|speed, by_mode| {
                by_mode.as_ref().try_map(|mode, group| {
                    if !group.games.is_empty() || !group.stats.is_empty() {
                        Header::Group {
                            speed,
                            mode,
                            num_games: min(group.games.len(), MAX_PERSONAL_GAMES),
                        }
                        .write(writer)?;

                        group.stats.write(writer)?;

                        for (game_idx, game) in group
                            .games
                            .iter()
                            .skip(group.games.len().saturating_sub(MAX_PERSONAL_GAMES))
                        {
                            write_uint(writer, *game_idx)?;
                            game.write(writer)?;
                        }
                    }

                    Ok::<_, io::Error>(())
                })
            })?;

            Header::End.write(writer)?;
        }

        Ok(())
    }

    pub fn prepare(self, filter: &PersonalQueryFilter) -> PreparedResponse {
        let mut total = Stats::default();
        let mut moves = Vec::with_capacity(self.sub_entries.len());
        let mut recent_games: Vec<(u64, Uci, GameId)> = Vec::new();

        for (uci, sub_entry) in self.sub_entries {
            let mut latest_game: Option<(u64, GameId)> = None;
            let mut stats = Stats::default();

            for speed in Speed::ALL {
                if filter
                    .speeds
                    .as_ref()
                    .map_or(true, |speeds| speeds.contains(&speed))
                {
                    for mode in Mode::ALL {
                        if filter
                            .modes
                            .as_ref()
                            .map_or(true, |modes| modes.contains(&mode))
                        {
                            let group = sub_entry.by_speed(speed).by_mode(mode);
                            stats += group.stats.to_owned();

                            for (idx, game) in group.games.iter().copied() {
                                if latest_game.map_or(true, |(latest_idx, _game)| latest_idx < idx)
                                {
                                    latest_game = Some((idx, game));
                                }
                            }

                            recent_games.extend(
                                group
                                    .games
                                    .iter()
                                    .copied()
                                    .map(|(idx, game)| (idx, uci.to_owned(), game)),
                            );
                        }
                    }
                }
            }

            if !stats.is_empty() || latest_game.is_some() {
                moves.push(PreparedMove {
                    uci,
                    stats: stats.clone(),
                    average_rating: None,
                    average_opponent_rating: stats.average_rating(),
                    game: latest_game.filter(|_| stats.is_single()).map(|(_, id)| id),
                });

                total += stats;
            }
        }

        moves.sort_by_key(|row| Reverse(row.stats.total()));
        recent_games.sort_by_key(|(idx, _, _)| Reverse(*idx));

        PreparedResponse {
            total,
            moves,
            recent_games: recent_games
                .into_iter()
                .map(|(_, uci, game)| (uci, game))
                .take(MAX_PERSONAL_GAMES as usize)
                .collect(),
            top_games: Vec::new(),
        }
    }
}

#[derive(Debug)]
pub struct PersonalStatus {
    pub latest_created_at: u64,
    pub revisit_ongoing_created_at: Option<u64>,
    pub indexed_at: SystemTime,
    pub revisited_at: SystemTime,
}

impl Default for PersonalStatus {
    fn default() -> PersonalStatus {
        PersonalStatus {
            latest_created_at: 0,
            revisit_ongoing_created_at: None,
            indexed_at: SystemTime::UNIX_EPOCH,
            revisited_at: SystemTime::UNIX_EPOCH,
        }
    }
}

impl PersonalStatus {
    pub const SIZE_HINT: usize = 3 * 8;

    pub fn maybe_revisit_ongoing(&mut self) -> Option<IndexRun> {
        if SystemTime::now()
            .duration_since(self.revisited_at)
            .unwrap_or_default()
            > Duration::from_secs(24 * 60 * 60)
        {
            self.revisit_ongoing_created_at
                .map(|since| IndexRun::Revisit { since })
        } else {
            None
        }
    }

    pub fn maybe_index(&self) -> Option<IndexRun> {
        SystemTime::now()
            .duration_since(self.indexed_at)
            .map_or(false, |cooldown| cooldown > Duration::from_secs(60))
            .then(|| IndexRun::Index {
                after: self.latest_created_at,
            })
    }

    pub fn finish_run(&mut self, run: IndexRun) {
        self.indexed_at = SystemTime::now();
        if matches!(run, IndexRun::Revisit { .. }) {
            self.revisited_at = self.indexed_at;
        }
    }

    pub fn read<R: Read>(reader: &mut R) -> io::Result<PersonalStatus> {
        Ok(PersonalStatus {
            latest_created_at: read_uint(reader)?,
            revisit_ongoing_created_at: Some(read_uint(reader)?).filter(|t| *t != 0),
            indexed_at: SystemTime::UNIX_EPOCH + Duration::from_secs(read_uint(reader)?),
            revisited_at: match read_uint(reader) {
                Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => SystemTime::UNIX_EPOCH, // bc
                Err(err) => return Err(err),
                Ok(secs) => SystemTime::UNIX_EPOCH + Duration::from_secs(secs),
            },
        })
    }

    pub fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
        write_uint(writer, self.latest_created_at)?;
        write_uint(writer, self.revisit_ongoing_created_at.unwrap_or(0))?;
        write_uint(
            writer,
            self.indexed_at
                .duration_since(SystemTime::UNIX_EPOCH)
                .expect("duration since unix epoch")
                .as_secs(),
        )?;
        write_uint(
            writer,
            self.revisited_at
                .duration_since(SystemTime::UNIX_EPOCH)
                .expect("duration since unix epoch")
                .as_secs(),
        )
    }
}

#[derive(Debug)]
pub enum IndexRun {
    Index { after: u64 },
    Revisit { since: u64 },
}

impl IndexRun {
    pub fn since(&self) -> u64 {
        match *self {
            IndexRun::Index { after } => {
                // Plus 1 millisecond, as an optimization to avoid overlap.
                // Might miss games if the index run happens between games
                // created in the same millisecond.
                after.saturating_add(1)
            }
            IndexRun::Revisit { since } => since,
        }
    }
}

impl fmt::Display for IndexRun {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            IndexRun::Index { after } => write!(f, "created_at > {}", after),
            IndexRun::Revisit { since } => write!(f, "created_at >= {}", since),
        }
    }
}

#[cfg(test)]
mod tests {
    use std::io::Cursor;

    use shakmaty::{Color, Square};

    use super::*;

    #[test]
    fn test_header_roundtrip() {
        let headers = [
            Header::Group {
                mode: Mode::Rated,
                speed: Speed::Correspondence,
                num_games: 15,
            },
            Header::End,
        ];

        let mut writer = Cursor::new(Vec::new());
        for header in &headers {
            header.write(&mut writer).unwrap();
        }

        let mut reader = Cursor::new(writer.into_inner());
        for header in headers {
            assert_eq!(Header::read(&mut reader).unwrap(), header);
        }
    }

    #[test]
    fn test_merge_personal() {
        let uci = Uci::Normal {
            from: Square::E2,
            to: Square::E4,
            promotion: None,
        };

        let a = PersonalEntry::new_single(
            uci.clone(),
            Speed::Bullet,
            Mode::Rated,
            "12345678".parse().unwrap(),
            Outcome::Decisive {
                winner: Color::White,
            },
            1600,
        );

        let b = PersonalEntry::new_single(
            uci.clone(),
            Speed::Bullet,
            Mode::Rated,
            "87654321".parse().unwrap(),
            Outcome::Decisive {
                winner: Color::Black,
            },
            1800,
        );

        let mut cursor = Cursor::new(Vec::new());
        a.write(&mut cursor).unwrap();
        assert_eq!(
            cursor.position() as usize,
            PersonalEntry::SIZE_HINT,
            "optimized for single entries"
        );

        let mut deserialized = PersonalEntry::default();
        deserialized
            .extend_from_reader(&mut Cursor::new(cursor.into_inner()))
            .unwrap();

        let mut cursor = Cursor::new(Vec::new());
        b.write(&mut cursor).unwrap();
        deserialized
            .extend_from_reader(&mut Cursor::new(cursor.into_inner()))
            .unwrap();

        assert_eq!(deserialized.sub_entries.len(), 1);
        let group = deserialized
            .sub_entries
            .get(&uci)
            .unwrap()
            .by_speed(Speed::Bullet)
            .by_mode(Mode::Rated);
        assert_eq!(group.stats.white, 1);
        assert_eq!(group.stats.draws, 0);
        assert_eq!(group.stats.black, 1);
        assert_eq!(group.stats.average_rating(), Some(1700));
        assert_eq!(group.games.len(), 2);
    }
}
<|endoftext|>


<commit_before>
//! Apply various kinds of transformations mid-stream.

use event;
use template;
use modifier;

/// A stream that can output either one of two kinds of streams depending on an
/// externally supplied criteria.
#[derive(Debug)]
pub struct ApplyEither<S1, S2> {
    stream: OneOf<S1, S2>
}

impl<S1, S2> ApplyEither<S1, S2> {

    pub(crate) fn first(stream: S1) -> ApplyEither<S1, S2> {
        ApplyEither {
            stream: OneOf::First(stream),
        }
    }

    pub(crate) fn second(stream: S2) -> ApplyEither<S1, S2> {
        ApplyEither {
            stream: OneOf::Second(stream),
        }
    }
}

impl<S1, S2> event::ElementStream for ApplyEither<S1, S2>
where
    S1: event::ElementStream,
    S2: event::ElementStream,
{}

impl<S1, S2> event::Stream for ApplyEither<S1, S2>
where
    S1: event::Stream,
    S2: event::Stream,
{
    fn next_event(&mut self) -> event::StreamResult {
        match self.stream {
            OneOf::First(ref mut stream) => stream.next_event(),
            OneOf::Second(ref mut stream) => stream.next_event(),
        }
    }
}

#[derive(Debug)]
enum OneOf<S1, S2> {
    First(S1),
    Second(S2),
}

/// A stream transformation with intermediate template storage.
#[derive(Debug)]
pub struct TemplateApply<S, B> {
    state: modifier::State<TemplateState<S, B>>,
}

impl<S, B> TemplateApply<S, B> {

    pub(crate) fn new(stream: S, builder: B) -> TemplateApply<S, B> {
        TemplateApply {
            state: modifier::State::new(TemplateState::Collect { stream, builder }),
        }
    }
}

impl<S, B> event::Stream for TemplateApply<S, B>
where
    S: event::Stream,
    B: FnOnce(template::Template) -> Result<template::Template, event::StreamError>,
{
    fn next_event(&mut self) -> event::StreamResult {
        self.state.step(|state| match state {
            TemplateState::Collect { stream, builder } => {
                let template = template::Template::from_stream(stream)?;
                let applied = builder(template)?;
                let stream = applied.to_stream();
                Ok(Some((event::noop(), Some(TemplateState::Emit { stream }))))
            },
            TemplateState::Emit { stream } =>
                modifier::passthrough(stream, |stream| TemplateState::Emit { stream }),
        })
    }
}

#[derive(Debug)]
enum TemplateState<S, B> {
    Collect {
        stream: S,
        builder: B,
    },
    Emit {
        stream: template::TemplateStream,
    },
}

#[cfg(test)]
mod tests {
    use std::str::{ FromStr };

    #[test]
    fn apply_as_template() {
        test_transform!(
            Default::default(),
            "23",
            "foo",
            |html| html
                .select(::select::Tag::from_str("b").unwrap(), |html| html
                    .apply_as_template(|template| template
                        .transform(|html| html
                            .select(::select::Tag::from_str("b").unwrap(), |html| html
                                .replace_contents("foo")
                            )
                        )
                    )
                )
        );
    }

    #[test]
    fn apply() {
        test_transform!(
            Default::default(),
            "23",
            "",
            |html| html
                .select(::select::Tag::from_str("b").unwrap(), |html| html
                    .apply(|html| html
                        .remove_contents()
                    )
                )
        );
    }

    #[test]
    fn apply_if_else() {
        test_transform!(
            Default::default(),
            "23",
            "99",
            |html| html
                .select(::select::Tag::from_str("b").unwrap(), |html| html
                    .apply_if_else(true,
                        |html| html.replace_contents("99"),
                        |html| html.replace_contents("33"),
                    )
                )
        );
        test_transform!(
            Default::default(),
            "23",
            "33",
            |html| html
                .select(::select::Tag::from_str("b").unwrap(), |html| html
                    .apply_if_else(false,
                        |html| html.replace_contents("99"),
                        |html| html.replace_contents("33"),
                    )
                )
        );
    }

    #[test]
    fn apply_if() {
        test_transform!(
            Default::default(),
            "23",
            "99",
            |html| html
                .select(::select::Tag::from_str("b").unwrap(), |html| html
                    .apply_if(true, |html| html
                        .replace_contents("99")
                    )
                )
        );
        test_transform!(
            Default::default(),
            "23",
            "23",
            |html| html
                .select(::select::Tag::from_str("b").unwrap(), |html| html
                    .apply_if(false, |html| html
                        .replace_contents("99")
                    )
                )
        );
    }
}
<commit_msg>moved apply to new test structure<commit_after>
//! Apply various kinds of transformations mid-stream.

use event;
use template;
use modifier;

/// A stream that can output either one of two kinds of streams depending on an
/// externally supplied criteria.
#[derive(Debug)]
pub struct ApplyEither<S1, S2> {
    stream: OneOf<S1, S2>
}

impl<S1, S2> ApplyEither<S1, S2> {

    pub(crate) fn first(stream: S1) -> ApplyEither<S1, S2> {
        ApplyEither {
            stream: OneOf::First(stream),
        }
    }

    pub(crate) fn second(stream: S2) -> ApplyEither<S1, S2> {
        ApplyEither {
            stream: OneOf::Second(stream),
        }
    }
}

impl<S1, S2> event::ElementStream for ApplyEither<S1, S2>
where
    S1: event::ElementStream,
    S2: event::ElementStream,
{}

impl<S1, S2> event::Stream for ApplyEither<S1, S2>
where
    S1: event::Stream,
    S2: event::Stream,
{
    fn next_event(&mut self) -> event::StreamResult {
        match self.stream {
            OneOf::First(ref mut stream) => stream.next_event(),
            OneOf::Second(ref mut stream) => stream.next_event(),
        }
    }
}

#[derive(Debug)]
enum OneOf<S1, S2> {
    First(S1),
    Second(S2),
}

/// A stream transformation with intermediate template storage.
#[derive(Debug)]
pub struct TemplateApply<S, B> {
    state: modifier::State<TemplateState<S, B>>,
}

impl<S, B> TemplateApply<S, B> {

    pub(crate) fn new(stream: S, builder: B) -> TemplateApply<S, B> {
        TemplateApply {
            state: modifier::State::new(TemplateState::Collect { stream, builder }),
        }
    }
}

impl<S, B> event::Stream for TemplateApply<S, B>
where
    S: event::Stream,
    B: FnOnce(template::Template) -> Result<template::Template, event::StreamError>,
{
    fn next_event(&mut self) -> event::StreamResult {
        self.state.step(|state| match state {
            TemplateState::Collect { stream, builder } => {
                let template = template::Template::from_stream(stream)?;
                let applied = builder(template)?;
                let stream = applied.to_stream();
                Ok(Some((event::noop(), Some(TemplateState::Emit { stream }))))
            },
            TemplateState::Emit { stream } =>
                modifier::passthrough(stream, |stream| TemplateState::Emit { stream }),
        })
    }
}

#[derive(Debug)]
enum TemplateState<S, B> {
    Collect {
        stream: S,
        builder: B,
    },
    Emit {
        stream: template::TemplateStream,
    },
}

#[cfg(test)]
mod tests {
    use std::str::{ FromStr };

    test_group!(apply_as_template:
        "with closure" => transform_test!(
            "23",
            "foo",
            |html| html.select("b", |html| html.apply_as_template(|template| template
                .transform(|html| html.select("b", |html| html.replace_contents("foo")))
            )),
        ),
    );

    test_group!(apply:
        "with closure" => transform_test!(
            "23",
            "",
            |html| html.select("b", |html| html.apply(|html| html.remove_contents())),
        ),
    );

    test_group!(apply_if_else:
        "true" => transform_test!(
            "23",
            "99",
            |html| html.select("b", |html| html.apply_if_else(
                true,
                |html| html.replace_contents("99"),
                |html| html.replace_contents("33"),
            )),
        ),
        "false" => transform_test!(
            "23",
            "33",
            |html| html.select("b", |html| html.apply_if_else(
                false,
                |html| html.replace_contents("99"),
                |html| html.replace_contents("33"),
            )),
        ),
    );

    test_group!(apply_if:
        "true" => transform_test!(
            "23",
            "99",
            |html| html.select("b", |html| html
                .apply_if(true, |html| html.replace_contents("99"))
            ),
        ),
        "false" => transform_test!(
            "23",
            "23",
            |html| html.select("b", |html| html
                .apply_if(false, |html| html.replace_contents("99"))
            ),
        ),
    );
}
<|endoftext|>


<commit_before>/// Windows implementation of dynamic library loading.
///
/// This module should be expanded with more Windows-specific functionality in the future.
extern crate winapi;
extern crate kernel32;

use util::{CowCString, CStringAsRef};

use std::ffi::{OsStr, OsString};
use std::{fmt, io, marker, mem, ptr};
use std::os::windows::ffi::{OsStrExt, OsStringExt};
use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT, Ordering};

/// A platform-specific equivalent of the cross-platform `Library`.
pub struct Library(winapi::HMODULE);

// There’s little documentation on Send-ability of HMODULE, but lack of warnings on the MSDN page
// and reading http://stackoverflow.com/a/11254061, it seems like it should be fine?
unsafe impl ::std::marker::Send for Library {}

impl Library {
    /// Find and load a shared library (module).
    ///
    /// Locations where library is searched for is platform specific and can’t be adjusted
    /// portably.
    ///
    /// Corresponds to `LoadLibraryW(filename)`.
    #[inline]
    pub fn new<P: AsRef<OsStr>>(filename: P) -> ::Result<Library> {
        let wide_filename: Vec<u16> = filename.as_ref().encode_wide().chain(Some(0)).collect();
        let _guard = ErrorModeGuard::new();

        let ret = with_get_last_error(|| {
            // Make sure no winapi calls as a result of drop happen inside this closure, because
            // otherwise that might change the return value of the GetLastError.
            let handle = unsafe { kernel32::LoadLibraryW(wide_filename.as_ptr()) };
            if handle.is_null()  {
                None
            } else {
                Some(Library(handle))
            }
        }).map_err(|e| e.unwrap_or_else(||
            panic!("LoadLibraryW failed but GetLastError did not report the error")
        ));

        drop(wide_filename); // Drop wide_filename here to ensure it doesn’t get moved and dropped
                             // inside the closure by mistake. See comment inside the closure.
        ret
    }

    /// Get a pointer to function or static variable by symbol name.
    ///
    /// The `symbol` may not contain any null bytes, with an exception of last byte. A null
    /// terminated `symbol` may avoid a string allocation in some cases.
    ///
    /// Symbol is interpreted as-is; no mangling is done. This means that symbols like `x::y` are
    /// most likely invalid.
    ///
    /// # Unsafety
    ///
    /// Pointer to a value of arbitrary type is returned. Using a value with wrong type is
    /// undefined.
    pub unsafe fn get<T>(&self, symbol: &[u8]) -> ::Result<Symbol<T>> {
        let symbol = try!(CowCString::from_bytes(symbol));
        with_get_last_error(|| {
            let symbol = kernel32::GetProcAddress(self.0, symbol.cstring_ref());
            if symbol.is_null() {
                None
            } else {
                Some(Symbol {
                    pointer: symbol,
                    pd: marker::PhantomData
                })
            }
        }).map_err(|e| e.unwrap_or_else(||
            panic!("GetProcAddress failed but GetLastError did not report the error")
        ))
    }
}

impl Drop for Library {
    fn drop(&mut self) {
        with_get_last_error(|| {
            if unsafe { kernel32::FreeLibrary(self.0) == 0 } {
                None
            } else {
                Some(())
            }
        }).unwrap()
    }
}

impl fmt::Debug for Library {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        unsafe {
            let mut buf: [winapi::WCHAR; 1024] = mem::uninitialized();
            let len = kernel32::GetModuleFileNameW(self.0,
                                                   (&mut buf[..]).as_mut_ptr(), 1024) as usize;
            if len == 0 {
                f.write_str(&format!("Library@{:p}", self.0))
            } else {
                let string: OsString = OsString::from_wide(&buf[..len]);
                f.write_str(&format!("Library@{:p} from {:?}", self.0, string))
            }
        }
    }
}

/// Symbol from a library.
///
/// A major difference compared to the cross-platform `Symbol` is that this does not ensure the
/// `Symbol` does not outlive `Library` it comes from.
#[derive(Clone)]
pub struct Symbol<T> {
    pointer: winapi::FARPROC,
    pd: marker::PhantomData<T>
}

impl<T> ::std::ops::Deref for Symbol<T> {
    type Target = T;
    fn deref(&self) -> &T {
        unsafe {
            // Additional reference level for a dereference on `deref` return value.
            mem::transmute(&self.pointer)
        }
    }
}

impl<T> fmt::Debug for Symbol<T> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.write_str(&format!("Symbol@{:p}", self.pointer))
    }
}

static USE_ERRORMODE: AtomicBool = ATOMIC_BOOL_INIT;
struct ErrorModeGuard(winapi::DWORD);

impl ErrorModeGuard {
    fn new() -> ErrorModeGuard {
        let mut ret = ErrorModeGuard(0);

        if !USE_ERRORMODE.load(Ordering::Acquire) {
            if unsafe { kernel32::SetThreadErrorMode(1, &mut ret.0) == 0
                        && kernel32::GetLastError() == winapi::ERROR_CALL_NOT_IMPLEMENTED } {
                USE_ERRORMODE.store(true, Ordering::Release);
            } else {
                return ret;
            }
        }
        ret.0 = unsafe { kernel32::SetErrorMode(1) };
        ret
    }
}

impl Drop for ErrorModeGuard {
    fn drop(&mut self) {
        unsafe {
            if !USE_ERRORMODE.load(Ordering::Relaxed) {
                kernel32::SetThreadErrorMode(self.0, ptr::null_mut());
            } else {
                kernel32::SetErrorMode(self.0);
            }
        }
    }
}

fn with_get_last_error<T, F>(closure: F) -> Result<T, Option<io::Error>>
where F: FnOnce() -> Option<T> {
    closure().ok_or_else(|| {
        let error = unsafe { kernel32::GetLastError() };
        if error == 0 {
            None
        } else {
            Some(io::Error::from_raw_os_error(error as i32))
        }
    })
}

#[test]
fn works_getlasterror() {
    let lib = Library::new("kernel32.dll").unwrap();
    let gle: Symbol<unsafe extern "system" fn() -> winapi::DWORD> = unsafe {
        lib.get(b"GetLastError").unwrap()
    };
    unsafe {
        kernel32::SetLastError(42);
        assert_eq!(kernel32::GetLastError(), gle())
    }
}

#[test]
fn works_getlasterror0() {
    let lib = Library::new("kernel32.dll").unwrap();
    let gle: Symbol<unsafe extern "system" fn() -> winapi::DWORD> = unsafe {
        lib.get(b"GetLastError\0").unwrap()
    };
    unsafe {
        kernel32::SetLastError(42);
        assert_eq!(kernel32::GetLastError(), gle())
    }
}

#[test]
fn fails_new_kernel23() {
    Library::new("kernel23").err().unwrap();
}
<commit_msg>[windows] Add a function to get item by ordinal<commit_after>/// Windows implementation of dynamic library loading.
///
/// This module should be expanded with more Windows-specific functionality in the future.
extern crate winapi;
extern crate kernel32;

use util::{CowCString, CStringAsRef};

use std::ffi::{OsStr, OsString};
use std::{fmt, io, marker, mem, ptr};
use std::os::windows::ffi::{OsStrExt, OsStringExt};
use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT, Ordering};

/// A platform-specific equivalent of the cross-platform `Library`.
pub struct Library(winapi::HMODULE);

// There’s little documentation on Send-ability of HMODULE, but lack of warnings on the MSDN page
// and reading http://stackoverflow.com/a/11254061, it seems like it should be fine?
unsafe impl ::std::marker::Send for Library {}

impl Library {
    /// Find and load a shared library (module).
    ///
    /// Locations where library is searched for is platform specific and can’t be adjusted
    /// portably.
    ///
    /// Corresponds to `LoadLibraryW(filename)`.
    #[inline]
    pub fn new<P: AsRef<OsStr>>(filename: P) -> ::Result<Library> {
        let wide_filename: Vec<u16> = filename.as_ref().encode_wide().chain(Some(0)).collect();
        let _guard = ErrorModeGuard::new();

        let ret = with_get_last_error(|| {
            // Make sure no winapi calls as a result of drop happen inside this closure, because
            // otherwise that might change the return value of the GetLastError.
            let handle = unsafe { kernel32::LoadLibraryW(wide_filename.as_ptr()) };
            if handle.is_null()  {
                None
            } else {
                Some(Library(handle))
            }
        }).map_err(|e| e.unwrap_or_else(||
            panic!("LoadLibraryW failed but GetLastError did not report the error")
        ));

        drop(wide_filename); // Drop wide_filename here to ensure it doesn’t get moved and dropped
                             // inside the closure by mistake. See comment inside the closure.
        ret
    }

    /// Get a pointer to function or static variable by symbol name.
    ///
    /// The `symbol` may not contain any null bytes, with an exception of last byte. A null
    /// terminated `symbol` may avoid a string allocation in some cases.
    ///
    /// Symbol is interpreted as-is; no mangling is done. This means that symbols like `x::y` are
    /// most likely invalid.
    ///
    /// # Unsafety
    ///
    /// Pointer to a value of arbitrary type is returned. Using a value with wrong type is
    /// undefined.
    pub unsafe fn get<T>(&self, symbol: &[u8]) -> ::Result<Symbol<T>> {
        let symbol = try!(CowCString::from_bytes(symbol));
        with_get_last_error(|| {
            let symbol = kernel32::GetProcAddress(self.0, symbol.cstring_ref());
            if symbol.is_null() {
                None
            } else {
                Some(Symbol {
                    pointer: symbol,
                    pd: marker::PhantomData
                })
            }
        }).map_err(|e| e.unwrap_or_else(||
            panic!("GetProcAddress failed but GetLastError did not report the error")
        ))
    }

    /// Get a pointer to function or static variable by ordinal number.
    pub unsafe fn get_ordinal<T>(&self, ordinal: winapi::WORD) -> ::Result<Symbol<T>> {
        with_get_last_error(|| {
            let ordinal = ordinal as usize as *mut _;
            let symbol = kernel32::GetProcAddress(self.0, ordinal);
            if symbol.is_null() {
                None
            } else {
                Some(Symbol {
                    pointer: symbol,
                    pd: marker::PhantomData
                })
            }
        }).map_err(|e| e.unwrap_or_else(||
            panic!("GetProcAddress failed but GetLastError did not report the error")
        ))
    }
}

impl Drop for Library {
    fn drop(&mut self) {
        with_get_last_error(|| {
            if unsafe { kernel32::FreeLibrary(self.0) == 0 } {
                None
            } else {
                Some(())
            }
        }).unwrap()
    }
}

impl fmt::Debug for Library {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        unsafe {
            let mut buf: [winapi::WCHAR; 1024] = mem::uninitialized();
            let len = kernel32::GetModuleFileNameW(self.0,
                                                   (&mut buf[..]).as_mut_ptr(), 1024) as usize;
            if len == 0 {
                f.write_str(&format!("Library@{:p}", self.0))
            } else {
                let string: OsString = OsString::from_wide(&buf[..len]);
                f.write_str(&format!("Library@{:p} from {:?}", self.0, string))
            }
        }
    }
}

/// Symbol from a library.
///
/// A major difference compared to the cross-platform `Symbol` is that this does not ensure the
/// `Symbol` does not outlive `Library` it comes from.
#[derive(Clone)]
pub struct Symbol<T> {
    pointer: winapi::FARPROC,
    pd: marker::PhantomData<T>
}

impl<T> ::std::ops::Deref for Symbol<T> {
    type Target = T;
    fn deref(&self) -> &T {
        unsafe {
            // Additional reference level for a dereference on `deref` return value.
            mem::transmute(&self.pointer)
        }
    }
}

impl<T> fmt::Debug for Symbol<T> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.write_str(&format!("Symbol@{:p}", self.pointer))
    }
}

static USE_ERRORMODE: AtomicBool = ATOMIC_BOOL_INIT;
struct ErrorModeGuard(winapi::DWORD);

impl ErrorModeGuard {
    fn new() -> ErrorModeGuard {
        let mut ret = ErrorModeGuard(0);

        if !USE_ERRORMODE.load(Ordering::Acquire) {
            if unsafe { kernel32::SetThreadErrorMode(1, &mut ret.0) == 0
                        && kernel32::GetLastError() == winapi::ERROR_CALL_NOT_IMPLEMENTED } {
                USE_ERRORMODE.store(true, Ordering::Release);
            } else {
                return ret;
            }
        }
        ret.0 = unsafe { kernel32::SetErrorMode(1) };
        ret
    }
}

impl Drop for ErrorModeGuard {
    fn drop(&mut self) {
        unsafe {
            if !USE_ERRORMODE.load(Ordering::Relaxed) {
                kernel32::SetThreadErrorMode(self.0, ptr::null_mut());
            } else {
                kernel32::SetErrorMode(self.0);
            }
        }
    }
}

fn with_get_last_error<T, F>(closure: F) -> Result<T, Option<io::Error>>
where F: FnOnce() -> Option<T> {
    closure().ok_or_else(|| {
        let error = unsafe { kernel32::GetLastError() };
        if error == 0 {
            None
        } else {
            Some(io::Error::from_raw_os_error(error as i32))
        }
    })
}

#[test]
fn works_getlasterror() {
    let lib = Library::new("kernel32.dll").unwrap();
    let gle: Symbol<unsafe extern "system" fn() -> winapi::DWORD> = unsafe {
        lib.get(b"GetLastError").unwrap()
    };
    unsafe {
        kernel32::SetLastError(42);
        assert_eq!(kernel32::GetLastError(), gle())
    }
}

#[test]
fn works_getlasterror0() {
    let lib = Library::new("kernel32.dll").unwrap();
    let gle: Symbol<unsafe extern "system" fn() -> winapi::DWORD> = unsafe {
        lib.get(b"GetLastError\0").unwrap()
    };
    unsafe {
        kernel32::SetLastError(42);
        assert_eq!(kernel32::GetLastError(), gle())
    }
}

#[test]
fn fails_new_kernel23() {
    Library::new("kernel23").err().unwrap();
}
<|endoftext|>


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

use std::f32::consts::{FRAC_1_SQRT_2};
use euclid::{Point2D, Rect, Size2D};
use euclid::{TypedRect, TypedPoint2D, TypedSize2D, TypedPoint4D, TypedMatrix4D};
use webrender_traits::{DeviceIntRect, DeviceIntPoint, DeviceIntSize};
use webrender_traits::{LayerRect, WorldPoint4D, LayerPoint4D, LayerToWorldTransform};
use webrender_traits::{BorderRadius, ComplexClipRegion, LayoutRect};
use num_traits::Zero;

// TODO: Implement these in euclid!
pub trait MatrixHelpers<Src, Dst> {
    fn transform_point_and_perspective_project(&self, point: &TypedPoint4D<f32, Src>) -> TypedPoint2D<f32, Dst>;
    fn transform_rect(&self, rect: &TypedRect<f32, Src>) -> TypedRect<f32, Dst>;

    /// Returns true if this matrix transforms an axis-aligned 2D rectangle to another axis-aligned
    /// 2D rectangle.
    fn can_losslessly_transform_a_2d_rect(&self) -> bool;

    /// Returns true if this matrix transforms an axis-aligned 2D rectangle to another axis-
    /// aligned 2D rectangle after perspective divide.
    fn can_losslessly_transform_and_perspective_project_a_2d_rect(&self) -> bool;

    /// Clears out the portions of the matrix that `transform_rect()` uses. This allows the use of
    /// `transform_rect()` while keeping the Z/W transform portions of the matrix intact.
    fn reset_after_transforming_rect(&self) -> TypedMatrix4D<f32, Src, Dst>;

    fn is_identity(&self) -> bool;
}

impl<Src, Dst> MatrixHelpers<Src, Dst> for TypedMatrix4D<f32, Src, Dst> {
    fn transform_point_and_perspective_project(&self, point: &TypedPoint4D<f32, Src>) -> TypedPoint2D<f32, Dst> {
        let point = self.transform_point4d(point);
        TypedPoint2D::new(point.x / point.w, point.y / point.w)
    }

    fn transform_rect(&self, rect: &TypedRect<f32, Src>) -> TypedRect<f32, Dst> {
        let top_left = self.transform_point(&rect.origin);
        let top_right = self.transform_point(&rect.top_right());
        let bottom_left = self.transform_point(&rect.bottom_left());
        let bottom_right = self.transform_point(&rect.bottom_right());
        TypedRect::from_points(&[top_left, top_right, bottom_right, bottom_left])
    }

    fn can_losslessly_transform_a_2d_rect(&self) -> bool {
        self.m12 == 0.0 && self.m14 == 0.0 && self.m21 == 0.0 && self.m24 == 0.0 && self.m44 == 1.0
    }

    fn can_losslessly_transform_and_perspective_project_a_2d_rect(&self) -> bool {
        self.m12 == 0.0 && self.m21 == 0.0
    }

    fn reset_after_transforming_rect(&self) -> TypedMatrix4D<f32, Src, Dst> {
        TypedMatrix4D::row_major(
            1.0,      0.0,      self.m13, 0.0,
            0.0,      1.0,      self.m23, 0.0,
            self.m31, self.m32, self.m33, self.m34,
            0.0,      0.0,      self.m43, 1.0,
        )
    }

    fn is_identity(&self) -> bool {
        *self == TypedMatrix4D::identity()
    }
}

pub trait RectHelpers<U> where Self: Sized {
    fn contains_rect(&self, other: &Self) -> bool;
    fn from_floats(x0: f32, y0: f32, x1: f32, y1: f32) -> Self;
    fn is_well_formed_and_nonempty(&self) -> bool;
}

impl<U> RectHelpers<U> for TypedRect<f32, U> {

    fn contains_rect(&self, other: &Self) -> bool {
        self.origin.x <= other.origin.x &&
        self.origin.y <= other.origin.y &&
        self.max_x() >= other.max_x() &&
        self.max_y() >= other.max_y()
    }

    fn from_floats(x0: f32, y0: f32, x1: f32, y1: f32) -> Self {
        TypedRect::new(TypedPoint2D::new(x0, y0),
                       TypedSize2D::new(x1 - x0, y1 - y0))
    }

    fn is_well_formed_and_nonempty(&self) -> bool {
        self.size.width > 0.0 && self.size.height > 0.0
    }
}

// Don't use `euclid`'s `is_empty` because that has effectively has an "and" in the conditional
// below instead of an "or".
pub fn rect_is_empty<N:PartialEq + Zero, U>(rect: &TypedRect<N, U>) -> bool {
    rect.size.width == Zero::zero() || rect.size.height == Zero::zero()
}

#[inline]
pub fn rect_from_points_f(x0: f32,
                          y0: f32,
                          x1: f32,
                          y1: f32) -> Rect<f32> {
    Rect::new(Point2D::new(x0, y0),
              Size2D::new(x1 - x0, y1 - y0))
}

pub fn lerp(a: f32, b: f32, t: f32) -> f32 {
    (b - a) * t + a
}

pub fn subtract_rect<U>(rect: &TypedRect<f32, U>,
                        other: &TypedRect<f32, U>,
                        results: &mut Vec<TypedRect<f32, U>>) {
    results.clear();

    let int = rect.intersection(other);
    match int {
        Some(int) => {
            let rx0 = rect.origin.x;
            let ry0 = rect.origin.y;
            let rx1 = rx0 + rect.size.width;
            let ry1 = ry0 + rect.size.height;

            let ox0 = int.origin.x;
            let oy0 = int.origin.y;
            let ox1 = ox0 + int.size.width;
            let oy1 = oy0 + int.size.height;

            let r = TypedRect::from_untyped(&rect_from_points_f(rx0, ry0, ox0, ry1));
            if r.size.width > 0.0 && r.size.height > 0.0 {
                results.push(r);
            }
            let r = TypedRect::from_untyped(&rect_from_points_f(ox0, ry0, ox1, oy0));
            if r.size.width > 0.0 && r.size.height > 0.0 {
                results.push(r);
            }
            let r = TypedRect::from_untyped(&rect_from_points_f(ox0, oy1, ox1, ry1));
            if r.size.width > 0.0 && r.size.height > 0.0 {
                results.push(r);
            }
            let r = TypedRect::from_untyped(&rect_from_points_f(ox1, ry0, rx1, ry1));
            if r.size.width > 0.0 && r.size.height > 0.0 {
                results.push(r);
            }
        }
        None => {
            results.push(*rect);
        }
    }
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[repr(u8)]
pub enum TransformedRectKind {
    AxisAligned = 0,
    Complex = 1,
}

#[derive(Debug, Clone)]
pub struct TransformedRect {
    pub local_rect: LayerRect,
    pub bounding_rect: DeviceIntRect,
    pub inner_rect: DeviceIntRect,
    pub vertices: [WorldPoint4D; 4],
    pub kind: TransformedRectKind,
}

impl TransformedRect {
    pub fn new(rect: &LayerRect,
               transform: &LayerToWorldTransform,
               device_pixel_ratio: f32) -> TransformedRect {

        let kind = if transform.can_losslessly_transform_and_perspective_project_a_2d_rect() {
            TransformedRectKind::AxisAligned
        } else {
            TransformedRectKind::Complex
        };

        // FIXME(gw): This code is meant to be a fast path for simple transforms.
        // However, it fails on transforms that translate Z but result in an
        // axis aligned rect.

/*
        match kind {
            TransformedRectKind::AxisAligned => {
                let v0 = transform.transform_point(&rect.origin);
                let v1 = transform.transform_point(&rect.top_right());
                let v2 = transform.transform_point(&rect.bottom_left());
                let v3 = transform.transform_point(&rect.bottom_right());

                let screen_min_dp = Point2D::new(DevicePixel((v0.x * device_pixel_ratio).floor() as i32),
                                                 DevicePixel((v0.y * device_pixel_ratio).floor() as i32));
                let screen_max_dp = Point2D::new(DevicePixel((v3.x * device_pixel_ratio).ceil() as i32),
                                                 DevicePixel((v3.y * device_pixel_ratio).ceil() as i32));

                let screen_rect_dp = Rect::new(screen_min_dp, Size2D::new(screen_max_dp.x - screen_min_dp.x,
                                                                          screen_max_dp.y - screen_min_dp.y));

                TransformedRect {
                    local_rect: *rect,
                    vertices: [
                        Point4D::new(v0.x, v0.y, 0.0, 1.0),
                        Point4D::new(v1.x, v1.y, 0.0, 1.0),
                        Point4D::new(v2.x, v2.y, 0.0, 1.0),
                        Point4D::new(v3.x, v3.y, 0.0, 1.0),
                    ],
                    bounding_rect: screen_rect_dp,
                    kind: kind,
                }
            }
            TransformedRectKind::Complex => {
                */
                let vertices = [
                    transform.transform_point4d(&LayerPoint4D::new(rect.origin.x,
                                                                   rect.origin.y,
                                                                   0.0,
                                                                   1.0)),
                    transform.transform_point4d(&LayerPoint4D::new(rect.bottom_left().x,
                                                                   rect.bottom_left().y,
                                                                   0.0,
                                                                   1.0)),
                    transform.transform_point4d(&LayerPoint4D::new(rect.bottom_right().x,
                                                                   rect.bottom_right().y,
                                                                   0.0,
                                                                   1.0)),
                    transform.transform_point4d(&LayerPoint4D::new(rect.top_right().x,
                                                                   rect.top_right().y,
                                                                   0.0,
                                                                   1.0)),
                ];

                let (mut xs, mut ys) = ([0.0; 4], [0.0; 4]);

                for (vertex, (x, y)) in vertices.iter().zip(xs.iter_mut().zip(ys.iter_mut())) {
                    let inv_w = 1.0 / vertex.w;
                    *x = vertex.x * inv_w;
                    *y = vertex.y * inv_w;
                }

                xs.sort_by(|a, b| a.partial_cmp(b).unwrap());
                ys.sort_by(|a, b| a.partial_cmp(b).unwrap());

                let outer_min_dp = DeviceIntPoint::new((xs[0] * device_pixel_ratio).floor() as i32,
                                                       (ys[0] * device_pixel_ratio).floor() as i32);
                let outer_max_dp = DeviceIntPoint::new((xs[3] * device_pixel_ratio).ceil() as i32,
                                                       (ys[3] * device_pixel_ratio).ceil() as i32);
                let inner_min_dp = DeviceIntPoint::new((xs[1] * device_pixel_ratio).ceil() as i32,
                                                       (ys[1] * device_pixel_ratio).ceil() as i32);
                let inner_max_dp = DeviceIntPoint::new((xs[2] * device_pixel_ratio).floor() as i32,
                                                       (ys[2] * device_pixel_ratio).floor() as i32);

                TransformedRect {
                    local_rect: *rect,
                    vertices: vertices,
                    bounding_rect: DeviceIntRect::new(outer_min_dp,
                                                      DeviceIntSize::new(outer_max_dp.x - outer_min_dp.x,
                                                                         outer_max_dp.y - outer_min_dp.y)),
                    inner_rect: DeviceIntRect::new(inner_min_dp,
                                                   DeviceIntSize::new(inner_max_dp.x - inner_min_dp.x,
                                                                      inner_max_dp.y - inner_min_dp.y)),
                    kind: kind,
                }
                /*
            }
        }*/
    }
}

#[inline(always)]
pub fn pack_as_float(value: u32) -> f32 {
    value as f32 + 0.5
}

pub trait ComplexClipRegionHelpers {
    /// Return an aligned rectangle that is inside the clip region and doesn't intersect
    /// any of the bounding rectangles of the rounded corners.
    fn get_inner_rect_safe(&self) -> Option<LayoutRect>;
    /// Return the approximately largest aligned rectangle that is fully inside
    /// the provided clip region.
    fn get_inner_rect_full(&self) -> Option<LayoutRect>;
}

impl ComplexClipRegionHelpers for ComplexClipRegion {
    fn get_inner_rect_safe(&self) -> Option<LayoutRect> {
        // value of `k==1.0` is used for extraction of the corner rectangles
        // see `SEGMENT_CORNER_*` in `clip_shared.glsl`
        extract_inner_rect_impl(&self.rect, &self.radii, 1.0)
    }

    fn get_inner_rect_full(&self) -> Option<LayoutRect> {
        // this `k` optimal for a simple case of all border radii being equal
        let k = 1.0 - 0.5 * FRAC_1_SQRT_2; // could be nicely approximated to `0.3`
        extract_inner_rect_impl(&self.rect, &self.radii, k)
    }
}

#[inline]
fn extract_inner_rect_impl<U>(rect: &TypedRect<f32, U>,
                              radii: &BorderRadius,
                              k: f32) -> Option<TypedRect<f32, U>> {
    // `k` defines how much border is taken into account

    let xl = rect.origin.x +
        k * radii.top_left.width.max(radii.bottom_left.width);
    let xr = rect.origin.x + rect.size.width -
        k * radii.top_right.width.max(radii.bottom_right.width);
    let yt = rect.origin.y +
        k * radii.top_left.height.max(radii.top_right.height);
    let yb = rect.origin.y + rect.size.height -
        k * radii.bottom_left.height.max(radii.bottom_right.height);

    if xl <= xr && yt <= yb {
        Some(TypedRect::new(TypedPoint2D::new(xl, yt),
             TypedSize2D::new(xr-xl, yb-yt)))
    } else {
        None
    }
}
<commit_msg>Fixed the inner rectangle offsets<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use std::f32::consts::{FRAC_1_SQRT_2};
use euclid::{Point2D, Rect, Size2D};
use euclid::{TypedRect, TypedPoint2D, TypedSize2D, TypedPoint4D, TypedMatrix4D};
use webrender_traits::{DeviceIntRect, DeviceIntPoint, DeviceIntSize};
use webrender_traits::{LayerRect, WorldPoint4D, LayerPoint4D, LayerToWorldTransform};
use webrender_traits::{BorderRadius, ComplexClipRegion, LayoutRect};
use num_traits::Zero;

// TODO: Implement these in euclid!
pub trait MatrixHelpers<Src, Dst> {
    fn transform_point_and_perspective_project(&self, point: &TypedPoint4D<f32, Src>) -> TypedPoint2D<f32, Dst>;
    fn transform_rect(&self, rect: &TypedRect<f32, Src>) -> TypedRect<f32, Dst>;

    /// Returns true if this matrix transforms an axis-aligned 2D rectangle to another axis-aligned
    /// 2D rectangle.
    fn can_losslessly_transform_a_2d_rect(&self) -> bool;

    /// Returns true if this matrix transforms an axis-aligned 2D rectangle to another axis-
    /// aligned 2D rectangle after perspective divide.
    fn can_losslessly_transform_and_perspective_project_a_2d_rect(&self) -> bool;

    /// Clears out the portions of the matrix that `transform_rect()` uses. This allows the use of
    /// `transform_rect()` while keeping the Z/W transform portions of the matrix intact.
    fn reset_after_transforming_rect(&self) -> TypedMatrix4D<f32, Src, Dst>;

    fn is_identity(&self) -> bool;
}

impl<Src, Dst> MatrixHelpers<Src, Dst> for TypedMatrix4D<f32, Src, Dst> {
    fn transform_point_and_perspective_project(&self, point: &TypedPoint4D<f32, Src>) -> TypedPoint2D<f32, Dst> {
        let point = self.transform_point4d(point);
        TypedPoint2D::new(point.x / point.w, point.y / point.w)
    }

    fn transform_rect(&self, rect: &TypedRect<f32, Src>) -> TypedRect<f32, Dst> {
        let top_left = self.transform_point(&rect.origin);
        let top_right = self.transform_point(&rect.top_right());
        let bottom_left = self.transform_point(&rect.bottom_left());
        let bottom_right = self.transform_point(&rect.bottom_right());
        TypedRect::from_points(&[top_left, top_right, bottom_right, bottom_left])
    }

    fn can_losslessly_transform_a_2d_rect(&self) -> bool {
        self.m12 == 0.0 && self.m14 == 0.0 && self.m21 == 0.0 && self.m24 == 0.0 && self.m44 == 1.0
    }

    fn can_losslessly_transform_and_perspective_project_a_2d_rect(&self) -> bool {
        self.m12 == 0.0 && self.m21 == 0.0
    }

    fn reset_after_transforming_rect(&self) -> TypedMatrix4D<f32, Src, Dst> {
        TypedMatrix4D::row_major(
            1.0,      0.0,      self.m13, 0.0,
            0.0,      1.0,      self.m23, 0.0,
            self.m31, self.m32, self.m33, self.m34,
            0.0,      0.0,      self.m43, 1.0,
        )
    }

    fn is_identity(&self) -> bool {
        *self == TypedMatrix4D::identity()
    }
}

pub trait RectHelpers<U> where Self: Sized {
    fn contains_rect(&self, other: &Self) -> bool;
    fn from_floats(x0: f32, y0: f32, x1: f32, y1: f32) -> Self;
    fn is_well_formed_and_nonempty(&self) -> bool;
}

impl<U> RectHelpers<U> for TypedRect<f32, U> {

    fn contains_rect(&self, other: &Self) -> bool {
        self.origin.x <= other.origin.x &&
        self.origin.y <= other.origin.y &&
        self.max_x() >= other.max_x() &&
        self.max_y() >= other.max_y()
    }

    fn from_floats(x0: f32, y0: f32, x1: f32, y1: f32) -> Self {
        TypedRect::new(TypedPoint2D::new(x0, y0),
                       TypedSize2D::new(x1 - x0, y1 - y0))
    }

    fn is_well_formed_and_nonempty(&self) -> bool {
        self.size.width > 0.0 && self.size.height > 0.0
    }
}

// Don't use `euclid`'s `is_empty` because that has effectively has an "and" in the conditional
// below instead of an "or".
pub fn rect_is_empty<N:PartialEq + Zero, U>(rect: &TypedRect<N, U>) -> bool {
    rect.size.width == Zero::zero() || rect.size.height == Zero::zero()
}

#[inline]
pub fn rect_from_points_f(x0: f32,
                          y0: f32,
                          x1: f32,
                          y1: f32) -> Rect<f32> {
    Rect::new(Point2D::new(x0, y0),
              Size2D::new(x1 - x0, y1 - y0))
}

pub fn lerp(a: f32, b: f32, t: f32) -> f32 {
    (b - a) * t + a
}

pub fn subtract_rect<U>(rect: &TypedRect<f32, U>,
                        other: &TypedRect<f32, U>,
                        results: &mut Vec<TypedRect<f32, U>>) {
    results.clear();

    let int = rect.intersection(other);
    match int {
        Some(int) => {
            let rx0 = rect.origin.x;
            let ry0 = rect.origin.y;
            let rx1 = rx0 + rect.size.width;
            let ry1 = ry0 + rect.size.height;

            let ox0 = int.origin.x;
            let oy0 = int.origin.y;
            let ox1 = ox0 + int.size.width;
            let oy1 = oy0 + int.size.height;

            let r = TypedRect::from_untyped(&rect_from_points_f(rx0, ry0, ox0, ry1));
            if r.size.width > 0.0 && r.size.height > 0.0 {
                results.push(r);
            }
            let r = TypedRect::from_untyped(&rect_from_points_f(ox0, ry0, ox1, oy0));
            if r.size.width > 0.0 && r.size.height > 0.0 {
                results.push(r);
            }
            let r = TypedRect::from_untyped(&rect_from_points_f(ox0, oy1, ox1, ry1));
            if r.size.width > 0.0 && r.size.height > 0.0 {
                results.push(r);
            }
            let r = TypedRect::from_untyped(&rect_from_points_f(ox1, ry0, rx1, ry1));
            if r.size.width > 0.0 && r.size.height > 0.0 {
                results.push(r);
            }
        }
        None => {
            results.push(*rect);
        }
    }
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[repr(u8)]
pub enum TransformedRectKind {
    AxisAligned = 0,
    Complex = 1,
}

#[derive(Debug, Clone)]
pub struct TransformedRect {
    pub local_rect: LayerRect,
    pub bounding_rect: DeviceIntRect,
    pub inner_rect: DeviceIntRect,
    pub vertices: [WorldPoint4D; 4],
    pub kind: TransformedRectKind,
}

impl TransformedRect {
    pub fn new(rect: &LayerRect,
               transform: &LayerToWorldTransform,
               device_pixel_ratio: f32) -> TransformedRect {

        let kind = if transform.can_losslessly_transform_and_perspective_project_a_2d_rect() {
            TransformedRectKind::AxisAligned
        } else {
            TransformedRectKind::Complex
        };

        // FIXME(gw): This code is meant to be a fast path for simple transforms.
        // However, it fails on transforms that translate Z but result in an
        // axis aligned rect.

/*
        match kind {
            TransformedRectKind::AxisAligned => {
                let v0 = transform.transform_point(&rect.origin);
                let v1 = transform.transform_point(&rect.top_right());
                let v2 = transform.transform_point(&rect.bottom_left());
                let v3 = transform.transform_point(&rect.bottom_right());

                let screen_min_dp = Point2D::new(DevicePixel((v0.x * device_pixel_ratio).floor() as i32),
                                                 DevicePixel((v0.y * device_pixel_ratio).floor() as i32));
                let screen_max_dp = Point2D::new(DevicePixel((v3.x * device_pixel_ratio).ceil() as i32),
                                                 DevicePixel((v3.y * device_pixel_ratio).ceil() as i32));

                let screen_rect_dp = Rect::new(screen_min_dp, Size2D::new(screen_max_dp.x - screen_min_dp.x,
                                                                          screen_max_dp.y - screen_min_dp.y));

                TransformedRect {
                    local_rect: *rect,
                    vertices: [
                        Point4D::new(v0.x, v0.y, 0.0, 1.0),
                        Point4D::new(v1.x, v1.y, 0.0, 1.0),
                        Point4D::new(v2.x, v2.y, 0.0, 1.0),
                        Point4D::new(v3.x, v3.y, 0.0, 1.0),
                    ],
                    bounding_rect: screen_rect_dp,
                    kind: kind,
                }
            }
            TransformedRectKind::Complex => {
                */
                let vertices = [
                    transform.transform_point4d(&LayerPoint4D::new(rect.origin.x,
                                                                   rect.origin.y,
                                                                   0.0,
                                                                   1.0)),
                    transform.transform_point4d(&LayerPoint4D::new(rect.bottom_left().x,
                                                                   rect.bottom_left().y,
                                                                   0.0,
                                                                   1.0)),
                    transform.transform_point4d(&LayerPoint4D::new(rect.bottom_right().x,
                                                                   rect.bottom_right().y,
                                                                   0.0,
                                                                   1.0)),
                    transform.transform_point4d(&LayerPoint4D::new(rect.top_right().x,
                                                                   rect.top_right().y,
                                                                   0.0,
                                                                   1.0)),
                ];

                let (mut xs, mut ys) = ([0.0; 4], [0.0; 4]);

                for (vertex, (x, y)) in vertices.iter().zip(xs.iter_mut().zip(ys.iter_mut())) {
                    let inv_w = 1.0 / vertex.w;
                    *x = vertex.x * inv_w;
                    *y = vertex.y * inv_w;
                }

                xs.sort_by(|a, b| a.partial_cmp(b).unwrap());
                ys.sort_by(|a, b| a.partial_cmp(b).unwrap());

                let outer_min_dp = DeviceIntPoint::new((xs[0] * device_pixel_ratio).floor() as i32,
                                                       (ys[0] * device_pixel_ratio).floor() as i32);
                let outer_max_dp = DeviceIntPoint::new((xs[3] * device_pixel_ratio).ceil() as i32,
                                                       (ys[3] * device_pixel_ratio).ceil() as i32);
                let inner_min_dp = DeviceIntPoint::new((xs[1] * device_pixel_ratio).ceil() as i32,
                                                       (ys[1] * device_pixel_ratio).ceil() as i32);
                let inner_max_dp = DeviceIntPoint::new((xs[2] * device_pixel_ratio).floor() as i32,
                                                       (ys[2] * device_pixel_ratio).floor() as i32);

                TransformedRect {
                    local_rect: *rect,
                    vertices: vertices,
                    bounding_rect: DeviceIntRect::new(outer_min_dp,
                                                      DeviceIntSize::new(outer_max_dp.x - outer_min_dp.x,
                                                                         outer_max_dp.y - outer_min_dp.y)),
                    inner_rect: DeviceIntRect::new(inner_min_dp,
                                                   DeviceIntSize::new(inner_max_dp.x - inner_min_dp.x,
                                                                      inner_max_dp.y - inner_min_dp.y)),
                    kind: kind,
                }
                /*
            }
        }*/
    }
}

#[inline(always)]
pub fn pack_as_float(value: u32) -> f32 {
    value as f32 + 0.5
}

pub trait ComplexClipRegionHelpers {
    /// Return an aligned rectangle that is inside the clip region and doesn't intersect
    /// any of the bounding rectangles of the rounded corners.
    fn get_inner_rect_safe(&self) -> Option<LayoutRect>;
    /// Return the approximately largest aligned rectangle that is fully inside
    /// the provided clip region.
    fn get_inner_rect_full(&self) -> Option<LayoutRect>;
}

impl ComplexClipRegionHelpers for ComplexClipRegion {
    fn get_inner_rect_safe(&self) -> Option<LayoutRect> {
        // value of `k==1.0` is used for extraction of the corner rectangles
        // see `SEGMENT_CORNER_*` in `clip_shared.glsl`
        extract_inner_rect_impl(&self.rect, &self.radii, 1.0)
    }

    fn get_inner_rect_full(&self) -> Option<LayoutRect> {
        // this `k` optimal for a simple case of all border radii being equal
        let k = 1.0 - 0.5 * FRAC_1_SQRT_2; // could be nicely approximated to `0.3`
        extract_inner_rect_impl(&self.rect, &self.radii, k)
    }
}

#[inline]
fn extract_inner_rect_impl<U>(rect: &TypedRect<f32, U>,
                              radii: &BorderRadius,
                              k: f32) -> Option<TypedRect<f32, U>> {
    // `k` defines how much border is taken into account
    // We enforce the offsets to be rounded to pixel boundaries
    // by `ceil`-ing and `floor`-ing them

    let xl = (k * radii.top_left.width.max(radii.bottom_left.width)).ceil();
    let xr = (rect.size.width - k * radii.top_right.width.max(radii.bottom_right.width)).floor();
    let yt = (k * radii.top_left.height.max(radii.top_right.height)).ceil();
    let yb = (rect.size.height - k * radii.bottom_left.height.max(radii.bottom_right.height)).floor();

    if xl <= xr && yt <= yb {
        Some(TypedRect::new(TypedPoint2D::new(rect.origin.x + xl, rect.origin.y + yt),
             TypedSize2D::new(xr-xl, yb-yt)))
    } else {
        None
    }
}
<|endoftext|>


<commit_before>use super::frontend_prelude::*;

use crate::models::{CrateOwnerInvitation, User};
use crate::schema::{crate_owner_invitations, crates, users};
use crate::views::{EncodableCrateOwnerInvitation, EncodablePublicUser, InvitationResponse};
use diesel::dsl::any;
use std::collections::HashMap;

/// Handles the `GET /me/crate_owner_invitations` route.
pub fn list(req: &mut dyn RequestExt) -> EndpointResult {
    // Ensure that the user is authenticated
    let user = req.authenticate()?.forbid_api_token_auth()?.user();

    // Load all pending invitations for the user
    let conn = &*req.db_read_only()?;
    let crate_owner_invitations: Vec<CrateOwnerInvitation> = crate_owner_invitations::table
        .filter(crate_owner_invitations::invited_user_id.eq(user.id))
        .load(&*conn)?;

    // Make a list of all related users
    let user_ids: Vec<_> = crate_owner_invitations
        .iter()
        .map(|invitation| invitation.invited_by_user_id)
        .collect();

    // Load all related users
    let users: Vec<User> = users::table
        .filter(users::id.eq(any(user_ids)))
        .load(conn)?;

    let users: HashMap<i32, User> = users.into_iter().map(|user| (user.id, user)).collect();

    // Make a list of all related crates
    let crate_ids: Vec<_> = crate_owner_invitations
        .iter()
        .map(|invitation| invitation.crate_id)
        .collect();

    // Load all related crates
    let crates: Vec<_> = crates::table
        .select((crates::id, crates::name))
        .filter(crates::id.eq(any(crate_ids)))
        .load(conn)?;

    let crates: HashMap<i32, String> = crates.into_iter().collect();

    // Turn `CrateOwnerInvitation` list into `EncodableCrateOwnerInvitation` list
    let config = &req.app().config;
    let crate_owner_invitations = crate_owner_invitations
        .into_iter()
        .filter(|i| !i.is_expired(config))
        .map(|invitation| {
            let inviter_id = invitation.invited_by_user_id;
            let inviter_name = users
                .get(&inviter_id)
                .map(|user| user.gh_login.clone())
                .unwrap_or_default();

            let crate_name = crates
                .get(&invitation.crate_id)
                .cloned()
                .unwrap_or_else(|| String::from("(unknown crate name)"));

            let expires_at = invitation.expires_at(config);
            EncodableCrateOwnerInvitation::from(invitation, inviter_name, crate_name, expires_at)
        })
        .collect();

    // Turn `User` list into `EncodablePublicUser` list
    let users = users
        .into_iter()
        .map(|(_, user)| EncodablePublicUser::from(user))
        .collect();

    #[derive(Serialize)]
    struct R {
        crate_owner_invitations: Vec<EncodableCrateOwnerInvitation>,
        users: Vec<EncodablePublicUser>,
    }
    Ok(req.json(&R {
        crate_owner_invitations,
        users,
    }))
}

#[derive(Deserialize)]
struct OwnerInvitation {
    crate_owner_invite: InvitationResponse,
}

/// Handles the `PUT /me/crate_owner_invitations/:crate_id` route.
pub fn handle_invite(req: &mut dyn RequestExt) -> EndpointResult {
    let mut body = String::new();
    req.body().read_to_string(&mut body)?;

    let crate_invite: OwnerInvitation =
        serde_json::from_str(&body).map_err(|_| bad_request("invalid json request"))?;

    let crate_invite = crate_invite.crate_owner_invite;
    let user_id = req.authenticate()?.user_id();
    let conn = &*req.db_conn()?;
    let config = &req.app().config;

    let invitation = CrateOwnerInvitation::find_by_id(user_id, crate_invite.crate_id, conn)?;
    if crate_invite.accepted {
        invitation.accept(conn, config)?;
    } else {
        invitation.decline(conn)?;
    }

    #[derive(Serialize)]
    struct R {
        crate_owner_invitation: InvitationResponse,
    }
    Ok(req.json(&R {
        crate_owner_invitation: crate_invite,
    }))
}

/// Handles the `PUT /me/crate_owner_invitations/accept/:token` route.
pub fn handle_invite_with_token(req: &mut dyn RequestExt) -> EndpointResult {
    let config = &req.app().config;
    let conn = req.db_conn()?;
    let req_token = &req.params()["token"];

    let invitation = CrateOwnerInvitation::find_by_token(req_token, &conn)?;
    let crate_id = invitation.crate_id;
    invitation.accept(&conn, config)?;

    #[derive(Serialize)]
    struct R {
        crate_owner_invitation: InvitationResponse,
    }
    Ok(req.json(&R {
        crate_owner_invitation: InvitationResponse {
            crate_id,
            accepted: true,
        },
    }))
}
<commit_msg>clarify existing crate_owner_invitations APIs are in the v1 namespace<commit_after>use super::frontend_prelude::*;

use crate::models::{CrateOwnerInvitation, User};
use crate::schema::{crate_owner_invitations, crates, users};
use crate::views::{EncodableCrateOwnerInvitation, EncodablePublicUser, InvitationResponse};
use diesel::dsl::any;
use std::collections::HashMap;

/// Handles the `GET /api/v1/me/crate_owner_invitations` route.
pub fn list(req: &mut dyn RequestExt) -> EndpointResult {
    // Ensure that the user is authenticated
    let user = req.authenticate()?.forbid_api_token_auth()?.user();

    // Load all pending invitations for the user
    let conn = &*req.db_read_only()?;
    let crate_owner_invitations: Vec<CrateOwnerInvitation> = crate_owner_invitations::table
        .filter(crate_owner_invitations::invited_user_id.eq(user.id))
        .load(&*conn)?;

    // Make a list of all related users
    let user_ids: Vec<_> = crate_owner_invitations
        .iter()
        .map(|invitation| invitation.invited_by_user_id)
        .collect();

    // Load all related users
    let users: Vec<User> = users::table
        .filter(users::id.eq(any(user_ids)))
        .load(conn)?;

    let users: HashMap<i32, User> = users.into_iter().map(|user| (user.id, user)).collect();

    // Make a list of all related crates
    let crate_ids: Vec<_> = crate_owner_invitations
        .iter()
        .map(|invitation| invitation.crate_id)
        .collect();

    // Load all related crates
    let crates: Vec<_> = crates::table
        .select((crates::id, crates::name))
        .filter(crates::id.eq(any(crate_ids)))
        .load(conn)?;

    let crates: HashMap<i32, String> = crates.into_iter().collect();

    // Turn `CrateOwnerInvitation` list into `EncodableCrateOwnerInvitation` list
    let config = &req.app().config;
    let crate_owner_invitations = crate_owner_invitations
        .into_iter()
        .filter(|i| !i.is_expired(config))
        .map(|invitation| {
            let inviter_id = invitation.invited_by_user_id;
            let inviter_name = users
                .get(&inviter_id)
                .map(|user| user.gh_login.clone())
                .unwrap_or_default();

            let crate_name = crates
                .get(&invitation.crate_id)
                .cloned()
                .unwrap_or_else(|| String::from("(unknown crate name)"));

            let expires_at = invitation.expires_at(config);
            EncodableCrateOwnerInvitation::from(invitation, inviter_name, crate_name, expires_at)
        })
        .collect();

    // Turn `User` list into `EncodablePublicUser` list
    let users = users
        .into_iter()
        .map(|(_, user)| EncodablePublicUser::from(user))
        .collect();

    #[derive(Serialize)]
    struct R {
        crate_owner_invitations: Vec<EncodableCrateOwnerInvitation>,
        users: Vec<EncodablePublicUser>,
    }
    Ok(req.json(&R {
        crate_owner_invitations,
        users,
    }))
}

#[derive(Deserialize)]
struct OwnerInvitation {
    crate_owner_invite: InvitationResponse,
}

/// Handles the `PUT /api/v1/me/crate_owner_invitations/:crate_id` route.
pub fn handle_invite(req: &mut dyn RequestExt) -> EndpointResult {
    let mut body = String::new();
    req.body().read_to_string(&mut body)?;

    let crate_invite: OwnerInvitation =
        serde_json::from_str(&body).map_err(|_| bad_request("invalid json request"))?;

    let crate_invite = crate_invite.crate_owner_invite;
    let user_id = req.authenticate()?.user_id();
    let conn = &*req.db_conn()?;
    let config = &req.app().config;

    let invitation = CrateOwnerInvitation::find_by_id(user_id, crate_invite.crate_id, conn)?;
    if crate_invite.accepted {
        invitation.accept(conn, config)?;
    } else {
        invitation.decline(conn)?;
    }

    #[derive(Serialize)]
    struct R {
        crate_owner_invitation: InvitationResponse,
    }
    Ok(req.json(&R {
        crate_owner_invitation: crate_invite,
    }))
}

/// Handles the `PUT /api/v1/me/crate_owner_invitations/accept/:token` route.
pub fn handle_invite_with_token(req: &mut dyn RequestExt) -> EndpointResult {
    let config = &req.app().config;
    let conn = req.db_conn()?;
    let req_token = &req.params()["token"];

    let invitation = CrateOwnerInvitation::find_by_token(req_token, &conn)?;
    let crate_id = invitation.crate_id;
    invitation.accept(&conn, config)?;

    #[derive(Serialize)]
    struct R {
        crate_owner_invitation: InvitationResponse,
    }
    Ok(req.json(&R {
        crate_owner_invitation: InvitationResponse {
            crate_id,
            accepted: true,
        },
    }))
}
<|endoftext|>


<commit_before>use ast;
use ast::visit::Visitor;
use class::*;
use ctxt::{Context, Fct, FctGen, FctId, FctKind};
use error::msg::Msg;
use lexer::position::Position;
use mem;
use semck;
use ty::BuiltinType;

pub fn check<'a, 'ast>(ctxt: &mut Context<'a, 'ast>) {
    let mut clsck = ClsDefCheck {
        ctxt: ctxt,
        ast: ctxt.ast,
        cls_id: None,
    };

    clsck.check();
}

struct ClsDefCheck<'x, 'a : 'x, 'ast: 'a> {
    ctxt: &'x mut Context<'a, 'ast>,
    ast: &'ast ast::Ast,
    cls_id: Option<ClassId>,
}

impl<'x, 'a, 'ast> ClsDefCheck<'x, 'a, 'ast> {
    fn check(&mut self) {
        self.visit_ast(self.ast);
    }

    fn cls(&self) -> &Class<'ast> {
        self.ctxt.cls_by_id(self.cls_id.unwrap())
    }

    fn cls_mut(&mut self) -> &mut Class<'ast> {
        self.ctxt.cls_by_id_mut(self.cls_id.unwrap())
    }

    fn add_ctor(&mut self) {
        let params = self.cls().props.iter().map(|p| p.ty).collect();

        let fct = {
            let cls = self.cls();

            Fct {
                id: FctId(0),
                name: cls.name,
                owner_class: Some(cls.id),
                params_types: params,
                return_type: BuiltinType::Class(cls.id),
                ctor: true,
                kind: FctKind::Gen(FctGen::new()),
            }
        };

        let fctid = self.ctxt.add_fct(fct);
        self.cls_mut().ctor = fctid;
    }
}

impl<'x, 'a, 'ast> Visitor<'ast> for ClsDefCheck<'x, 'a, 'ast> {
    fn visit_class(&mut self, c: &'ast ast::Class) {
        self.cls_id = Some(*self.ctxt.cls_defs.get(&c.id).unwrap());

        for p in &c.params {
            self.visit_prop(p);
        }

        self.add_ctor();
    }

    fn visit_prop(&mut self, p: &'ast ast::Param) {
        let ty = semck::read_type(self.ctxt, &p.data_type);

        for prop in &self.cls().props {
            if prop.name == p.name {
                let name = self.ctxt.interner.str(p.name).to_string();
                report(self.ctxt, p.pos, Msg::ShadowProp(name));
            }
        }

        let mut class = self.cls_mut();

        let offset = if ty.size() > 0 {
            mem::align_i32(class.size, ty.size())
        } else {
            class.size
        };

        let prop = Prop {
            name: p.name,
            ty: ty,
            offset: offset,
        };

        class.size = offset + ty.size();
        class.props.push(prop);
    }
}

fn report(ctxt: &Context, pos: Position, msg: Msg) {
    ctxt.diag.borrow_mut().report(pos, msg);
}

#[cfg(test)]
mod tests {
    use error::msg::Msg;
    use mem;
    use semck::tests::*;

    fn class_size(code: &'static str) -> i32 {
        ok_with_test(code, |ctxt| {
            ctxt.classes[0].size
        })
    }

    #[test]
    fn test_class_size() {
        assert_eq!(mem::ptr_width(), class_size("class Foo"));
        assert_eq!(mem::ptr_width() + 4, class_size("class Foo(a: int)"));
        assert_eq!(2 * mem::ptr_width(), class_size("class Foo(a: Str)"));
    }

    #[test]
    fn test_class_definition() {
        ok("class Foo");
        ok("class Foo()");
        ok("class Foo(a: int)");
        ok("class Foo(a: int, b:int)");
        ok("class Foo(a: Foo)");
        ok("class Foo(a: Bar) class Bar");
        err("class Foo(a: Unknown)", pos(1, 14), Msg::UnknownType("Unknown".to_string()));
        err("class Foo(a: int, a: int)", pos(1, 19), Msg::ShadowProp("a".to_string()));
    }
}
<commit_msg>clsdefck: moved params into condition<commit_after>use ast;
use ast::visit::Visitor;
use class::*;
use ctxt::{Context, Fct, FctGen, FctId, FctKind};
use error::msg::Msg;
use lexer::position::Position;
use mem;
use semck;
use ty::BuiltinType;

pub fn check<'a, 'ast>(ctxt: &mut Context<'a, 'ast>) {
    let mut clsck = ClsDefCheck {
        ctxt: ctxt,
        ast: ctxt.ast,
        cls_id: None,
    };

    clsck.check();
}

struct ClsDefCheck<'x, 'a : 'x, 'ast: 'a> {
    ctxt: &'x mut Context<'a, 'ast>,
    ast: &'ast ast::Ast,
    cls_id: Option<ClassId>,
}

impl<'x, 'a, 'ast> ClsDefCheck<'x, 'a, 'ast> {
    fn check(&mut self) {
        self.visit_ast(self.ast);
    }

    fn cls(&self) -> &Class<'ast> {
        self.ctxt.cls_by_id(self.cls_id.unwrap())
    }

    fn cls_mut(&mut self) -> &mut Class<'ast> {
        self.ctxt.cls_by_id_mut(self.cls_id.unwrap())
    }

    fn add_ctor(&mut self) {
        let fct = {
            let cls = self.cls();

            Fct {
                id: FctId(0),
                name: cls.name,
                owner_class: Some(cls.id),
                params_types: cls.props.iter().map(|p| p.ty).collect(),
                return_type: BuiltinType::Class(cls.id),
                ctor: true,
                kind: FctKind::Gen(FctGen::new()),
            }
        };

        let fctid = self.ctxt.add_fct(fct);
        self.cls_mut().ctor = fctid;
    }
}

impl<'x, 'a, 'ast> Visitor<'ast> for ClsDefCheck<'x, 'a, 'ast> {
    fn visit_class(&mut self, c: &'ast ast::Class) {
        self.cls_id = Some(*self.ctxt.cls_defs.get(&c.id).unwrap());

        for p in &c.params {
            self.visit_prop(p);
        }

        self.add_ctor();
    }

    fn visit_prop(&mut self, p: &'ast ast::Param) {
        let ty = semck::read_type(self.ctxt, &p.data_type);

        for prop in &self.cls().props {
            if prop.name == p.name {
                let name = self.ctxt.interner.str(p.name).to_string();
                report(self.ctxt, p.pos, Msg::ShadowProp(name));
            }
        }

        let mut class = self.cls_mut();

        let offset = if ty.size() > 0 {
            mem::align_i32(class.size, ty.size())
        } else {
            class.size
        };

        let prop = Prop {
            name: p.name,
            ty: ty,
            offset: offset,
        };

        class.size = offset + ty.size();
        class.props.push(prop);
    }
}

fn report(ctxt: &Context, pos: Position, msg: Msg) {
    ctxt.diag.borrow_mut().report(pos, msg);
}

#[cfg(test)]
mod tests {
    use error::msg::Msg;
    use mem;
    use semck::tests::*;

    fn class_size(code: &'static str) -> i32 {
        ok_with_test(code, |ctxt| {
            ctxt.classes[0].size
        })
    }

    #[test]
    fn test_class_size() {
        assert_eq!(mem::ptr_width(), class_size("class Foo"));
        assert_eq!(mem::ptr_width() + 4, class_size("class Foo(a: int)"));
        assert_eq!(2 * mem::ptr_width(), class_size("class Foo(a: Str)"));
    }

    #[test]
    fn test_class_definition() {
        ok("class Foo");
        ok("class Foo()");
        ok("class Foo(a: int)");
        ok("class Foo(a: int, b:int)");
        ok("class Foo(a: Foo)");
        ok("class Foo(a: Bar) class Bar");
        err("class Foo(a: Unknown)", pos(1, 14), Msg::UnknownType("Unknown".to_string()));
        err("class Foo(a: int, a: int)", pos(1, 19), Msg::ShadowProp("a".to_string()));
    }
}
<|endoftext|>