Remove FrameExecutor indirection by inlining its logic into RuntimeHostLoop. Add set_video_mode() for NTSC/PAL switching, set_joypad2_buttons() for player 2 input, and fix mapper scanline IRQ test timing.
183 lines
4.6 KiB
Rust
183 lines
4.6 KiB
Rust
use crate::runtime::{
|
|
AudioMixer, FRAME_HEIGHT, FRAME_RGBA_BYTES, FRAME_WIDTH, NesRuntime, RuntimeError,
|
|
};
|
|
|
|
use super::clock::{FrameClock, NoopClock, PacingClock};
|
|
use super::config::HostConfig;
|
|
use super::io::{AudioOutput, InputProvider, VideoOutput};
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
#[non_exhaustive]
|
|
pub struct FrameExecution {
|
|
pub frame_number: u64,
|
|
pub audio_samples: usize,
|
|
}
|
|
|
|
pub struct RuntimeHostLoop<C = PacingClock> {
|
|
runtime: NesRuntime,
|
|
mixer: AudioMixer,
|
|
frame_buffer: Vec<u8>,
|
|
audio_buffer: Vec<f32>,
|
|
clock: C,
|
|
}
|
|
|
|
impl RuntimeHostLoop<PacingClock> {
|
|
pub fn new(runtime: NesRuntime, sample_rate: u32) -> Self {
|
|
let mixer = runtime.default_audio_mixer(sample_rate);
|
|
let clock = PacingClock::from_runtime(&runtime);
|
|
Self {
|
|
runtime,
|
|
mixer,
|
|
frame_buffer: vec![0; FRAME_RGBA_BYTES],
|
|
audio_buffer: Vec::new(),
|
|
clock,
|
|
}
|
|
}
|
|
|
|
pub fn with_config(
|
|
runtime: NesRuntime,
|
|
config: HostConfig,
|
|
) -> RuntimeHostLoop<Box<dyn FrameClock>> {
|
|
let mixer = runtime.default_audio_mixer(config.sample_rate);
|
|
let clock: Box<dyn FrameClock> = if config.pacing {
|
|
Box::new(PacingClock::from_runtime(&runtime))
|
|
} else {
|
|
Box::new(NoopClock)
|
|
};
|
|
RuntimeHostLoop {
|
|
runtime,
|
|
mixer,
|
|
frame_buffer: vec![0; FRAME_RGBA_BYTES],
|
|
audio_buffer: Vec::new(),
|
|
clock,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<C> RuntimeHostLoop<C>
|
|
where
|
|
C: FrameClock,
|
|
{
|
|
pub fn with_clock(runtime: NesRuntime, sample_rate: u32, clock: C) -> Self {
|
|
let mixer = runtime.default_audio_mixer(sample_rate);
|
|
Self {
|
|
runtime,
|
|
mixer,
|
|
frame_buffer: vec![0; FRAME_RGBA_BYTES],
|
|
audio_buffer: Vec::new(),
|
|
clock,
|
|
}
|
|
}
|
|
|
|
pub fn runtime(&self) -> &NesRuntime {
|
|
&self.runtime
|
|
}
|
|
|
|
pub fn runtime_mut(&mut self) -> &mut NesRuntime {
|
|
&mut self.runtime
|
|
}
|
|
|
|
pub fn into_runtime(self) -> NesRuntime {
|
|
self.runtime
|
|
}
|
|
|
|
pub fn mixer(&self) -> &AudioMixer {
|
|
&self.mixer
|
|
}
|
|
|
|
pub fn mixer_mut(&mut self) -> &mut AudioMixer {
|
|
&mut self.mixer
|
|
}
|
|
|
|
pub fn clock(&self) -> &C {
|
|
&self.clock
|
|
}
|
|
|
|
pub fn clock_mut(&mut self) -> &mut C {
|
|
&mut self.clock
|
|
}
|
|
|
|
pub fn run_frame<I, V, A>(
|
|
&mut self,
|
|
input: &mut I,
|
|
video: &mut V,
|
|
audio: &mut A,
|
|
) -> Result<FrameExecution, RuntimeError>
|
|
where
|
|
I: InputProvider,
|
|
V: VideoOutput,
|
|
A: AudioOutput,
|
|
{
|
|
let stats = self.run_frame_unpaced(input, video, audio)?;
|
|
self.clock.wait_next_frame();
|
|
Ok(stats)
|
|
}
|
|
|
|
pub fn run_frame_unpaced<I, V, A>(
|
|
&mut self,
|
|
input: &mut I,
|
|
video: &mut V,
|
|
audio: &mut A,
|
|
) -> Result<FrameExecution, RuntimeError>
|
|
where
|
|
I: InputProvider,
|
|
V: VideoOutput,
|
|
A: AudioOutput,
|
|
{
|
|
self.runtime.set_buttons(input.poll_buttons());
|
|
self.audio_buffer.clear();
|
|
|
|
self.runtime
|
|
.run_until_frame_complete_with_audio(&mut self.mixer, &mut self.audio_buffer)?;
|
|
self.runtime.render_frame_rgba(&mut self.frame_buffer)?;
|
|
|
|
audio.push_samples(&self.audio_buffer);
|
|
video.present_rgba(&self.frame_buffer, FRAME_WIDTH, FRAME_HEIGHT);
|
|
|
|
Ok(FrameExecution {
|
|
frame_number: self.runtime.frame_number(),
|
|
audio_samples: self.audio_buffer.len(),
|
|
})
|
|
}
|
|
|
|
pub fn run_frames<I, V, A>(
|
|
&mut self,
|
|
frames: usize,
|
|
input: &mut I,
|
|
video: &mut V,
|
|
audio: &mut A,
|
|
) -> Result<usize, RuntimeError>
|
|
where
|
|
I: InputProvider,
|
|
V: VideoOutput,
|
|
A: AudioOutput,
|
|
{
|
|
let mut total_samples = 0usize;
|
|
for _ in 0..frames {
|
|
total_samples =
|
|
total_samples.saturating_add(self.run_frame(input, video, audio)?.audio_samples);
|
|
}
|
|
Ok(total_samples)
|
|
}
|
|
|
|
pub fn run_frames_unpaced<I, V, A>(
|
|
&mut self,
|
|
frames: usize,
|
|
input: &mut I,
|
|
video: &mut V,
|
|
audio: &mut A,
|
|
) -> Result<usize, RuntimeError>
|
|
where
|
|
I: InputProvider,
|
|
V: VideoOutput,
|
|
A: AudioOutput,
|
|
{
|
|
let mut total_samples = 0usize;
|
|
for _ in 0..frames {
|
|
total_samples = total_samples
|
|
.saturating_add(self.run_frame_unpaced(input, video, audio)?.audio_samples);
|
|
}
|
|
Ok(total_samples)
|
|
}
|
|
}
|