use alloc::boxed::Box;
use alloc::vec;
use alloc::vec::Vec;
use gte_w65c02s::{System, W65C02S};
use log::{debug, error, info, warn};
use gte_w65c02s::State::AwaitingInterrupt;
use core::fmt::{Debug, Formatter};
use bytemuck::bytes_of;
use heapless::{FnvIndexMap};
use rtrb::PushError;
use gte_acp::audio_output::GameTankAudio;
use crate::blitter::Blitter;
use crate::cartridges::CartridgeType;
use crate::emulator::PlayState::{Paused, Playing, WasmInit};
use crate::gametank_bus::{CpuBus};
use gte_acp::AcpBus;
use crate::inputs::{ControllerButton, InputCommand, KeyState};
use crate::inputs::ControllerButton::{Down, Left, Right, Start, Up, A, B, C};
use crate::inputs::InputCommand::{Controller1, Controller2, HardReset, PlayPause, SoftReset};
use crate::inputs::KeyState::JustReleased;
pub const WIDTH: u32 = 128;
pub const HEIGHT: u32 = 128;
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum PlayState {
WasmInit,
Paused,
Playing,
}
pub trait TimeDaemon {
fn get_now_ms(&self) -> f64;
}
#[derive(Copy, Clone, Debug, Default)]
pub struct AcpLoadStats {
pub worst_case_cycles: i32,
pub budget_cycles: i32,
pub overrun_percent: u32,
pub sample_rate_hz: u32,
pub periods_measured: u32,
}
#[derive(Default, Debug)]
struct AcpStats {
periods: u32,
overruns: u32,
worst_compute: i32,
cycles_awake: i32,
asleep: bool,
last_worst_compute: i32,
last_budget: i32,
last_overrun_percent: u32,
last_sample_rate: u32,
last_periods: u32,
}
impl AcpStats {
fn tick(&mut self, cycles: i32, waiting: bool) {
if self.asleep {
return;
}
self.cycles_awake += cycles;
if waiting {
self.worst_compute = self.worst_compute.max(self.cycles_awake);
self.asleep = true;
}
}
fn record(&mut self) {
self.periods += 1;
if self.asleep {
self.asleep = false;
self.cycles_awake = 0;
} else {
self.overruns += 1;
}
}
fn report(&mut self, interval: u32, budget: i32, reg: u8) {
if self.periods < interval {
return;
}
if self.overruns > 0 {
warn!(
"ACP: {}cy/sample, ${:02X} budgets {}cy - {}% of samples duplicated",
self.worst_compute,
reg,
budget,
self.overruns * 100 / self.periods,
);
}
self.last_worst_compute = self.worst_compute;
self.last_budget = budget;
self.last_overrun_percent = self.overruns * 100 / self.periods.max(1);
self.last_sample_rate = interval;
self.last_periods = self.periods;
self.periods = 0;
self.overruns = 0;
self.worst_compute = 0;
}
}
pub struct Emulator<Clock: TimeDaemon> {
pub cpu_bus: CpuBus,
pub acp_bus: AcpBus,
pub cpu: W65C02S,
pub acp: W65C02S,
pub blitter: Blitter,
pub clock_cycles_to_vblank: i32,
pub last_emu_tick: f64,
pub cpu_ns_per_cycle: f64,
pub cpu_frequency_hz: f64,
pub last_render_time: f64,
pub audio_out: Option<GameTankAudio>,
pub target_sample_rate: f64,
pub play_state: PlayState,
pub wait_counter: u64,
pub input_state: FnvIndexMap<InputCommand, KeyState, 32>, pub clock: Clock,
pub mouse_paddle_enabled: bool,
acp_stats: AcpStats,
}
impl <Clock: TimeDaemon> Emulator<Clock> {
pub fn load_rom(&mut self, bytes: &[u8]) {
warn!("loading new rom from memory, size: {}", bytes.len());
self.cpu_bus.cartridge = CartridgeType::from_slice(bytes);
warn!(" - cartridge loaded from memory");
self.cpu.reset();
warn!(" - cpu reset");
self.acp.reset();
warn!(" - acp reset");
self.blitter.clear_irq_trigger();
warn!(" - blitter irq cleared");
}
}
impl <Clock: TimeDaemon> Debug for Emulator<Clock> {
fn fmt(&self, f: &mut Formatter) -> core::fmt::Result {
f.debug_struct("Emulator")
.field("cpu_bus", &self.cpu_bus)
.field("acp_bus", &self.acp_bus)
.field("cpu", &self.cpu)
.field("acp", &self.acp)
.field("blitter", &self.blitter)
.field("clock_cycles_to_vblank", &self.clock_cycles_to_vblank)
.field("last_emu_tick", &self.last_emu_tick)
.finish()
}
}
impl <Clock: TimeDaemon> Emulator<Clock> {
pub fn wasm_init(&mut self) {
if self.play_state == WasmInit {
self.play_state = Playing;
self.last_emu_tick = self.clock.get_now_ms();
self.last_render_time = self.clock.get_now_ms();
}
}
pub fn init(clock: Clock, target_sample_rate: f64) -> Self {
let play_state = WasmInit;
let mut bus = CpuBus::default();
let mut cpu = W65C02S::new();
cpu.step(&mut bus);
let acp = W65C02S::new();
let blitter = Blitter::default();
let last_cpu_tick_ms = clock.get_now_ms();
let cpu_frequency_hz = 3_579_545.0; let cpu_ns_per_cycle = 1_000_000_000.0 / cpu_frequency_hz; let last_render_time = last_cpu_tick_ms;
Emulator {
play_state,
cpu_bus: bus,
acp_bus: AcpBus::default(),
cpu,
acp,
blitter,
clock_cycles_to_vblank: 59659,
last_emu_tick: last_cpu_tick_ms,
cpu_frequency_hz,
cpu_ns_per_cycle,
last_render_time,
audio_out: None,
target_sample_rate,
wait_counter: 0,
input_state: Default::default(),
clock,
acp_stats: AcpStats::default(),
mouse_paddle_enabled: false
}
}
pub fn process_cycles(&mut self, is_web: bool) {
self.process_inputs();
if self.play_state != Playing {
return
}
let now_ms = self.clock.get_now_ms();
let mut elapsed_ms = now_ms - self.last_emu_tick;
if elapsed_ms > 33.0 {
warn!("emulator took more than 33ms to process cycles");
elapsed_ms = 16.667;
}
let elapsed_ns = elapsed_ms * 1000000.0;
let mut remaining_cycles: i32 = (elapsed_ns / self.cpu_ns_per_cycle) as i32;
let mut acp_cycle_accumulator = 0;
while remaining_cycles > 0 {
if self.cpu.get_state() == AwaitingInterrupt {
self.wait_counter += 1;
} else if self.wait_counter > 0 {
debug!("waited {} cycles", self.wait_counter);
self.wait_counter = 0;
}
let cpu_cycles = self.cpu.step(&mut self.cpu_bus);
remaining_cycles -= cpu_cycles;
acp_cycle_accumulator += cpu_cycles * 4;
if self.cpu_bus.system_control.acp_enabled() {
self.run_acp(&mut acp_cycle_accumulator);
}
for _ in 0..cpu_cycles {
self.blitter.cycle(&mut self.cpu_bus);
}
let blit_irq = self.blitter.irq_trigger;
if blit_irq {
debug!("blit irq");
}
self.cpu.set_irq(blit_irq);
self.clock_cycles_to_vblank -= cpu_cycles;
if self.clock_cycles_to_vblank <= 0 {
self.vblank();
}
}
self.last_emu_tick = now_ms;
if !is_web && (now_ms - self.last_render_time) >= 16.67 {
debug!("time since last render: {}", now_ms - self.last_render_time);
self.last_render_time = now_ms;
}
}
fn run_acp(&mut self, acp_cycle_accumulator: &mut i32) {
if self.cpu_bus.system_control.clear_acp_reset() {
self.acp.reset();
}
if self.cpu_bus.system_control.clear_acp_nmi() {
self.acp.set_nmi(true);
}
while *acp_cycle_accumulator > 0 {
let acp_cycles = self.acp.step(&mut self.acp_bus);
*acp_cycle_accumulator -= acp_cycles;
self.acp_bus.irq_counter -= acp_cycles;
self.acp_bus.cycles_since_write += acp_cycles;
self.acp_stats.tick(acp_cycles, self.acp.get_state() == AwaitingInterrupt);
self.acp.set_irq(false);
self.acp.set_nmi(false);
if self.acp_bus.irq_counter <= 0 {
let divisor = self.cpu_bus.system_control.sample_divisor() as i32;
let budget = divisor * 4;
self.acp_bus.irq_counter = budget;
self.acp.set_irq(true);
self.acp_stats.record();
self.acp_stats.report(
(self.cpu_frequency_hz / divisor as f64) as u32,
budget,
self.cpu_bus.system_control.audio_enable_sample_rate,
);
self.emit_sample(divisor);
}
}
}
fn emit_sample(&mut self, divisor: i32) {
let sample_rate = self.cpu_frequency_hz / divisor as f64;
if self.audio_out.as_ref().is_none_or(|gta| gta.sample_rate != sample_rate) {
warn!(
"recreated audio stream with new sample rate: {:.3}Hz (${:02X})",
sample_rate, self.cpu_bus.system_control.audio_enable_sample_rate
);
self.audio_out = Some(GameTankAudio::new(sample_rate, self.target_sample_rate));
}
if let Some(audio) = &mut self.audio_out {
if let Err(e) = audio.producer.push(self.acp_bus.sample) {
error!("not enough slots in audio producer: {e}");
}
audio.convert_to_output_buffers();
}
}
fn vblank(&mut self) {
self.clock_cycles_to_vblank += 59659;
if self.cpu_bus.vblank_nmi_enabled() {
self.cpu.set_nmi(true);
debug!("vblanked");
}
}
pub fn set_input_state(&mut self, input_command: InputCommand, state: KeyState) {
self.input_state.insert(input_command, state).expect("shit's full dog ://");
}
pub fn opcode_cycle_profile(&self) -> gte_w65c02s::OpcodeCycleProfile {
self.cpu.get_cycle_profile()
}
pub fn set_opcode_cycle_profile(&mut self, profile: gte_w65c02s::OpcodeCycleProfile) {
self.cpu.set_cycle_profile(profile);
self.acp.set_cycle_profile(profile);
}
pub fn acp_load_stats(&self) -> AcpLoadStats {
AcpLoadStats {
worst_case_cycles: self.acp_stats.last_worst_compute,
budget_cycles: self.acp_stats.last_budget,
overrun_percent: self.acp_stats.last_overrun_percent,
sample_rate_hz: self.acp_stats.last_sample_rate,
periods_measured: self.acp_stats.last_periods,
}
}
fn process_inputs(&mut self) {
let keys: Vec<_> = self.input_state.keys().cloned().collect();
if keys.len() > 0 && self.play_state == WasmInit {
self.play_state = Playing;
}
for key in &keys {
match key {
Controller1(button) => { self.set_gamepad_input(0, &key, &button); }
Controller2(button) => { self.set_gamepad_input(1, &key, &button); }
PlayPause => {
if self.input_state[key] == JustReleased {
match self.play_state {
Paused => { self.play_state = Playing; }
Playing => { self.play_state = Paused; }
WasmInit => { self.play_state = Playing; }
}
}
}
SoftReset => {
self.cpu.reset();
}
HardReset => {
let cart = self.cpu_bus.cartridge.clone();
self.cpu_bus = CpuBus::default();
self.cpu_bus.cartridge = cart;
self.cpu = W65C02S::new();
self.cpu.step(&mut self.cpu_bus); self.acp = W65C02S::new();
self.blitter = Blitter::default();
}
}
self.input_state.insert(*key, self.input_state[key].update()).expect("shit's full dog ://");
}
}
pub fn apply_paddle_delta(&mut self, delta: i8) {
self.cpu_bus.system_control.apply_paddle_delta(delta);
}
pub fn set_mouse_paddle_enabled(&mut self, enabled: bool) {
self.mouse_paddle_enabled = enabled;
}
fn set_gamepad_input(&mut self, gamepad: usize, key: &InputCommand, button: &ControllerButton) {
let gamepad = &mut self.cpu_bus.system_control.gamepads[gamepad];
match button {
Up => { gamepad.up = self.input_state[&key].is_pressed(); }
Down => { gamepad.down = self.input_state[&key].is_pressed(); }
Left => { gamepad.left = self.input_state[&key].is_pressed(); }
Right => { gamepad.right = self.input_state[&key].is_pressed(); }
B => { gamepad.b = self.input_state[&key].is_pressed(); }
A => { gamepad.a = self.input_state[&key].is_pressed(); }
Start => { gamepad.start = self.input_state[&key].is_pressed(); }
C => { gamepad.c = self.input_state[&key].is_pressed(); }
}
}
}