use crate::cartridge::Cartridge;
use crate::input::{HostInput, InputRegisters};
use crate::nes::dma::{Dma, DmaState, DmaType};
use crate::nes::ppu_cart_memory::PpuCartMemory;
use crate::nes::system::nes_memory_space::NesMemorySpace;
use crate::ppu::{Color, FrameEvent, Ppu};
use cpu6502::{Cpu, MemorySpace};
use devices6502::size_const::*;
use std::ops::DerefMut;
#[cfg(feature = "instr_log")]
use std::path::Path;
const PPU_CYCLES_PER_CPU_CYCLE: u8 = 3;
#[derive(Default)]
pub struct NesConsole {
cpu: Cpu,
ppu: Ppu,
internal_ram: devices6502::Mirror<devices6502::Ram<SIZE_2K>, 4>,
cartridge: Option<Box<dyn Cartridge>>,
input_registers: InputRegisters,
ppu_dma: Dma,
current_dma_put: DmaType,
cycles_since_reset: usize,
on: bool,
}
impl NesConsole {
pub fn new() -> Self {
Self::default()
}
pub fn screen_colors(&self) -> &[Color] {
self.ppu.screen_colors()
}
pub fn insert_cartridge(&mut self, cart: Box<dyn Cartridge>) {
assert!(
!self.cartridge_connected(),
"attempt to insert cartridge when there is already one"
);
self.ppu.cycle_accurate_sprites_enabled = cart.requires_cycle_accurate_sprites();
self.cartridge = Some(cart);
}
pub fn on(&self) -> bool {
self.on
}
pub fn remove_cartridge(&mut self) {
assert!(!self.on);
self.cartridge = None;
}
pub fn cartridge_connected(&self) -> bool {
self.cartridge.is_some()
}
pub fn switch_on(&mut self) {
if self.on == false {
self.cpu.reset();
self.ppu.reset();
self.cycles_since_reset = 0;
self.on = true;
#[cfg(feature = "instr_log")]
{
self.cpu.init_logging_debug(Path::new("nes_cpu_log.txt"));
}
}
}
pub fn switch_off(&mut self) {
self.on = false;
}
pub fn reset(&mut self) {
self.ppu.reset();
self.cpu.reset();
self.cycles_since_reset = 0;
}
#[inline]
pub fn run_console_cycle(
&mut self,
cartridge: &mut dyn Cartridge,
host_input: &mut impl HostInput,
) -> FrameEvent {
if self.ppu_dma.state() == DmaState::WaitingForCpuHalt {
if self.cpu.is_current_cycle_write() == false {
self.ppu_dma.start_transfer();
}
}
if self.ppu_dma.state() == DmaState::Transferring {
if self.current_dma_put != DmaType::Ppu {
let pull_addr = self.ppu_dma.addr();
let mut memory = NesMemorySpace::new(
&mut self.internal_ram,
cartridge,
&mut self.ppu,
&mut self.input_registers,
host_input,
&mut self.ppu_dma,
);
let data = memory.read(pull_addr);
self.ppu_dma.data_pull(data);
} else if let Some(data) = self.ppu_dma.data_put() {
let mut ppu_memory = PpuCartMemory::new(cartridge);
self.ppu
.write_ppu_register(data, crate::ppu::Register::OamData, &mut ppu_memory);
}
} else {
let mut memory = NesMemorySpace::new(
&mut self.internal_ram,
cartridge,
&mut self.ppu,
&mut self.input_registers,
host_input,
&mut self.ppu_dma,
);
self.cpu.run(&mut memory);
let _chr_rom_changed = memory.chr_rom_changed();
};
let mut ppu_memory = PpuCartMemory::new(cartridge);
let mut nmi_signal = false;
let mut frame_event = crate::ppu::FrameEvent::None;
for _ in 0..PPU_CYCLES_PER_CPU_CYCLE {
let new_frame_state = self.ppu.run_cycle(&mut ppu_memory);
frame_event.join(new_frame_state);
nmi_signal |= self.ppu.nmi_signal();
}
self.cpu.set_nmi_pin_value(nmi_signal);
self.cpu.set_irq_pin_value(cartridge.irq_pin());
self.current_dma_put.toggle();
self.cycles_since_reset += 1;
frame_event
}
pub fn run_until_present(&mut self, host_input: &mut impl HostInput) {
if self.on == false {
return;
}
let mut cart = self.cartridge.take();
if let Some(cart) = cart.as_mut() {
let cart = cart.deref_mut();
while self.run_console_cycle(cart, host_input) != FrameEvent::ReadyToPresent {}
}
self.cartridge = cart.take();
}
pub fn run_frame(&mut self, host_input: &mut impl HostInput) {
if self.on == false {
return;
}
let mut cart = self.cartridge.take();
if let Some(cart) = cart.as_mut() {
let cart = cart.deref_mut();
while self.run_console_cycle(cart, host_input) != FrameEvent::EndOfFrame {}
}
self.cartridge = cart.take();
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cartridge::ChrRomContentStatus;
use crate::input::{ConnectedSocket, InputDeviceState};
struct MockCart;
impl Cartridge for MockCart {
fn read_cpu_mapped(&self, _addr: u16) -> u8 {
0xEA
}
fn write_cpu_mapped(&mut self, _data: u8, _addr: u16) -> ChrRomContentStatus {
ChrRomContentStatus::Unchanged
}
fn read_ppu_mapped(&mut self, _addr: u16) -> u8 {
0
}
fn write_ppu_mapped(&mut self, _data: u8, _addr: u16) -> ChrRomContentStatus {
ChrRomContentStatus::Unchanged
}
}
struct CycleAccurateSpriteCart;
impl Cartridge for CycleAccurateSpriteCart {
fn read_cpu_mapped(&self, _addr: u16) -> u8 {
0xEA
}
fn write_cpu_mapped(&mut self, _data: u8, _addr: u16) -> ChrRomContentStatus {
ChrRomContentStatus::Unchanged
}
fn read_ppu_mapped(&mut self, _addr: u16) -> u8 {
0
}
fn write_ppu_mapped(&mut self, _data: u8, _addr: u16) -> ChrRomContentStatus {
ChrRomContentStatus::Unchanged
}
fn requires_cycle_accurate_sprites(&self) -> bool {
true
}
}
struct MockHostInput;
impl HostInput for MockHostInput {
fn input_device_state(&mut self, _player: ConnectedSocket) -> InputDeviceState {
Default::default()
}
}
#[test]
fn create_console_and_run_until_present() {
let mut console = NesConsole::default();
let cart = Box::new(MockCart);
let mut host_input = MockHostInput;
console.insert_cartridge(cart);
console.switch_on();
console.run_until_present(&mut host_input);
let pixels_drawn = console.ppu.screen_colors().len();
assert_eq!(pixels_drawn, 256 * 240);
}
const PPU_CYCLES_PER_SCANLINE: usize = 341;
const PPU_SCANLINES: usize = 262;
#[test]
fn create_console_and_run_frames() {
let mut console = NesConsole::default();
let cart = Box::new(MockCart);
let mut host_input = MockHostInput;
console.insert_cartridge(cart);
console.switch_on();
console.run_frame(&mut host_input);
console.run_frame(&mut host_input);
let pixels_drawn = console.ppu.screen_colors().len();
assert_eq!(pixels_drawn, 256 * 240);
let ppu_cycles = PPU_CYCLES_PER_SCANLINE * PPU_SCANLINES * 2 - 1;
assert_eq!(console.cycles_since_reset, ppu_cycles / 3);
}
#[test]
#[cfg(not(debug_assertions))]
fn create_console_and_run60sec() {
use crate::ppu::Register;
let mut console = NesConsole::default();
let cart = Box::new(MockCart);
let mut ppu_cart = Box::new(MockCart);
let mut host_input = MockHostInput;
let mut ppu_mem = PpuCartMemory::new(ppu_cart.as_mut());
console.insert_cartridge(cart);
console.switch_on();
console
.ppu
.write_ppu_register(0b_00011110, Register::PpuMask, &mut ppu_mem);
for _ in 0..3600 {
console.run_frame(&mut host_input);
}
}
#[test]
fn run_without_cart() {
let mut console = NesConsole::default();
let mut host_input = MockHostInput;
console.switch_on();
console.run_frame(&mut host_input);
assert_eq!(console.cycles_since_reset, 0);
}
#[test]
#[should_panic(expected = "attempt to insert cartridge when there is already one")]
fn connectwice_panics() {
let mut console = NesConsole::default();
let cart = Box::new(MockCart);
let cart2 = Box::new(MockCart);
console.insert_cartridge(cart);
console.insert_cartridge(cart2);
}
#[test]
#[should_panic(expected = "assertion failed: !self.on")]
fn removewhileon_panics() {
let mut console = NesConsole::default();
let cart = Box::new(MockCart);
let mut host_input = MockHostInput;
console.insert_cartridge(cart);
console.switch_on();
console.run_frame(&mut host_input);
console.remove_cartridge();
}
#[test]
fn run_while_off() {
let mut console = NesConsole::default();
let cart = Box::new(MockCart);
let mut host_input = MockHostInput;
console.insert_cartridge(cart);
console.run_frame(&mut host_input);
assert_eq!(console.cycles_since_reset, 0);
}
#[test]
fn insert_cartridge_propagates_cycle_accurate_sprites_flag() {
let mut console = NesConsole::default();
console.insert_cartridge(Box::new(MockCart));
assert!(!console.ppu.cycle_accurate_sprites_enabled);
let mut console = NesConsole::default();
console.insert_cartridge(Box::new(CycleAccurateSpriteCart));
assert!(console.ppu.cycle_accurate_sprites_enabled);
}
}