nes-sim 0.1.3

A NES (Famicom) emulator core library written in pure Rust.
Documentation
use crate::{
    AudioBatch, CartridgeError, ControllerState, CoreCommand, DebugSnapshot, NES, SaveStateError,
    VideoFrame,
};

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct FrontendInput {
    pub controller1: ControllerState,
    pub controller2: ControllerState,
    pub reset: bool,
    pub toggle_pause: bool,
    pub step_frame: bool,
    pub step_cpu_instruction: bool,
    pub quit: bool,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RunMode {
    Running,
    Paused,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ExecutionTarget {
    None,
    Frame,
    CpuInstruction,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct RuntimeStatus {
    pub mode: RunMode,
    pub executed: ExecutionTarget,
    pub quit_requested: bool,
}

#[derive(Clone, Copy, Debug)]
pub struct RuntimeSnapshot<'a> {
    pub status: RuntimeStatus,
    pub video: VideoFrame<'a>,
    pub audio: AudioBatch<'a>,
    pub debug: DebugSnapshot,
}

pub struct FrontendRuntime {
    nes: NES,
    mode: RunMode,
}

impl FrontendRuntime {
    pub fn new(nes: NES) -> Self {
        Self {
            nes,
            mode: RunMode::Running,
        }
    }

    pub fn from_rom_bytes(rom: &[u8]) -> Result<Self, CartridgeError> {
        let mut nes = NES::new();
        nes.load_cartridge_ines(rom)?;
        nes.reset();
        Ok(Self::new(nes))
    }

    pub fn mode(&self) -> RunMode {
        self.mode
    }

    pub fn nes(&self) -> &NES {
        &self.nes
    }

    pub fn nes_mut(&mut self) -> &mut NES {
        &mut self.nes
    }

    pub fn save_state(&self) -> Result<Vec<u8>, SaveStateError> {
        self.nes.save_state()
    }

    pub fn load_state(&mut self, bytes: &[u8]) -> Result<(), SaveStateError> {
        self.nes.load_state(bytes)
    }

    pub fn set_mode(&mut self, mode: RunMode) {
        self.mode = mode;
    }

    pub fn snapshot(&self) -> RuntimeSnapshot<'_> {
        RuntimeSnapshot {
            status: RuntimeStatus {
                mode: self.mode,
                executed: ExecutionTarget::None,
                quit_requested: false,
            },
            video: self.nes.video_frame(),
            audio: self.nes.audio_batch(),
            debug: self.nes.debug_snapshot(),
        }
    }

    pub fn step(&mut self, input: FrontendInput) -> RuntimeSnapshot<'_> {
        self.nes.clear_audio_samples();
        self.nes.execute(CoreCommand::SetControllerState {
            port: 0,
            state: input.controller1,
        });
        self.nes.execute(CoreCommand::SetControllerState {
            port: 1,
            state: input.controller2,
        });

        if input.reset {
            self.nes.execute(CoreCommand::Reset);
        }

        if input.toggle_pause {
            self.mode = match self.mode {
                RunMode::Running => RunMode::Paused,
                RunMode::Paused => RunMode::Running,
            };
        }

        let executed = if input.step_cpu_instruction {
            self.nes.execute(CoreCommand::StepCpuInstruction);
            ExecutionTarget::CpuInstruction
        } else if input.step_frame || matches!(self.mode, RunMode::Running) {
            self.nes.execute(CoreCommand::RunFrame);
            ExecutionTarget::Frame
        } else {
            ExecutionTarget::None
        };

        RuntimeSnapshot {
            status: RuntimeStatus {
                mode: self.mode,
                executed,
                quit_requested: input.quit,
            },
            video: self.nes.video_frame(),
            audio: self.nes.audio_batch(),
            debug: self.nes.debug_snapshot(),
        }
    }
}