<commit_before>use bob;

fn process_response_case(phrase: &str, expected_response: &str) {
    assert_eq!(bob::reply(phrase), expected_response);
}

#[test]
fn test_stating_something() {
    process_response_case("Tom-ay-to, tom-aaaah-to.", "Whatever.");
}

#[test]
#[ignore]
fn test_shouting() {
    process_response_case("WATCH OUT!", "Whoa, chill out!");
}

#[test]
#[ignore]
fn test_shouting_gibberish() {
    process_response_case("FCECDFCAAB", "Whoa, chill out!");
}

#[test]
#[ignore]
fn test_asking_a_question() {
    process_response_case("Does this cryogenic chamber make me look fat?", "Sure.");
}

#[test]
#[ignore]
fn test_asking_a_numeric_question() {
    process_response_case("You are, what, like 15?", "Sure.");
}

#[test]
#[ignore]
fn test_asking_gibberish() {
    process_response_case("fffbbcbeab?", "Sure.");
}

#[test]
#[ignore]
fn test_talking_forcefully() {
    process_response_case("Let's go make out behind the gym!", "Whatever.");
}

#[test]
#[ignore]
fn test_using_acronyms_in_regular_speech() {
    process_response_case("It's OK if you don't want to go to the DMV.", "Whatever.");
}

#[test]
#[ignore]
fn test_forceful_question() {
    process_response_case(
        "WHAT THE HELL WERE YOU THINKING?",
        "Calm down, I know what I'm doing!",
    );
}

#[test]
#[ignore]
fn test_shouting_numbers() {
    process_response_case("1, 2, 3 GO!", "Whoa, chill out!");
}

#[test]
#[ignore]
fn test_no_letters() {
    process_response_case("1, 2, 3", "Whatever.");
}

#[test]
#[ignore]
fn test_question_with_no_letters() {
    process_response_case("4?", "Sure.");
}

#[test]
#[ignore]
fn test_shouting_with_special_characters() {
    process_response_case(
        "ZOMG THE %^*@#$(*^ ZOMBIES ARE COMING!!11!!1!",
        "Whoa, chill out!",
    );
}

#[test]
#[ignore]
fn test_shouting_with_no_exclamation_mark() {
    process_response_case("I HATE THE DMV", "Whoa, chill out!");
}

#[test]
#[ignore]
fn test_statement_containing_question_mark() {
    process_response_case("Ending with ? means a question.", "Whatever.");
}

#[test]
#[ignore]
fn test_nonletters_with_question() {
    process_response_case(":) ?", "Sure.");
}

#[test]
#[ignore]
fn test_prattling_on() {
    process_response_case("Wait! Hang on. Are you going to be OK?", "Sure.");
}

#[test]
#[ignore]
fn test_silence() {
    process_response_case("", "Fine. Be that way!");
}

#[test]
#[ignore]
fn test_prolonged_silence() {
    process_response_case("          ", "Fine. Be that way!");
}

#[test]
#[ignore]
fn test_alternate_silence() {
    process_response_case("\t\t\t\t\t\t\t\t\t\t", "Fine. Be that way!");
}

#[test]
#[ignore]
fn test_multiple_line_question() {
    process_response_case(
        "\nDoes this cryogenic chamber make me look fat?\nNo.",
        "Whatever.",
    );
}

#[test]
#[ignore]
fn test_starting_with_whitespace() {
    process_response_case("         hmmmmmmm...", "Whatever.");
}

#[test]
#[ignore]
fn test_ending_with_whitespace() {
    process_response_case("Okay if like my  spacebar  quite a bit?   ", "Sure.");
}

#[test]
#[ignore]
fn test_other_whitespace() {
    process_response_case("\n\r \t", "Fine. Be that way!");
}

#[test]
#[ignore]
fn test_nonquestion_ending_with_whitespace() {
    process_response_case(
        "This is a statement ending with whitespace      ",
        "Whatever.",
    );
}

//UPDATED
#[test]
#[ignore]
/// silence
fn test_silence() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", ""); hm}, "Fine. Be that way!");
}

//UPDATED
#[test]
#[ignore]
/// other whitespace
fn test_other_whitespace() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "

 	"); hm}, "Fine. Be that way!");
}

//UPDATED
#[test]
#[ignore]
/// talking forcefully
fn test_talking_forcefully() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "Hi there!"); hm}, "Whatever.");
}

//UPDATED
#[test]
#[ignore]
/// non-question ending with whitespace
fn test_nonquestion_ending_with_whitespace() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "This is a statement ending with whitespace      "); hm}, "Whatever.");
}

//UPDATED
#[test]
#[ignore]
/// shouting numbers
fn test_shouting_numbers() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "1, 2, 3 GO!"); hm}, "Whoa, chill out!");
}

//UPDATED
#[test]
#[ignore]
/// shouting with no exclamation mark
fn test_shouting_with_no_exclamation_mark() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "I HATE THE DENTIST"); hm}, "Whoa, chill out!");
}

//UPDATED
#[test]
#[ignore]
/// shouting gibberish
fn test_shouting_gibberish() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "FCECDFCAAB"); hm}, "Whoa, chill out!");
}

//UPDATED
#[test]
#[ignore]
/// prattling on
fn test_prattling_on() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "Wait! Hang on. Are you going to be OK?"); hm}, "Sure.");
}

//UPDATED
#[test]
#[ignore]
/// starting with whitespace
fn test_starting_with_whitespace() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "         hmmmmmmm..."); hm}, "Whatever.");
}

//UPDATED
#[test]
#[ignore]
/// asking gibberish
fn test_asking_gibberish() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "fffbbcbeab?"); hm}, "Sure.");
}

//UPDATED
#[test]
#[ignore]
/// alternate silence
fn test_alternate_silence() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "										"); hm}, "Fine. Be that way!");
}

//UPDATED
#[test]
#[ignore]
/// ending with whitespace
fn test_ending_with_whitespace() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "Okay if like my  spacebar  quite a bit?   "); hm}, "Sure.");
}

//UPDATED
#[test]
#[ignore]
/// no letters
fn test_no_letters() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "1, 2, 3"); hm}, "Whatever.");
}

//UPDATED
#[test]
#[ignore]
/// shouting with special characters
fn test_shouting_with_special_characters() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "ZOMG THE %^*@#$(*^ ZOMBIES ARE COMING!!11!!1!"); hm}, "Whoa, chill out!");
}

//UPDATED
#[test]
#[ignore]
/// stating something
fn test_stating_something() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "Tom-ay-to, tom-aaaah-to."); hm}, "Whatever.");
}

//UPDATED
#[test]
#[ignore]
/// asking a numeric question
fn test_asking_a_numeric_question() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "You are, what, like 15?"); hm}, "Sure.");
}

//UPDATED
#[test]
#[ignore]
/// non-letters with question
fn test_nonletters_with_question() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", ":) ?"); hm}, "Sure.");
}

//UPDATED
#[test]
#[ignore]
/// question with no letters
fn test_question_with_no_letters() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "4?"); hm}, "Sure.");
}

//UPDATED
#[test]
#[ignore]
/// prolonged silence
fn test_prolonged_silence() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "          "); hm}, "Fine. Be that way!");
}

//UPDATED
#[test]
#[ignore]
/// multiple line question
fn test_multiple_line_question() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "
Does this cryogenic chamber make me look fat?
No."); hm}, "Whatever.");
}

//UPDATED
#[test]
#[ignore]
/// using acronyms in regular speech
fn test_using_acronyms_in_regular_speech() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "It's OK if you don't want to go work for NASA."); hm}, "Whatever.");
}

//UPDATED
#[test]
#[ignore]
/// statement containing question mark
fn test_statement_containing_question_mark() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "Ending with ? means a question."); hm}, "Whatever.");
}

//UPDATED
#[test]
#[ignore]
/// shouting
fn test_shouting() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "WATCH OUT!"); hm}, "Whoa, chill out!");
}

//UPDATED
#[test]
#[ignore]
/// asking a question
fn test_asking_a_question() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "Does this cryogenic chamber make me look fat?"); hm}, "Sure.");
}

//UPDATED
#[test]
#[ignore]
/// forceful question
fn test_forceful_question() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "WHAT'S GOING ON?"); hm}, "Calm down, I know what I'm doing!");
}

<commit_msg>Fixes #922<commit_after>

//NEW
#[test]
#[ignore]
/// ending with whitespace
fn test_ending_with_whitespace() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "Okay if like my  spacebar  quite a bit?   "); hm}, "Sure.");
}

//NEW
#[test]
#[ignore]
/// shouting numbers
fn test_shouting_numbers() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "1, 2, 3 GO!"); hm}, "Whoa, chill out!");
}

//NEW
#[test]
#[ignore]
/// other whitespace
fn test_other_whitespace() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "

 	"); hm}, "Fine. Be that way!");
}

//NEW
#[test]
#[ignore]
/// shouting with special characters
fn test_shouting_with_special_characters() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "ZOMG THE %^*@#$(*^ ZOMBIES ARE COMING!!11!!1!"); hm}, "Whoa, chill out!");
}

//NEW
#[test]
#[ignore]
/// talking forcefully
fn test_talking_forcefully() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "Hi there!"); hm}, "Whatever.");
}

//NEW
#[test]
#[ignore]
/// prattling on
fn test_prattling_on() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "Wait! Hang on. Are you going to be OK?"); hm}, "Sure.");
}

//NEW
#[test]
#[ignore]
/// forceful question
fn test_forceful_question() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "WHAT'S GOING ON?"); hm}, "Calm down, I know what I'm doing!");
}

//NEW
#[test]
#[ignore]
/// shouting with no exclamation mark
fn test_shouting_with_no_exclamation_mark() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "I HATE THE DENTIST"); hm}, "Whoa, chill out!");
}

//NEW
#[test]
#[ignore]
/// asking gibberish
fn test_asking_gibberish() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "fffbbcbeab?"); hm}, "Sure.");
}

//NEW
#[test]
#[ignore]
/// question with no letters
fn test_question_with_no_letters() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "4?"); hm}, "Sure.");
}

//NEW
#[test]
#[ignore]
/// no letters
fn test_no_letters() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "1, 2, 3"); hm}, "Whatever.");
}

//NEW
#[test]
#[ignore]
/// statement containing question mark
fn test_statement_containing_question_mark() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "Ending with ? means a question."); hm}, "Whatever.");
}

//NEW
#[test]
#[ignore]
/// multiple line question
fn test_multiple_line_question() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "
Does this cryogenic chamber make me look fat?
No."); hm}, "Whatever.");
}

//NEW
#[test]
#[ignore]
/// non-question ending with whitespace
fn test_nonquestion_ending_with_whitespace() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "This is a statement ending with whitespace      "); hm}, "Whatever.");
}

//NEW
#[test]
#[ignore]
/// shouting
fn test_shouting() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "WATCH OUT!"); hm}, "Whoa, chill out!");
}

//NEW
#[test]
#[ignore]
/// non-letters with question
fn test_nonletters_with_question() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", ":) ?"); hm}, "Sure.");
}

//NEW
#[test]
#[ignore]
/// stating something
fn test_stating_something() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "Tom-ay-to, tom-aaaah-to."); hm}, "Whatever.");
}

//NEW
#[test]
#[ignore]
/// shouting gibberish
fn test_shouting_gibberish() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "FCECDFCAAB"); hm}, "Whoa, chill out!");
}

//NEW
#[test]
#[ignore]
/// asking a question
fn test_asking_a_question() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "Does this cryogenic chamber make me look fat?"); hm}, "Sure.");
}

//NEW
/// Process a single test case for the property `response`
///
/// All cases for the `response` property are implemented
/// in terms of this function.
///
/// Note that you'll need to both name the expected transform which
/// the student needs to write, and name the types of the inputs and outputs.
/// While rustc _may_ be able to handle things properly given a working example,
/// students will face confusing errors if the `I` and `O` types are not concrete.
///
fn process_response_case<I, O>(input: I, expected: O) {
//  typical implementation:
//  assert_eq!(
//      student_response_func(input),
//      expected
//  )
    unimplemented!()
}

//NEW
#[test]
#[ignore]
/// asking a numeric question
fn test_asking_a_numeric_question() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "You are, what, like 15?"); hm}, "Sure.");
}

//NEW
#[test]
#[ignore]
/// silence
fn test_silence() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", ""); hm}, "Fine. Be that way!");
}

//NEW
#[test]
#[ignore]
/// starting with whitespace
fn test_starting_with_whitespace() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "         hmmmmmmm..."); hm}, "Whatever.");
}

//NEW
#[test]
#[ignore]
/// using acronyms in regular speech
fn test_using_acronyms_in_regular_speech() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "It's OK if you don't want to go work for NASA."); hm}, "Whatever.");
}

//NEW
#[test]
#[ignore]
/// alternate silence
fn test_alternate_silence() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "										"); hm}, "Fine. Be that way!");
}

//NEW
#[test]
#[ignore]
/// prolonged silence
fn test_prolonged_silence() {
process_response_case({let mut hm = ::std::collections::HashMap::new(); hm.insert("heyBob", "          "); hm}, "Fine. Be that way!");
}

<|endoftext|>


<commit_before>//! Background layer rendering

use super::{Ppu, Rgb};

/// Collected background settings
struct BgSettings {
    /// Mosaic pixel size (1-16). 1 = Normal pixels.
    /// FIXME: I think there's a difference between disabled and enabled with 1x1 mosaic size in
    /// some modes (highres presumably)
    #[allow(dead_code)] // FIXME NYI
    mosaic: u8,
    /// Tilemap word address in VRAM
    /// "Starting at the tilemap address, the first $800 bytes are for tilemap A. Then come the
    /// $800 bytes for B, then C then D."
    tilemap_word_addr: u16,
    /// When `true`, this BGs tilemaps are repeated sideways
    tilemap_mirror_h: bool,
    /// When `true`, this BGs tilemaps are repeated downwards
    tilemap_mirror_v: bool,
    /// If `true`, BG tiles are 16x16 pixels. If `false`, they are 8x8 pixels.
    tile_size_16: bool,
    /// Character Data start address in VRAM
    chr_addr: u16,
    /// Horizontal scroll offset. Moves the BG layer to the left by some number of pixels.
    hofs: u16,
    /// Vertical scroll offset. Moves the BG layer up by some number of pixels.
    vofs: u16,
}

/// Unpacked tilemap entry for internal (rendering) use.
///
/// A tilemap entry is 2 bytes large and contains informations about a single background layer tile.
struct TilemapEntry {
    #[allow(dead_code)] // FIXME
    vflip: bool,
    #[allow(dead_code)]
    hflip: bool,
    /// Priority bit (0-1)
    priority: u8,
    /// Tile palette (0-7)
    palette: u8,
    /// Index into the character/tile data, where the actual tile character data is stored in
    /// bitplanes (10 bits)
    tile_number: u16,
}

impl Ppu {
    /// Determines whether the given BG layer (1-4) is enabled
    fn bg_enabled(&self, bg: u8) -> bool { self.tm & (1 << (bg - 1)) != 0 }

    /// Reads the tilemap entry at the given VRAM word address.
    ///     vhopppcc cccccccc (high, low)
    ///     v/h        = Vertical/Horizontal flip this tile.
    ///     o          = Tile priority.
    ///     ppp        = Tile palette base.
    ///     cccccccccc = Tile number.
    fn tilemap_entry(&self, word_address: u16) -> TilemapEntry {
        let byte_address = word_address << 1;
        let lo = self.vram[byte_address];
        let hi = self.vram[byte_address + 1];

        TilemapEntry {
            vflip: hi & 0x80 != 0,
            hflip: hi & 0x40 != 0,
            priority: (hi & 0x20) >> 5,
            palette: (hi & 0x1c) >> 2,
            tile_number: ((hi as u16 & 0x03) << 8) | lo as u16,
        }
    }

    /// Collects properties of a background layer
    fn bg_settings(&self, bg: u8) -> BgSettings {
        // The BGxSC register for our background layer
        let bgsc = match bg {
            1 => self.bg1sc,
            2 => self.bg2sc,
            3 => self.bg3sc,
            4 => self.bg4sc,
            _ => unreachable!(),
        };
        // Chr (Tileset, not Tilemap) start (word?) address >> 12
        let chr = match bg {
            1 => self.bg12nba & 0x0f,
            2 => (self.bg12nba & 0xf0) >> 4,
            3 => self.bg34nba & 0x0f,
            4 => (self.bg34nba & 0xf0) >> 4,
            _ => unreachable!(),
        };
        let (hofs, vofs) = match bg {
            1 => (self.bg1hofs, self.bg1vofs),
            2 => (self.bg2hofs, self.bg2vofs),
            3 => (self.bg3hofs, self.bg3vofs),
            4 => (self.bg4hofs, self.bg4vofs),
            _ => unreachable!(),
        };

        BgSettings {
            mosaic: if self.mosaic & (1 << (bg-1)) == 0 {
                1
            } else {
                ((self.mosaic & 0xf0) >> 4) + 1
            },
            // FIXME: This looks more like the byte address to me!
            tilemap_word_addr: ((bgsc as u16 & 0xfc) >> 2) << 10,
            tilemap_mirror_h: bgsc & 0b01 == 0, // inverted bit value
            tilemap_mirror_v: bgsc & 0b10 == 0, // inverted bit value
            tile_size_16: match self.bg_mode() {
                // "If the BG character size for BG1/BG2/BG3/BG4 bit is set, then the BG is made of
                // 16x16 tiles. Otherwise, 8x8 tiles are used. However, note that Modes 5 and 6
                // always use 16-pixel wide tiles, and Mode 7 always uses 8x8 tiles."
                5 | 6 => true,
                7 => false,
                _ => {
                    // BGMODE: `4321----` (`-` = not relevant here) - Use 16x16 tiles?
                    self.bgmode & (1 << (bg + 3)) != 0
                }
            },
            chr_addr: (chr as u16) << 12,
            hofs: hofs,
            vofs: vofs,
        }
    }

    /// Returns the number of color bits in the given BG layer in the current BG mode (2, 4, 7 or
    /// 8). To get the number of colors, use `1 << color_bits_for_bg`.
    ///
    /// Table of colors for BG layers (not what this function returns!). `X` denotes a BG for
    /// offset-per-tile data.
    /// ```text
    /// Mode    # Colors for BG
    ///          1   2   3   4
    /// ======---=---=---=---=
    /// 0        4   4   4   4
    /// 1       16  16   4   -
    /// 2       16  16   X   -
    /// 3      256  16   -   -
    /// 4      256   4   X   -
    /// 5       16   4   -   -
    /// 6       16   -   X   -
    /// 7      256   -   -   -
    /// 7EXTBG 256 128   -   -
    /// ```
    fn color_bits_for_bg(&self, bg: u8) -> u8 {
        match self.bg_mode() {
            0 => 2,
            1 => match bg {
                1 | 2 => 4,
                3 => 2,
                _ => unreachable!(),
            },
            2 => 4,
            3 => match bg {
                1 => 8,
                2 => 4,
                _ => unreachable!(),
            },
            4 => match bg {
                1 => 8,
                2 => 2,
                _ => unreachable!(),
            },
            5 => match bg {
                1 => 4,
                2 => 2,
                _ => unreachable!(),
            },
            6 => 4,
            7 => panic!("NYI: color_count_for_bg for mode 7"),   // (make sure to handle EXTBG)
            _ => unreachable!(),
        }
    }

    /// Calculates the palette base index for a tile in the given background layer. `palette_num`
    /// is the palette number stored in the tilemap entry (the 3 `p` bits).
    fn palette_base_for_bg_tile(&self, bg: u8, palette_num: u8) -> u8 {
        debug_assert!(bg >= 1 && bg <= 4);
        match self.bg_mode() {
            0 => palette_num * 4 + (bg - 1) * 32,
            1 | 5 => palette_num * (1 << self.color_bits_for_bg(bg) as u8),
            2 => palette_num * 16,
            3 => match bg {
                1 => 0,
                2 => palette_num * 16,
                _ => unreachable!(),    // no BG3/4
            },
            4 => match bg {
                1 => 0,
                2 => palette_num * 4,
                _ => unreachable!(),    // no BG3/4
            },
            6 => palette_num * 16,      // BG1 has 16 colors
            7 => panic!("NYI: palette_base_for_bg_tile for mode 7"),
            _ => unreachable!(),
        }
    }

    /// Lookup the color of the given background layer (1-4) at the current pixel, using the given
    /// priority (0-1) only. This will also scroll backgrounds accordingly and apply color math.
    ///
    /// Returns `None` if the pixel is transparent, `Some(Rgb)` otherwise.
    pub fn lookup_bg_color(&self, bg_num: u8, prio: u8) -> Option<Rgb> {
        debug_assert!(bg_num >= 1 && bg_num <= 4);
        debug_assert!(prio == 0 || prio == 1);
        if !self.bg_enabled(bg_num) { return None }

        // Apply BG scrolling and get the tile coordinates
        // FIXME Apply mosaic filter
        // FIXME Fix this: "Note that many games will set their vertical scroll values to -1 rather
        // than 0. This is because the SNES loads OBJ data for each scanline during the previous
        // scanline. The very first line, though, wouldn’t have any OBJ data loaded! So the SNES
        // doesn’t actually output scanline 0, although it does everything to render it. These
        // games want the first line of their tilemap to be the first line output, so they set
        // their VOFS registers in this manner. Note that an interlace screen needs -2 rather than
        // -1 to properly correct for the missing line 0 (and an emulator would need to add 2
        // instead of 1 to account for this)."
        let x = self.x;
        let y = self.scanline;
        let bg = self.bg_settings(bg_num);
        let tile_size = if bg.tile_size_16 { 16 } else { 8 };
        let (hofs, vofs) = (bg.hofs, bg.vofs);
        let tile_x = x.wrapping_add(hofs) / tile_size as u16;
        let tile_y = y.wrapping_add(vofs) / tile_size as u16;
        let off_x = (x.wrapping_add(hofs) % tile_size as u16) as u8;
        let off_y = (y.wrapping_add(vofs) % tile_size as u16) as u8;
        let (sx, sy) = (!bg.tilemap_mirror_h, !bg.tilemap_mirror_v);

        // Calculate the VRAM word address, where the tilemap entry for our tile is stored
        // FIXME Check if this really is correct
        let tilemap_entry_word_address =
            bg.tilemap_word_addr |
            ((tile_y & 0x1f) << 5) |
            (tile_x & 0x1f) |
            if sy {(tile_y & 0x20) << if sx {6} else {5}} else {0} |
            if sx {(tile_x & 0x20) << 5} else {0};
        let tilemap_entry = self.tilemap_entry(tilemap_entry_word_address);
        if tilemap_entry.priority != prio { return None }

        let color_bits = self.color_bits_for_bg(bg_num);
        if color_bits == 8 {
            // can use direct color mode
            debug_assert!(self.cgwsel & 0x01 == 0, "NYI: direct color mode");
        }

        let bitplane_start_addr =
            (bg.chr_addr << 1) +
            (tilemap_entry.tile_number * 8 * color_bits as u16);   // 8 bytes per bitplane

        let palette_base = self.palette_base_for_bg_tile(bg_num, tilemap_entry.palette);
        let palette_index = self.read_chr_entry(color_bits,
                                                bitplane_start_addr,
                                                tile_size,
                                                (off_x, off_y));

        match palette_index {
            0 => None,
            _ => Some(self.lookup_color(palette_base + palette_index)),
        }
    }
}
<commit_msg>Use match better<commit_after>//! Background layer rendering

use super::{Ppu, Rgb};

/// Collected background settings
struct BgSettings {
    /// Mosaic pixel size (1-16). 1 = Normal pixels.
    /// FIXME: I think there's a difference between disabled and enabled with 1x1 mosaic size in
    /// some modes (highres presumably)
    #[allow(dead_code)] // FIXME NYI
    mosaic: u8,
    /// Tilemap word address in VRAM
    /// "Starting at the tilemap address, the first $800 bytes are for tilemap A. Then come the
    /// $800 bytes for B, then C then D."
    tilemap_word_addr: u16,
    /// When `true`, this BGs tilemaps are repeated sideways
    tilemap_mirror_h: bool,
    /// When `true`, this BGs tilemaps are repeated downwards
    tilemap_mirror_v: bool,
    /// If `true`, BG tiles are 16x16 pixels. If `false`, they are 8x8 pixels.
    tile_size_16: bool,
    /// Character Data start address in VRAM
    chr_addr: u16,
    /// Horizontal scroll offset. Moves the BG layer to the left by some number of pixels.
    hofs: u16,
    /// Vertical scroll offset. Moves the BG layer up by some number of pixels.
    vofs: u16,
}

/// Unpacked tilemap entry for internal (rendering) use.
///
/// A tilemap entry is 2 bytes large and contains informations about a single background layer tile.
struct TilemapEntry {
    #[allow(dead_code)] // FIXME
    vflip: bool,
    #[allow(dead_code)]
    hflip: bool,
    /// Priority bit (0-1)
    priority: u8,
    /// Tile palette (0-7)
    palette: u8,
    /// Index into the character/tile data, where the actual tile character data is stored in
    /// bitplanes (10 bits)
    tile_number: u16,
}

impl Ppu {
    /// Determines whether the given BG layer (1-4) is enabled
    fn bg_enabled(&self, bg: u8) -> bool { self.tm & (1 << (bg - 1)) != 0 }

    /// Reads the tilemap entry at the given VRAM word address.
    ///     vhopppcc cccccccc (high, low)
    ///     v/h        = Vertical/Horizontal flip this tile.
    ///     o          = Tile priority.
    ///     ppp        = Tile palette base.
    ///     cccccccccc = Tile number.
    fn tilemap_entry(&self, word_address: u16) -> TilemapEntry {
        let byte_address = word_address << 1;
        let lo = self.vram[byte_address];
        let hi = self.vram[byte_address + 1];

        TilemapEntry {
            vflip: hi & 0x80 != 0,
            hflip: hi & 0x40 != 0,
            priority: (hi & 0x20) >> 5,
            palette: (hi & 0x1c) >> 2,
            tile_number: ((hi as u16 & 0x03) << 8) | lo as u16,
        }
    }

    /// Collects properties of a background layer
    fn bg_settings(&self, bg: u8) -> BgSettings {
        // The BGxSC register for our background layer
        let bgsc = match bg {
            1 => self.bg1sc,
            2 => self.bg2sc,
            3 => self.bg3sc,
            4 => self.bg4sc,
            _ => unreachable!(),
        };
        // Chr (Tileset, not Tilemap) start (word?) address >> 12
        let chr = match bg {
            1 => self.bg12nba & 0x0f,
            2 => (self.bg12nba & 0xf0) >> 4,
            3 => self.bg34nba & 0x0f,
            4 => (self.bg34nba & 0xf0) >> 4,
            _ => unreachable!(),
        };
        let (hofs, vofs) = match bg {
            1 => (self.bg1hofs, self.bg1vofs),
            2 => (self.bg2hofs, self.bg2vofs),
            3 => (self.bg3hofs, self.bg3vofs),
            4 => (self.bg4hofs, self.bg4vofs),
            _ => unreachable!(),
        };

        BgSettings {
            mosaic: if self.mosaic & (1 << (bg-1)) == 0 {
                1
            } else {
                ((self.mosaic & 0xf0) >> 4) + 1
            },
            // FIXME: This looks more like the byte address to me!
            tilemap_word_addr: ((bgsc as u16 & 0xfc) >> 2) << 10,
            tilemap_mirror_h: bgsc & 0b01 == 0, // inverted bit value
            tilemap_mirror_v: bgsc & 0b10 == 0, // inverted bit value
            tile_size_16: match self.bg_mode() {
                // "If the BG character size for BG1/BG2/BG3/BG4 bit is set, then the BG is made of
                // 16x16 tiles. Otherwise, 8x8 tiles are used. However, note that Modes 5 and 6
                // always use 16-pixel wide tiles, and Mode 7 always uses 8x8 tiles."
                5 | 6 => true,
                7 => false,
                _ => {
                    // BGMODE: `4321----` (`-` = not relevant here) - Use 16x16 tiles?
                    self.bgmode & (1 << (bg + 3)) != 0
                }
            },
            chr_addr: (chr as u16) << 12,
            hofs: hofs,
            vofs: vofs,
        }
    }

    /// Returns the number of color bits in the given BG layer in the current BG mode (2, 4, 7 or
    /// 8). To get the number of colors, use `1 << color_bits_for_bg`.
    ///
    /// Table of colors for BG layers (not what this function returns!). `X` denotes a BG for
    /// offset-per-tile data.
    /// ```text
    /// Mode    # Colors for BG
    ///          1   2   3   4
    /// ======---=---=---=---=
    /// 0        4   4   4   4
    /// 1       16  16   4   -
    /// 2       16  16   X   -
    /// 3      256  16   -   -
    /// 4      256   4   X   -
    /// 5       16   4   -   -
    /// 6       16   -   X   -
    /// 7      256   -   -   -
    /// 7EXTBG 256 128   -   -
    /// ```
    fn color_bits_for_bg(&self, bg: u8) -> u8 {
        match (self.bg_mode(), bg) {
            (0, _) => 2,
            (1, 1) |
            (1, 2) => 4,
            (1, 3) => 2,
            (2, _) => 4,
            (3, 1) => 8,
            (3, 2) => 4,
            (4, 1) => 8,
            (4, 2) => 2,
            (5, 1) => 4,
            (5, 2) => 2,
            (6, _) => 4,
            (7, _) => panic!("NYI: color_count_for_bg for mode 7"),   // (make sure to handle EXTBG)
            _ => unreachable!(),
        }
    }

    /// Calculates the palette base index for a tile in the given background layer. `palette_num`
    /// is the palette number stored in the tilemap entry (the 3 `p` bits).
    fn palette_base_for_bg_tile(&self, bg: u8, palette_num: u8) -> u8 {
        debug_assert!(bg >= 1 && bg <= 4);
        match (self.bg_mode(), bg) {
            (0, _) => palette_num * 4 + (bg - 1) * 32,
            (1, _) |
            (5, _) => palette_num * (1 << self.color_bits_for_bg(bg) as u8),
            (2, _) => palette_num * 16,
            (3, 1) => 0,
            (3, 2) => palette_num * 16,
            (4, 1) => 0,
            (4, 2) => palette_num * 4,
            (6, _) => palette_num * 16, // BG1 has 16 colors
            (7, _) => panic!("NYI: palette_base_for_bg_tile for mode 7"),
            _ => unreachable!(),
        }
    }

    /// Lookup the color of the given background layer (1-4) at the current pixel, using the given
    /// priority (0-1) only. This will also scroll backgrounds accordingly and apply color math.
    ///
    /// Returns `None` if the pixel is transparent, `Some(Rgb)` otherwise.
    pub fn lookup_bg_color(&self, bg_num: u8, prio: u8) -> Option<Rgb> {
        debug_assert!(bg_num >= 1 && bg_num <= 4);
        debug_assert!(prio == 0 || prio == 1);
        if !self.bg_enabled(bg_num) { return None }

        // Apply BG scrolling and get the tile coordinates
        // FIXME Apply mosaic filter
        // FIXME Fix this: "Note that many games will set their vertical scroll values to -1 rather
        // than 0. This is because the SNES loads OBJ data for each scanline during the previous
        // scanline. The very first line, though, wouldn’t have any OBJ data loaded! So the SNES
        // doesn’t actually output scanline 0, although it does everything to render it. These
        // games want the first line of their tilemap to be the first line output, so they set
        // their VOFS registers in this manner. Note that an interlace screen needs -2 rather than
        // -1 to properly correct for the missing line 0 (and an emulator would need to add 2
        // instead of 1 to account for this)."
        let x = self.x;
        let y = self.scanline;
        let bg = self.bg_settings(bg_num);
        let tile_size = if bg.tile_size_16 { 16 } else { 8 };
        let (hofs, vofs) = (bg.hofs, bg.vofs);
        let tile_x = x.wrapping_add(hofs) / tile_size as u16;
        let tile_y = y.wrapping_add(vofs) / tile_size as u16;
        let off_x = (x.wrapping_add(hofs) % tile_size as u16) as u8;
        let off_y = (y.wrapping_add(vofs) % tile_size as u16) as u8;
        let (sx, sy) = (!bg.tilemap_mirror_h, !bg.tilemap_mirror_v);

        // Calculate the VRAM word address, where the tilemap entry for our tile is stored
        // FIXME Check if this really is correct
        let tilemap_entry_word_address =
            bg.tilemap_word_addr |
            ((tile_y & 0x1f) << 5) |
            (tile_x & 0x1f) |
            if sy {(tile_y & 0x20) << if sx {6} else {5}} else {0} |
            if sx {(tile_x & 0x20) << 5} else {0};
        let tilemap_entry = self.tilemap_entry(tilemap_entry_word_address);
        if tilemap_entry.priority != prio { return None }

        let color_bits = self.color_bits_for_bg(bg_num);
        if color_bits == 8 {
            // can use direct color mode
            debug_assert!(self.cgwsel & 0x01 == 0, "NYI: direct color mode");
        }

        let bitplane_start_addr =
            (bg.chr_addr << 1) +
            (tilemap_entry.tile_number * 8 * color_bits as u16);   // 8 bytes per bitplane

        let palette_base = self.palette_base_for_bg_tile(bg_num, tilemap_entry.palette);
        let palette_index = self.read_chr_entry(color_bits,
                                                bitplane_start_addr,
                                                tile_size,
                                                (off_x, off_y));

        match palette_index {
            0 => None,
            _ => Some(self.lookup_color(palette_base + palette_index)),
        }
    }
}
<|endoftext|>


<commit_before>use crate::context::Context;
use crate::error::RenderError;
use crate::helpers::HelperDef;
use crate::json::value::{to_json, ScopedJson};
use crate::registry::Registry;
use crate::render::{Helper, RenderContext};

use rhai::{Dynamic, Engine, INT};

use serde_json::value::Value as Json;

pub struct ScriptHelper {
    script: String,
    engine: Engine,
}

impl HelperDef for ScriptHelper {
    fn call_inner<'reg: 'rc, 'rc>(
        &self,
        h: &Helper<'reg, 'rc>,
        reg: &'reg Registry<'reg>,
        ctx: &'rc Context,
        rc: &mut RenderContext<'reg, 'rc>,
    ) -> Result<Option<ScopedJson<'reg, 'rc>>, RenderError> {
        // TODO:
        let result = self
            .engine
            .eval::<INT>(&self.script)
            .map_err(RenderError::from)?;

        Ok(Some(ScopedJson::Derived(to_json(result))))
    }
}

impl<'reg, 'rc> Into<Dynamic> for &ScopedJson<'reg, 'rc> {
    fn into(self) -> Dynamic {
        match self.as_json() {
            Json::Number(ref n) => Dynamic::from(n),
            Json::Bool(ref b) => Dynamic::from(b),
            Json::Null => Dynamic::from(()),
            Json::String(ref s) => Dynamic::from(s),
            Json::Array(ref v) => Dynamic::from(v.clone()),
            Json::Object(ref o) => Dynamic::from(o.clone()),
        }
    }
}
<commit_msg>(feat) added support for hash and param access<commit_after>use std::collections::HashMap;
use std::iter::FromIterator;

use crate::context::Context;
use crate::error::RenderError;
use crate::helpers::HelperDef;
use crate::json::value::{to_json, ScopedJson};
use crate::registry::Registry;
use crate::render::{Helper, RenderContext};

use rhai::{Dynamic, Engine, Scope};

use serde_json::value::Value as Json;

pub struct ScriptHelper {
    script: String,
    engine: Engine,
}

impl HelperDef for ScriptHelper {
    fn call_inner<'reg: 'rc, 'rc>(
        &self,
        h: &Helper<'reg, 'rc>,
        _reg: &'reg Registry<'reg>,
        _ctx: &'rc Context,
        _rc: &mut RenderContext<'reg, 'rc>,
    ) -> Result<Option<ScopedJson<'reg, 'rc>>, RenderError> {
        let params: Vec<Dynamic> = h.params().iter().map(|p| convert(p.value())).collect();
        let hash: Dynamic = HashMap::from_iter(
            h.hash()
                .iter()
                .map(|(k, v)| ((*k).to_owned(), convert(v.value()))),
        )
        .into();

        let mut scope = Scope::new();
        scope.push_dynamic("params", params.into());
        scope.push_dynamic("hash", hash);

        let result = self
            .engine
            .eval_with_scope::<Dynamic>(&mut scope, &self.script)
            .map_err(RenderError::from)?;
        let result_string = result.take_string().unwrap_or_else(|e| e.to_owned());

        Ok(Some(ScopedJson::Derived(to_json(result_string))))
    }
}

fn convert(j: &Json) -> Dynamic {
    match j {
        Json::Number(n) => Dynamic::from(n.clone()),
        Json::Bool(b) => Dynamic::from(*b),
        Json::Null => Dynamic::from(()),
        Json::String(s) => Dynamic::from(s.clone()),
        Json::Array(ref v) => Dynamic::from(v.clone()),
        Json::Object(ref o) => Dynamic::from(o.clone()),
    }
}
<|endoftext|>


<commit_before>use std;
use std::fs::File;
use std::path::Path;
use std::hash::{Hash, Hasher};
use std::collections::{HashMap, HashSet};

use serde_json;

use feed;
use errors::*;

fn get_hash<T: Hash>(t: T) -> u64 {
    let mut hasher = std::collections::hash_map::DefaultHasher::default();
    t.hash(&mut hasher);
    hasher.finish()
}

type FeedID = u64;
type SubscriberID = i64;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Feed {
    pub link: String,
    pub title: String,
    pub error_count: u32,
    pub subscribers: HashSet<SubscriberID>,
    hash_list: HashSet<u64>,
}

#[derive(Debug)]
pub struct Database {
    path: String,
    feeds: HashMap<FeedID, Feed>,
    subscribers: HashMap<SubscriberID, HashSet<FeedID>>,
}

fn gen_item_hash(item: &feed::Item) -> u64 {
    item.id
        .as_ref()
        .map(|id| get_hash(&id))
        .unwrap_or_else(|| {
            let title = item.title.as_ref().map(|s| s.as_str()).unwrap_or_default();
            let link = item.link.as_ref().map(|s| s.as_str()).unwrap_or_default();
            get_hash(format!("{}{}", title, link))
        })
}

fn truncate_hashset<T, S>(set: &mut HashSet<T, S>, u: usize)
    where T: std::hash::Hash + Clone + Eq,
          S: std::hash::BuildHasher
{
    let len = set.len();
    if len <= u {
        return;
    }
    let removed: Vec<T> = set.iter().take(u - len).map(|v| v.clone()).collect();
    for r in removed {
        set.remove(&r);
    }
}

impl Database {
    pub fn create(path: &str) -> Result<Database> {
        let feeds: HashMap<FeedID, Feed> = HashMap::new();
        let subscribers: HashMap<SubscriberID, HashSet<FeedID>> = HashMap::new();
        let mut result = Database {
            path: path.to_owned(),
            feeds: feeds,
            subscribers: subscribers,
        };

        result.save()?;

        Ok(result)
    }

    pub fn open(path: &str) -> Result<Database> {
        let p = Path::new(path);
        if p.exists() {
            let f = File::open(path).chain_err(|| ErrorKind::DatabaseOpen(path.to_owned()))?;
            let feeds_list: Vec<Feed> = serde_json::from_reader(&f).chain_err(|| ErrorKind::DatabaseFormat)?;

            let mut feeds: HashMap<FeedID, Feed> = HashMap::with_capacity(feeds_list.len());
            let mut subscribers: HashMap<SubscriberID, HashSet<FeedID>> = HashMap::new();

            for feed in feeds_list {
                let feed_id = get_hash(&feed.link);
                for subscriber in &feed.subscribers {
                    let subscribed_feeds = subscribers.entry(subscriber.to_owned())
                        .or_insert_with(|| HashSet::new());
                    subscribed_feeds.insert(feed_id);
                }
                feeds.insert(feed_id, feed);
            }

            Ok(Database {
                path: path.to_owned(),
                feeds: feeds,
                subscribers: subscribers,
            })
        } else {
            Database::create(path)
        }
    }

    pub fn get_all_feeds(&self) -> Vec<Feed> {
        self.feeds.iter().map(|(_, v)| v.clone()).collect()
    }

    pub fn get_subscribed_feeds(&self, subscriber: SubscriberID) -> Option<Vec<&Feed>> {
        self.subscribers.get(&subscriber).map(|feeds| {
            feeds.iter()
                .map(|feed_id| &self.feeds[feed_id])
                .collect()
        })
    }

    pub fn inc_error_count(&mut self, rss_link: &str) -> u32 {
        let feed_id = get_hash(rss_link);
        self.feeds
            .get_mut(&feed_id)
            .map(|feed| {
                feed.error_count += 1;
                feed.error_count
            })
            .unwrap_or_default()
    }

    pub fn is_subscribed(&self, subscriber: SubscriberID, rss_link: &str) -> bool {
        self.subscribers.get(&subscriber).map(|feeds| feeds.contains(&get_hash(rss_link))).unwrap_or(false)
    }

    pub fn subscribe(&mut self, subscriber: SubscriberID, rss_link: &str, rss: &feed::RSS) -> Result<()> {
        let feed_id = get_hash(rss_link);
        {
            let subscribed_feeds = self.subscribers.entry(subscriber).or_insert_with(|| HashSet::new());
            if !subscribed_feeds.insert(feed_id) {
                return Err(ErrorKind::AlreadySubscribed.into());
            }
        }
        {
            let feed = self.feeds.entry(feed_id).or_insert_with(|| {
                Feed {
                    link: rss_link.to_owned(),
                    title: rss.title.to_owned(),
                    error_count: 0,
                    hash_list: rss.items.iter().map(gen_item_hash).collect(),
                    subscribers: HashSet::new(),
                }
            });
            feed.subscribers.insert(subscriber);
        }
        self.save()
    }

    pub fn unsubscribe(&mut self, subscriber: SubscriberID, rss_link: &str) -> Result<Feed> {
        let feed_id = get_hash(rss_link);

        {
            let mut clear_subscriber = false;
            self.subscribers
                .get_mut(&subscriber)
                .map(|subscribed_feeds| if !subscribed_feeds.remove(&feed_id) {
                    Err::<(), Error>(ErrorKind::NotSubscribed.into())
                } else {
                    clear_subscriber = subscribed_feeds.len() == 0;
                    Ok(())
                })
                .unwrap_or(Err(ErrorKind::NotSubscribed.into()))?;
            if clear_subscriber {
                self.subscribers.remove(&subscriber);
            }
        }
        let result;
        {
            let mut clear_feed = false;
            result = self.feeds
                .get_mut(&feed_id)
                .map(|feed| if !feed.subscribers.remove(&subscriber) {
                    Err::<Feed, Error>(ErrorKind::NotSubscribed.into())
                } else {
                    clear_feed = feed.subscribers.len() == 0;
                    Ok(feed.clone())
                })
                .unwrap_or(Err(ErrorKind::NotSubscribed.into()))?;
            if clear_feed {
                self.feeds.remove(&feed_id);
            }
        }
        self.save()?;
        Ok(result)
    }

    pub fn update<'a>(&mut self, rss_link: &str, items: Vec<feed::Item>) -> Vec<feed::Item> {
        let feed_id = get_hash(rss_link);
        let mut result = Vec::new();
        let mut new_hash = Vec::new();
        let items_len = items.len();
        for item in items {
            let hash = gen_item_hash(&item);
            if !self.feeds[&feed_id].hash_list.contains(&hash) {
                result.push(item);
                new_hash.push(hash);
            }
        }
        if !new_hash.is_empty() {
            self.feeds.get_mut(&feed_id).map(|feed| {
                truncate_hashset(&mut feed.hash_list, items_len * 2 - new_hash.len());
                for hash in new_hash {
                    feed.hash_list.insert(hash);
                }
            });
        }
        self.save().unwrap_or_default();
        result
    }

    fn save(&mut self) -> Result<()> {
        let feeds_list: Vec<&Feed> = self.feeds.iter().map(|(_id, feed)| feed).collect();
        let mut file = File::create(&self.path).chain_err(|| ErrorKind::DatabaseSave(self.path.to_owned()))?;
        serde_json::to_writer(&mut file, &feeds_list).chain_err(|| ErrorKind::DatabaseSave(self.path.to_owned()))
    }
}
<commit_msg>fix a panic when truncate a hashset<commit_after>use std;
use std::fs::File;
use std::path::Path;
use std::hash::{Hash, Hasher};
use std::collections::{HashMap, HashSet};

use serde_json;

use feed;
use errors::*;

fn get_hash<T: Hash>(t: T) -> u64 {
    let mut hasher = std::collections::hash_map::DefaultHasher::default();
    t.hash(&mut hasher);
    hasher.finish()
}

type FeedID = u64;
type SubscriberID = i64;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Feed {
    pub link: String,
    pub title: String,
    pub error_count: u32,
    pub subscribers: HashSet<SubscriberID>,
    hash_list: HashSet<u64>,
}

#[derive(Debug)]
pub struct Database {
    path: String,
    feeds: HashMap<FeedID, Feed>,
    subscribers: HashMap<SubscriberID, HashSet<FeedID>>,
}

fn gen_item_hash(item: &feed::Item) -> u64 {
    item.id
        .as_ref()
        .map(|id| get_hash(&id))
        .unwrap_or_else(|| {
            let title = item.title.as_ref().map(|s| s.as_str()).unwrap_or_default();
            let link = item.link.as_ref().map(|s| s.as_str()).unwrap_or_default();
            get_hash(format!("{}{}", title, link))
        })
}

fn truncate_hashset<T, S>(set: &mut HashSet<T, S>, u: usize)
    where T: std::hash::Hash + Clone + Eq,
          S: std::hash::BuildHasher
{
    let len = set.len();
    if len <= u {
        return;
    }
    let removed: Vec<T> = set.iter().take(len - u).map(|v| v.clone()).collect();
    for r in removed {
        set.remove(&r);
    }
}

impl Database {
    pub fn create(path: &str) -> Result<Database> {
        let feeds: HashMap<FeedID, Feed> = HashMap::new();
        let subscribers: HashMap<SubscriberID, HashSet<FeedID>> = HashMap::new();
        let mut result = Database {
            path: path.to_owned(),
            feeds: feeds,
            subscribers: subscribers,
        };

        result.save()?;

        Ok(result)
    }

    pub fn open(path: &str) -> Result<Database> {
        let p = Path::new(path);
        if p.exists() {
            let f = File::open(path).chain_err(|| ErrorKind::DatabaseOpen(path.to_owned()))?;
            let feeds_list: Vec<Feed> = serde_json::from_reader(&f).chain_err(|| ErrorKind::DatabaseFormat)?;

            let mut feeds: HashMap<FeedID, Feed> = HashMap::with_capacity(feeds_list.len());
            let mut subscribers: HashMap<SubscriberID, HashSet<FeedID>> = HashMap::new();

            for feed in feeds_list {
                let feed_id = get_hash(&feed.link);
                for subscriber in &feed.subscribers {
                    let subscribed_feeds = subscribers.entry(subscriber.to_owned())
                        .or_insert_with(|| HashSet::new());
                    subscribed_feeds.insert(feed_id);
                }
                feeds.insert(feed_id, feed);
            }

            Ok(Database {
                path: path.to_owned(),
                feeds: feeds,
                subscribers: subscribers,
            })
        } else {
            Database::create(path)
        }
    }

    pub fn get_all_feeds(&self) -> Vec<Feed> {
        self.feeds.iter().map(|(_, v)| v.clone()).collect()
    }

    pub fn get_subscribed_feeds(&self, subscriber: SubscriberID) -> Option<Vec<&Feed>> {
        self.subscribers.get(&subscriber).map(|feeds| {
            feeds.iter()
                .map(|feed_id| &self.feeds[feed_id])
                .collect()
        })
    }

    pub fn inc_error_count(&mut self, rss_link: &str) -> u32 {
        let feed_id = get_hash(rss_link);
        self.feeds
            .get_mut(&feed_id)
            .map(|feed| {
                feed.error_count += 1;
                feed.error_count
            })
            .unwrap_or_default()
    }

    pub fn is_subscribed(&self, subscriber: SubscriberID, rss_link: &str) -> bool {
        self.subscribers.get(&subscriber).map(|feeds| feeds.contains(&get_hash(rss_link))).unwrap_or(false)
    }

    pub fn subscribe(&mut self, subscriber: SubscriberID, rss_link: &str, rss: &feed::RSS) -> Result<()> {
        let feed_id = get_hash(rss_link);
        {
            let subscribed_feeds = self.subscribers.entry(subscriber).or_insert_with(|| HashSet::new());
            if !subscribed_feeds.insert(feed_id) {
                return Err(ErrorKind::AlreadySubscribed.into());
            }
        }
        {
            let feed = self.feeds.entry(feed_id).or_insert_with(|| {
                Feed {
                    link: rss_link.to_owned(),
                    title: rss.title.to_owned(),
                    error_count: 0,
                    hash_list: rss.items.iter().map(gen_item_hash).collect(),
                    subscribers: HashSet::new(),
                }
            });
            feed.subscribers.insert(subscriber);
        }
        self.save()
    }

    pub fn unsubscribe(&mut self, subscriber: SubscriberID, rss_link: &str) -> Result<Feed> {
        let feed_id = get_hash(rss_link);

        {
            let mut clear_subscriber = false;
            self.subscribers
                .get_mut(&subscriber)
                .map(|subscribed_feeds| if !subscribed_feeds.remove(&feed_id) {
                    Err::<(), Error>(ErrorKind::NotSubscribed.into())
                } else {
                    clear_subscriber = subscribed_feeds.len() == 0;
                    Ok(())
                })
                .unwrap_or(Err(ErrorKind::NotSubscribed.into()))?;
            if clear_subscriber {
                self.subscribers.remove(&subscriber);
            }
        }
        let result;
        {
            let mut clear_feed = false;
            result = self.feeds
                .get_mut(&feed_id)
                .map(|feed| if !feed.subscribers.remove(&subscriber) {
                    Err::<Feed, Error>(ErrorKind::NotSubscribed.into())
                } else {
                    clear_feed = feed.subscribers.len() == 0;
                    Ok(feed.clone())
                })
                .unwrap_or(Err(ErrorKind::NotSubscribed.into()))?;
            if clear_feed {
                self.feeds.remove(&feed_id);
            }
        }
        self.save()?;
        Ok(result)
    }

    pub fn update<'a>(&mut self, rss_link: &str, items: Vec<feed::Item>) -> Vec<feed::Item> {
        let feed_id = get_hash(rss_link);
        let mut result = Vec::new();
        let mut new_hash = Vec::new();
        let items_len = items.len();
        for item in items {
            let hash = gen_item_hash(&item);
            if !self.feeds[&feed_id].hash_list.contains(&hash) {
                result.push(item);
                new_hash.push(hash);
            }
        }
        if !new_hash.is_empty() {
            self.feeds.get_mut(&feed_id).map(|feed| {
                truncate_hashset(&mut feed.hash_list, items_len * 2 - new_hash.len());
                for hash in new_hash {
                    feed.hash_list.insert(hash);
                }
            });
        }
        self.save().unwrap_or_default();
        result
    }

    fn save(&mut self) -> Result<()> {
        let feeds_list: Vec<&Feed> = self.feeds.iter().map(|(_id, feed)| feed).collect();
        let mut file = File::create(&self.path).chain_err(|| ErrorKind::DatabaseSave(self.path.to_owned()))?;
        serde_json::to_writer(&mut file, &feeds_list).chain_err(|| ErrorKind::DatabaseSave(self.path.to_owned()))
    }
}
<|endoftext|>


<commit_before>use std;
use std::fs::File;
use std::path::Path;
use std::hash::{Hash, Hasher};
use std::collections::{HashMap, HashSet};

use rss;
use serde_json;

use errors::*;

fn get_hash<T: Hash>(t: T) -> u64 {
    let mut hasher = std::collections::hash_map::DefaultHasher::default();
    t.hash(&mut hasher);
    hasher.finish()
}

type FeedID = u64;
type SubscriberID = i64;

#[derive(Debug, Serialize, Deserialize)]
pub struct Feed {
    link: String,
    title: String,
    error_count: u32,
    hash_list: HashSet<u64>,
    subscribers: HashSet<SubscriberID>,
}

#[derive(Debug)]
pub struct Database {
    path: String,
    feeds: HashMap<FeedID, Feed>,
    subscribers: HashMap<SubscriberID, HashSet<FeedID>>,
}

fn gen_hash_list(items: &[rss::Item]) -> HashSet<u64> {
    items.iter()
        .map(|item| {
            item.guid
                .as_ref()
                .map(|guid| get_hash(&guid.value))
                .unwrap_or_else(|| {
                    let default = "".to_string();
                    let title = item.title.as_ref().unwrap_or(&default);
                    let link = item.link.as_ref().unwrap_or(&default);
                    let string = String::with_capacity(title.len() + link.len());
                    get_hash(string)
                })
        })
        .collect()
}

impl Database {
    pub fn create(path: &str) -> Result<Database> {
        let feeds: HashMap<FeedID, Feed> = HashMap::new();
        let subscribers: HashMap<SubscriberID, HashSet<FeedID>> = HashMap::new();
        let mut result = Database {
            path: path.to_owned(),
            feeds: feeds,
            subscribers: subscribers,
        };

        result.save()?;

        Ok(result)
    }

    pub fn open(path: &str) -> Result<Database> {
        let p = Path::new(path);
        if p.exists() {
            let f = File::open(path).chain_err(|| ErrorKind::DatabaseOpen(path.to_owned()))?;
            let feeds_list: Vec<Feed> =
                serde_json::from_reader(&f).chain_err(|| ErrorKind::DatabaseFormat)?;

            let mut feeds: HashMap<FeedID, Feed> = HashMap::with_capacity(feeds_list.len());
            let mut subscribers: HashMap<SubscriberID, HashSet<FeedID>> = HashMap::new();

            for feed in feeds_list {
                let feed_id = get_hash(&feed.link);
                for subscriber in &feed.subscribers {
                    if subscribers.contains_key(subscriber) {
                        subscribers.get_mut(subscriber).unwrap().insert(feed_id);
                    } else {
                        let mut sets = HashSet::new();
                        sets.insert(feed_id);
                        subscribers.insert(subscriber.to_owned(), sets);
                    }
                }
                feeds.insert(feed_id, feed);
            }

            Ok(Database {
                path: path.to_owned(),
                feeds: feeds,
                subscribers: subscribers,
            })
        } else {
            Database::create(path)
        }
    }

    pub fn subscribe(&mut self,
                     subscriber: SubscriberID,
                     rss_link: &str,
                     rss: &rss::Channel)
                     -> Result<()> {
        let feed_id = get_hash(rss_link);
        {
            let subscribed_feeds =
                self.subscribers.entry(subscriber).or_insert_with(|| HashSet::new());
            if !subscribed_feeds.contains(&feed_id) {
                subscribed_feeds.insert(feed_id);
            } else {
                return Err(ErrorKind::AlreadySubscribed.into());
            }
        }
        {
            let feed = self.feeds.entry(feed_id).or_insert_with(|| {
                Feed {
                    link: rss_link.to_owned(),
                    title: rss.title.to_owned(),
                    error_count: 0,
                    hash_list: gen_hash_list(&rss.items),
                    subscribers: HashSet::new(),
                }
            });
            feed.subscribers.insert(subscriber);
        }
        self.save()
    }

    pub fn unsubscribe(&mut self, subscriber: SubscriberID, rss_link: &str) -> Result<()> {
        let feed_id = get_hash(rss_link);

        {
            let mut clear_subscriber = false;
            self.subscribers
                .get_mut(&subscriber)
                .map(|subscribed_feeds| if !subscribed_feeds.remove(&feed_id) {
                    Err::<(), Error>(ErrorKind::NotSubscribed.into())
                } else {
                    clear_subscriber = subscribed_feeds.len() == 0;
                    Ok(())
                })
                .unwrap_or(Err(ErrorKind::NotSubscribed.into()))?;
            if clear_subscriber {
                self.subscribers.remove(&subscriber);
            }
        }
        {
            let mut clear_feed = false;
            self.feeds
                .get_mut(&feed_id)
                .map(|feed| if !feed.subscribers.remove(&subscriber) {
                    Err::<(), Error>(ErrorKind::NotSubscribed.into())
                } else {
                    clear_feed = feed.subscribers.len() == 0;
                    Ok(())
                })
                .unwrap_or(Err(ErrorKind::NotSubscribed.into()))?;
            if clear_feed {
                self.feeds.remove(&feed_id);
            }
        }

        self.save()
    }

    fn save(&mut self) -> Result<()> {
        let feeds_list: Vec<&Feed> = self.feeds.iter().map(|(_id, feed)| feed).collect();
        let mut file =
            File::create(&self.path).chain_err(|| ErrorKind::DatabaseSave(self.path.to_owned()))?;
        serde_json::to_writer(&mut file, &feeds_list)
            .chain_err(|| ErrorKind::DatabaseSave(self.path.to_owned()))
    }
}
<commit_msg>just improve code style<commit_after>use std;
use std::fs::File;
use std::path::Path;
use std::hash::{Hash, Hasher};
use std::collections::{HashMap, HashSet};

use rss;
use serde_json;

use errors::*;

fn get_hash<T: Hash>(t: T) -> u64 {
    let mut hasher = std::collections::hash_map::DefaultHasher::default();
    t.hash(&mut hasher);
    hasher.finish()
}

type FeedID = u64;
type SubscriberID = i64;

#[derive(Debug, Serialize, Deserialize)]
pub struct Feed {
    link: String,
    title: String,
    error_count: u32,
    hash_list: HashSet<u64>,
    subscribers: HashSet<SubscriberID>,
}

#[derive(Debug)]
pub struct Database {
    path: String,
    feeds: HashMap<FeedID, Feed>,
    subscribers: HashMap<SubscriberID, HashSet<FeedID>>,
}

fn gen_hash_list(items: &[rss::Item]) -> HashSet<u64> {
    items.iter()
        .map(|item| {
            item.guid
                .as_ref()
                .map(|guid| get_hash(&guid.value))
                .unwrap_or_else(|| {
                    let default = "".to_string();
                    let title = item.title.as_ref().unwrap_or(&default);
                    let link = item.link.as_ref().unwrap_or(&default);
                    let string = String::with_capacity(title.len() + link.len());
                    get_hash(string)
                })
        })
        .collect()
}

impl Database {
    pub fn create(path: &str) -> Result<Database> {
        let feeds: HashMap<FeedID, Feed> = HashMap::new();
        let subscribers: HashMap<SubscriberID, HashSet<FeedID>> = HashMap::new();
        let mut result = Database {
            path: path.to_owned(),
            feeds: feeds,
            subscribers: subscribers,
        };

        result.save()?;

        Ok(result)
    }

    pub fn open(path: &str) -> Result<Database> {
        let p = Path::new(path);
        if p.exists() {
            let f = File::open(path).chain_err(|| ErrorKind::DatabaseOpen(path.to_owned()))?;
            let feeds_list: Vec<Feed> =
                serde_json::from_reader(&f).chain_err(|| ErrorKind::DatabaseFormat)?;

            let mut feeds: HashMap<FeedID, Feed> = HashMap::with_capacity(feeds_list.len());
            let mut subscribers: HashMap<SubscriberID, HashSet<FeedID>> = HashMap::new();

            for feed in feeds_list {
                let feed_id = get_hash(&feed.link);
                for subscriber in &feed.subscribers {
                    let subscribed_feeds = subscribers.entry(subscriber.to_owned())
                        .or_insert_with(|| HashSet::new());
                    subscribed_feeds.insert(feed_id);
                }
                feeds.insert(feed_id, feed);
            }

            Ok(Database {
                path: path.to_owned(),
                feeds: feeds,
                subscribers: subscribers,
            })
        } else {
            Database::create(path)
        }
    }

    pub fn subscribe(&mut self,
                     subscriber: SubscriberID,
                     rss_link: &str,
                     rss: &rss::Channel)
                     -> Result<()> {
        let feed_id = get_hash(rss_link);
        {
            let subscribed_feeds =
                self.subscribers.entry(subscriber).or_insert_with(|| HashSet::new());
            if !subscribed_feeds.insert(feed_id) {
                return Err(ErrorKind::AlreadySubscribed.into());
            }
        }
        {
            let feed = self.feeds.entry(feed_id).or_insert_with(|| {
                Feed {
                    link: rss_link.to_owned(),
                    title: rss.title.to_owned(),
                    error_count: 0,
                    hash_list: gen_hash_list(&rss.items),
                    subscribers: HashSet::new(),
                }
            });
            feed.subscribers.insert(subscriber);
        }
        self.save()
    }

    pub fn unsubscribe(&mut self, subscriber: SubscriberID, rss_link: &str) -> Result<()> {
        let feed_id = get_hash(rss_link);

        {
            let mut clear_subscriber = false;
            self.subscribers
                .get_mut(&subscriber)
                .map(|subscribed_feeds| if !subscribed_feeds.remove(&feed_id) {
                    Err::<(), Error>(ErrorKind::NotSubscribed.into())
                } else {
                    clear_subscriber = subscribed_feeds.len() == 0;
                    Ok(())
                })
                .unwrap_or(Err(ErrorKind::NotSubscribed.into()))?;
            if clear_subscriber {
                self.subscribers.remove(&subscriber);
            }
        }
        {
            let mut clear_feed = false;
            self.feeds
                .get_mut(&feed_id)
                .map(|feed| if !feed.subscribers.remove(&subscriber) {
                    Err::<(), Error>(ErrorKind::NotSubscribed.into())
                } else {
                    clear_feed = feed.subscribers.len() == 0;
                    Ok(())
                })
                .unwrap_or(Err(ErrorKind::NotSubscribed.into()))?;
            if clear_feed {
                self.feeds.remove(&feed_id);
            }
        }

        self.save()
    }

    fn save(&mut self) -> Result<()> {
        let feeds_list: Vec<&Feed> = self.feeds.iter().map(|(_id, feed)| feed).collect();
        let mut file =
            File::create(&self.path).chain_err(|| ErrorKind::DatabaseSave(self.path.to_owned()))?;
        serde_json::to_writer(&mut file, &feeds_list)
            .chain_err(|| ErrorKind::DatabaseSave(self.path.to_owned()))
    }
}
<|endoftext|>


<commit_before>use std::io::File; /* input/output */
use std::string::String;

use std::os;

static MAX_RAM : uint = 4096;

/* Reads a ROM file and returns a vector containing
 * its bytes if successful. Otherwise an error string
 * if the file is too large or IoError is raised */
fn read_rom(file_path: String) -> Result<Vec<u8>,String> {

    match File::open(&Path::new(file_path)).read_to_end() {
        Ok(mem) =>  {

            match mem.len() {
            /* Pad end of memory until max memory capacity reached with 0s */
                s if s <=  MAX_RAM  =>
                    Ok(mem + Vec::from_elem(MAX_RAM - s, 0u8)),
            /* Memory read in is too large */
                s  =>   Err(format!("game image is too large
                    ({} bytes), must be a maximum of {} bytes"
                    ,s , MAX_RAM).to_string()),
            }
        },
        /* Error reading file */
        Err(e) => Err(e.detail.unwrap_or("".to_string()))
    }
}

fn main() {
    let mut args = os::args();

   let file_name = match args.remove(1)  {
       Some(name) => name,
       None => fail!("No file name specified")
   };

    debug!("reading file {}",file_name);

    let memory = match read_game(file_name) {
        Ok(mem) => mem,
        Err(e) => fail!("{}",e)
    };

    assert_eq!(memory.len(), MAX_RAM);
}

<commit_msg>Updated main so loaded programs from memory start at address 0x200 which each program should start at hardcoded address 0x200 due to the original VM spec.<commit_after>use std::io::File; /* input/output */
use std::string::String;
use std::os;

static MAX_RAM : uint = 0xFFF;
static START_RAM :uint = 0x200;

/* Reads a ROM file and returns a vector containing
 * its bytes if successful. Otherwise an error string
 * if the file is too large or IoError is raised */
fn read_rom(file_path: String) -> Result<Vec<u8>,String> {

    match File::open(&Path::new(file_path)).read_to_end() {

        Ok(rom_contents) =>  {
            /* Programs start at address 0x200, in original implementation
             * 0x000 - 0x1FF reserved for VM, just pad start of mem with
             * 0s in this case */
            let mem = Vec::from_elem(START_RAM, 0u8) + rom_contents;

            match mem.len() {
            /* Pad end of memory until max memory capacity reached with 0s */
                s if s <=  MAX_RAM  =>
                    Ok(mem + Vec::from_elem(MAX_RAM -  s, 0u8)),
            /* Memory read in from game ROM is too large */
                s  =>   Err(format!("game image is too large
                    ({} bytes), must be a maximum of {} bytes"
                    ,s , MAX_RAM - START_RAM).to_string()),
            }
        },
        /* Error reading file */
        Err(e) => Err(e.detail.unwrap_or("".to_string()))
    }
}

fn main() {
    let mut args = os::args();

   let file_name = match args.remove(1)  {
       Some(name) => name,
       None => fail!("No file name specified")
   };

    debug!("reading file {}",file_name);

    let memory = match read_game(file_name) {
        Ok(mem) => mem,
        Err(e) => fail!("{}",e)
    };

    assert_eq!(memory.len(), MAX_RAM);
}

<|endoftext|>