use crate::core::error::Result;
use crate::core::state::{ChunkReader, ChunkWriter, Sink, Source};
pub const PPUCTRL: u8 = 0;
pub const PPUMASK: u8 = 1;
pub const PPUSTATUS: u8 = 2;
pub const OAMADDR: u8 = 3;
pub const OAMDATA: u8 = 4;
pub const PPUSCROLL: u8 = 5;
pub const PPUADDR: u8 = 6;
pub const PPUDATA: u8 = 7;
pub const CTRL_NAMETABLE: u8 = 0b0000_0011;
pub const CTRL_INCREMENT: u8 = 0b0000_0100;
pub const CTRL_SPRITE_TABLE: u8 = 0b0000_1000;
pub const CTRL_BG_TABLE: u8 = 0b0001_0000;
pub const CTRL_SPRITE_16: u8 = 0b0010_0000;
pub const CTRL_MASTER: u8 = 0b0100_0000;
pub const CTRL_NMI: u8 = 0b1000_0000;
pub const MASK_GREYSCALE: u8 = 0b0000_0001;
pub const MASK_BG_LEFT: u8 = 0b0000_0010;
pub const MASK_SPRITE_LEFT: u8 = 0b0000_0100;
pub const MASK_BG: u8 = 0b0000_1000;
pub const MASK_SPRITE: u8 = 0b0001_0000;
pub const MASK_EMPHASIS_R: u8 = 0b0010_0000;
pub const MASK_EMPHASIS_G: u8 = 0b0100_0000;
pub const MASK_EMPHASIS_B: u8 = 0b1000_0000;
pub const MASK_RENDERING: u8 = MASK_BG | MASK_SPRITE;
pub const STATUS_OVERFLOW: u8 = 0b0010_0000;
pub const STATUS_SPRITE0: u8 = 0b0100_0000;
pub const STATUS_VBLANK: u8 = 0b1000_0000;
pub const STATUS_DRIVEN: u8 = STATUS_OVERFLOW | STATUS_SPRITE0 | STATUS_VBLANK;
pub const SPRITE_PALETTE: u8 = 0b0000_0011;
pub const SPRITE_BEHIND: u8 = 0b0010_0000;
pub const SPRITE_FLIP_X: u8 = 0b0100_0000;
pub const SPRITE_FLIP_Y: u8 = 0b1000_0000;
pub const SPRITE_ATTR_IMPLEMENTED: u8 = 0b1110_0011;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct IoLatch {
value: u8,
refreshed: [u64; 8],
ttl: u64,
}
impl IoLatch {
pub const fn new(ttl: u64) -> IoLatch {
IoLatch {
value: 0,
refreshed: [0; 8],
ttl,
}
}
pub const fn ttl(&self) -> u64 {
self.ttl
}
pub fn read(&mut self, now: u64) -> u8 {
for bit in 0..8 {
if now.saturating_sub(self.refreshed[bit]) > self.ttl {
self.value &= !(1u8 << bit);
}
}
self.value
}
pub const fn peek(&self, now: u64) -> u8 {
let mut value = self.value;
let mut bit = 0;
while bit < 8 {
if now.saturating_sub(self.refreshed[bit]) > self.ttl {
value &= !(1u8 << bit);
}
bit += 1;
}
value
}
pub const fn value(&self) -> u8 {
self.value
}
pub fn refresh(&mut self, now: u64, value: u8, mask: u8) {
self.value = (self.value & !mask) | (value & mask);
for bit in 0..8 {
if mask & (1u8 << bit) != 0 {
self.refreshed[bit] = now;
}
}
}
pub fn save(&self, w: &mut ChunkWriter<'_>) -> Result<()> {
w.write_u8(self.value)?;
w.write_u64(self.ttl)?;
for age in self.refreshed {
w.write_u64(age)?;
}
Ok(())
}
pub fn load(&mut self, r: &mut ChunkReader<'_>) -> Result<()> {
self.value = r.read_u8()?;
self.ttl = r.read_u64()?;
for age in &mut self.refreshed {
*age = r.read_u64()?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_refreshed_bit_survives_and_an_old_one_does_not() {
let mut latch = IoLatch::new(100);
latch.refresh(0, 0xff, 0xff);
assert_eq!(latch.read(100), 0xff);
latch.refresh(150, 0x01, 0x01);
assert_eq!(latch.read(201), 0x01);
}
#[test]
fn masked_bits_keep_their_own_age() {
let mut latch = IoLatch::new(10);
latch.refresh(0, 0xf0, 0xf0);
latch.refresh(9, 0x0f, 0x0f);
assert_eq!(latch.read(12), 0x0f);
}
}