pub struct Status {
vblank_flag: bool,
sprite_0_hit: bool,
pending_sprite_0_hit: bool,
sprite_overflow: bool,
nmi_enabled: bool,
frame_complete: bool,
}
impl Default for Status {
fn default() -> Self {
Self::new()
}
}
impl Status {
pub fn new() -> Self {
Self {
vblank_flag: false,
sprite_0_hit: false,
pending_sprite_0_hit: false,
sprite_overflow: false,
nmi_enabled: false,
frame_complete: false,
}
}
pub fn reset(&mut self) {
self.vblank_flag = false;
self.sprite_0_hit = false;
self.pending_sprite_0_hit = false;
self.sprite_overflow = false;
self.nmi_enabled = false;
self.frame_complete = false;
}
pub fn enter_vblank(&mut self) {
self.vblank_flag = true;
self.frame_complete = true;
}
pub fn exit_vblank(&mut self) {
self.vblank_flag = false;
}
pub fn clear_sprite_flags(&mut self) {
self.sprite_0_hit = false;
self.pending_sprite_0_hit = false;
self.sprite_overflow = false;
}
pub fn trigger_nmi(&mut self) {
self.nmi_enabled = true;
}
pub fn clear_nmi(&mut self) {
self.nmi_enabled = false;
}
pub fn read_status(&mut self) -> u8 {
let status = self.peek_status();
self.vblank_flag = false;
status
}
pub fn peek_status(&self) -> u8 {
let mut status = 0u8;
if self.vblank_flag {
status |= 0b1000_0000; }
if self.sprite_0_hit {
status |= 0b0100_0000; }
if self.sprite_overflow {
status |= 0b0010_0000; }
status
}
pub fn poll_nmi(&mut self) -> bool {
let result = self.nmi_enabled;
self.nmi_enabled = false;
result
}
pub fn poll_frame_complete(&mut self) -> bool {
let result = self.frame_complete;
self.frame_complete = false;
result
}
pub fn is_in_vblank(&self) -> bool {
self.vblank_flag
}
pub fn set_sprite_0_hit(&mut self) {
self.sprite_0_hit = true;
}
pub fn set_sprite_overflow(&mut self) {
self.sprite_overflow = true;
}
pub fn is_sprite_0_hit(&self) -> bool {
self.sprite_0_hit
}
pub fn is_sprite_overflow(&self) -> bool {
self.sprite_overflow
}
pub fn is_nmi_enabled(&self) -> bool {
self.nmi_enabled
}
pub fn pending_sprite_0_hit(&self) -> bool {
self.pending_sprite_0_hit
}
pub fn frame_complete(&self) -> bool {
self.frame_complete
}
pub fn restore_state(
&mut self,
vblank: bool,
sprite_0_hit: bool,
sprite_overflow: bool,
nmi_enabled: bool,
) {
self.vblank_flag = vblank;
self.sprite_0_hit = sprite_0_hit;
self.pending_sprite_0_hit = false;
self.sprite_overflow = sprite_overflow;
self.nmi_enabled = nmi_enabled;
self.frame_complete = false;
}
pub fn set_pending_sprite_0_hit(&mut self, pending: bool) {
self.pending_sprite_0_hit = pending;
}
pub fn set_frame_complete(&mut self, complete: bool) {
self.frame_complete = complete;
}
}
#[cfg(test)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StatusDebugState {
pub vblank_flag: bool,
pub sprite_0_hit: bool,
pub pending_sprite_0_hit: bool,
pub sprite_overflow: bool,
pub nmi_enabled: bool,
pub frame_complete: bool,
}
#[cfg(test)]
impl Status {
pub fn debug_state(&self) -> StatusDebugState {
StatusDebugState {
vblank_flag: self.vblank_flag,
sprite_0_hit: self.sprite_0_hit,
pending_sprite_0_hit: self.pending_sprite_0_hit,
sprite_overflow: self.sprite_overflow,
nmi_enabled: self.nmi_enabled,
frame_complete: self.frame_complete,
}
}
pub fn set_debug_state(&mut self, state: StatusDebugState) {
self.vblank_flag = state.vblank_flag;
self.sprite_0_hit = state.sprite_0_hit;
self.pending_sprite_0_hit = state.pending_sprite_0_hit;
self.sprite_overflow = state.sprite_overflow;
self.nmi_enabled = state.nmi_enabled;
self.frame_complete = state.frame_complete;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_status_new() {
let status = Status::new();
assert!(!status.is_in_vblank());
assert!(!status.is_sprite_0_hit());
}
#[test]
fn test_status_reset() {
let mut status = Status::new();
status.enter_vblank();
status.reset();
assert!(!status.is_in_vblank());
}
#[test]
fn test_enter_vblank() {
let mut status = Status::new();
status.enter_vblank();
assert!(status.is_in_vblank());
}
#[test]
fn test_exit_vblank() {
let mut status = Status::new();
status.enter_vblank();
status.exit_vblank();
assert!(!status.is_in_vblank());
}
#[test]
fn test_read_status_clears_vblank() {
let mut status = Status::new();
status.enter_vblank();
let status_byte = status.read_status();
assert_eq!(status_byte & 0b1000_0000, 0b1000_0000);
assert!(!status.is_in_vblank());
}
#[test]
fn test_peek_status_does_not_clear_vblank() {
let mut status = Status::new();
status.enter_vblank();
let status_byte = status.peek_status();
assert_eq!(status_byte & 0b1000_0000, 0b1000_0000);
assert!(status.is_in_vblank());
}
#[test]
fn test_read_status_clears_vblank_even_on_vblank_start() {
let mut status = Status::new();
status.enter_vblank();
let status_byte = status.read_status();
assert_eq!(status_byte & 0b1000_0000, 0b1000_0000);
assert!(!status.is_in_vblank());
}
#[test]
fn test_sprite_0_hit() {
let mut status = Status::new();
status.set_sprite_0_hit();
assert!(status.is_sprite_0_hit());
let status_byte = status.read_status();
assert_eq!(status_byte & 0b0100_0000, 0b0100_0000);
}
#[test]
fn test_sprite_overflow() {
let mut status = Status::new();
status.set_sprite_overflow();
let status_byte = status.read_status();
assert_eq!(status_byte & 0b0010_0000, 0b0010_0000);
}
#[test]
fn test_poll_nmi() {
let mut status = Status::new();
status.enter_vblank();
status.trigger_nmi();
assert!(status.poll_nmi());
assert!(!status.poll_nmi()); }
#[test]
fn test_poll_frame_complete() {
let mut status = Status::new();
status.enter_vblank();
assert!(status.poll_frame_complete());
assert!(!status.poll_frame_complete()); }
}