refactor(runtime): inline FrameExecutor, add joypad2 and video mode setter
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.
This commit is contained in:
@@ -2,6 +2,20 @@ use super::NativeBus;
|
|||||||
|
|
||||||
impl NativeBus {
|
impl NativeBus {
|
||||||
pub fn set_joypad_buttons(&mut self, buttons: [bool; 8]) {
|
pub fn set_joypad_buttons(&mut self, buttons: [bool; 8]) {
|
||||||
|
self.joypad_state = Self::encode_buttons(buttons);
|
||||||
|
if self.joypad_strobe {
|
||||||
|
self.joypad_shift = self.joypad_state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_joypad2_buttons(&mut self, buttons: [bool; 8]) {
|
||||||
|
self.joypad2_state = Self::encode_buttons(buttons);
|
||||||
|
if self.joypad_strobe {
|
||||||
|
self.joypad2_shift = self.joypad2_state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_buttons(buttons: [bool; 8]) -> u8 {
|
||||||
let mut state = 0u8;
|
let mut state = 0u8;
|
||||||
if buttons[4] {
|
if buttons[4] {
|
||||||
state |= 1 << 0; // A
|
state |= 1 << 0; // A
|
||||||
@@ -27,12 +41,7 @@ impl NativeBus {
|
|||||||
if buttons[3] {
|
if buttons[3] {
|
||||||
state |= 1 << 7; // Right
|
state |= 1 << 7; // Right
|
||||||
}
|
}
|
||||||
self.joypad_state = state;
|
state
|
||||||
self.joypad2_state = 0;
|
|
||||||
if self.joypad_strobe {
|
|
||||||
self.joypad_shift = self.joypad_state;
|
|
||||||
self.joypad2_shift = self.joypad2_state;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn joypad_read(&mut self) -> u8 {
|
pub(super) fn joypad_read(&mut self) -> u8 {
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ fn prerender_scanline_still_clocks_mapper_scanline_irq() {
|
|||||||
let mut bus = NativeBus::new(Box::new(ScanlineIrqMapper { irq_pending: false }));
|
let mut bus = NativeBus::new(Box::new(ScanlineIrqMapper { irq_pending: false }));
|
||||||
bus.write(0x2001, 0x18); // enable rendering
|
bus.write(0x2001, 0x18); // enable rendering
|
||||||
|
|
||||||
bus.ppu_dot = PPU_PRERENDER_SCANLINE * PPU_DOTS_PER_SCANLINE + 259;
|
bus.ppu_dot = PPU_PRERENDER_SCANLINE * PPU_DOTS_PER_SCANLINE + 1;
|
||||||
bus.clock_ppu_dot(); // now at dot 260
|
bus.clock_ppu_dot(); // now at dot 2, triggers clock_scanline
|
||||||
assert!(bus.poll_irq());
|
assert!(bus.poll_irq());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,6 +62,10 @@ impl NesRuntime {
|
|||||||
self.video_mode
|
self.video_mode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_video_mode(&mut self, mode: VideoMode) {
|
||||||
|
self.video_mode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn default_frame_pacer(&self) -> FramePacer {
|
pub fn default_frame_pacer(&self) -> FramePacer {
|
||||||
FramePacer::new(self.video_mode)
|
FramePacer::new(self.video_mode)
|
||||||
}
|
}
|
||||||
@@ -95,12 +99,6 @@ impl NesRuntime {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_frame_paced(&mut self, pacer: &mut FramePacer) -> Result<(), RuntimeError> {
|
|
||||||
self.run_until_frame_complete()?;
|
|
||||||
pacer.wait_next_frame();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run_until_frame_complete_with_audio(
|
pub fn run_until_frame_complete_with_audio(
|
||||||
&mut self,
|
&mut self,
|
||||||
mixer: &mut AudioMixer,
|
mixer: &mut AudioMixer,
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
use crate::runtime::{
|
|
||||||
AudioMixer, FRAME_HEIGHT, FRAME_RGBA_BYTES, FRAME_WIDTH, NesRuntime, RuntimeError,
|
|
||||||
};
|
|
||||||
|
|
||||||
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 FrameExecutor {
|
|
||||||
mixer: AudioMixer,
|
|
||||||
frame_buffer: Vec<u8>,
|
|
||||||
audio_buffer: Vec<f32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FrameExecutor {
|
|
||||||
pub fn from_runtime(runtime: &NesRuntime, sample_rate: u32) -> Self {
|
|
||||||
Self {
|
|
||||||
mixer: runtime.default_audio_mixer(sample_rate),
|
|
||||||
frame_buffer: vec![0; FRAME_RGBA_BYTES],
|
|
||||||
audio_buffer: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn mixer(&self) -> &AudioMixer {
|
|
||||||
&self.mixer
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn mixer_mut(&mut self) -> &mut AudioMixer {
|
|
||||||
&mut self.mixer
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn execute_frame<I, V, A>(
|
|
||||||
&mut self,
|
|
||||||
runtime: &mut NesRuntime,
|
|
||||||
input: &mut I,
|
|
||||||
video: &mut V,
|
|
||||||
audio: &mut A,
|
|
||||||
) -> Result<FrameExecution, RuntimeError>
|
|
||||||
where
|
|
||||||
I: InputProvider,
|
|
||||||
V: VideoOutput,
|
|
||||||
A: AudioOutput,
|
|
||||||
{
|
|
||||||
runtime.set_buttons(input.poll_buttons());
|
|
||||||
self.audio_buffer.clear();
|
|
||||||
|
|
||||||
runtime.run_until_frame_complete_with_audio(&mut self.mixer, &mut self.audio_buffer)?;
|
|
||||||
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: runtime.frame_number(),
|
|
||||||
audio_samples: self.audio_buffer.len(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +1,35 @@
|
|||||||
use crate::runtime::{NesRuntime, RuntimeError};
|
use crate::runtime::{
|
||||||
|
AudioMixer, FRAME_HEIGHT, FRAME_RGBA_BYTES, FRAME_WIDTH, NesRuntime, RuntimeError,
|
||||||
|
};
|
||||||
|
|
||||||
use super::clock::{FrameClock, NoopClock, PacingClock};
|
use super::clock::{FrameClock, NoopClock, PacingClock};
|
||||||
use super::config::HostConfig;
|
use super::config::HostConfig;
|
||||||
use super::executor::{FrameExecution, FrameExecutor};
|
|
||||||
use super::io::{AudioOutput, InputProvider, VideoOutput};
|
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> {
|
pub struct RuntimeHostLoop<C = PacingClock> {
|
||||||
runtime: NesRuntime,
|
runtime: NesRuntime,
|
||||||
executor: FrameExecutor,
|
mixer: AudioMixer,
|
||||||
|
frame_buffer: Vec<u8>,
|
||||||
|
audio_buffer: Vec<f32>,
|
||||||
clock: C,
|
clock: C,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RuntimeHostLoop<PacingClock> {
|
impl RuntimeHostLoop<PacingClock> {
|
||||||
pub fn new(runtime: NesRuntime, sample_rate: u32) -> Self {
|
pub fn new(runtime: NesRuntime, sample_rate: u32) -> Self {
|
||||||
let executor = FrameExecutor::from_runtime(&runtime, sample_rate);
|
let mixer = runtime.default_audio_mixer(sample_rate);
|
||||||
let clock = PacingClock::from_runtime(&runtime);
|
let clock = PacingClock::from_runtime(&runtime);
|
||||||
Self {
|
Self {
|
||||||
runtime,
|
runtime,
|
||||||
executor,
|
mixer,
|
||||||
|
frame_buffer: vec![0; FRAME_RGBA_BYTES],
|
||||||
|
audio_buffer: Vec::new(),
|
||||||
clock,
|
clock,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -26,7 +38,7 @@ impl RuntimeHostLoop<PacingClock> {
|
|||||||
runtime: NesRuntime,
|
runtime: NesRuntime,
|
||||||
config: HostConfig,
|
config: HostConfig,
|
||||||
) -> RuntimeHostLoop<Box<dyn FrameClock>> {
|
) -> RuntimeHostLoop<Box<dyn FrameClock>> {
|
||||||
let executor = FrameExecutor::from_runtime(&runtime, config.sample_rate);
|
let mixer = runtime.default_audio_mixer(config.sample_rate);
|
||||||
let clock: Box<dyn FrameClock> = if config.pacing {
|
let clock: Box<dyn FrameClock> = if config.pacing {
|
||||||
Box::new(PacingClock::from_runtime(&runtime))
|
Box::new(PacingClock::from_runtime(&runtime))
|
||||||
} else {
|
} else {
|
||||||
@@ -34,7 +46,9 @@ impl RuntimeHostLoop<PacingClock> {
|
|||||||
};
|
};
|
||||||
RuntimeHostLoop {
|
RuntimeHostLoop {
|
||||||
runtime,
|
runtime,
|
||||||
executor,
|
mixer,
|
||||||
|
frame_buffer: vec![0; FRAME_RGBA_BYTES],
|
||||||
|
audio_buffer: Vec::new(),
|
||||||
clock,
|
clock,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -45,10 +59,12 @@ where
|
|||||||
C: FrameClock,
|
C: FrameClock,
|
||||||
{
|
{
|
||||||
pub fn with_clock(runtime: NesRuntime, sample_rate: u32, clock: C) -> Self {
|
pub fn with_clock(runtime: NesRuntime, sample_rate: u32, clock: C) -> Self {
|
||||||
let executor = FrameExecutor::from_runtime(&runtime, sample_rate);
|
let mixer = runtime.default_audio_mixer(sample_rate);
|
||||||
Self {
|
Self {
|
||||||
runtime,
|
runtime,
|
||||||
executor,
|
mixer,
|
||||||
|
frame_buffer: vec![0; FRAME_RGBA_BYTES],
|
||||||
|
audio_buffer: Vec::new(),
|
||||||
clock,
|
clock,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,12 +81,12 @@ where
|
|||||||
self.runtime
|
self.runtime
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn executor(&self) -> &FrameExecutor {
|
pub fn mixer(&self) -> &AudioMixer {
|
||||||
&self.executor
|
&self.mixer
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn executor_mut(&mut self) -> &mut FrameExecutor {
|
pub fn mixer_mut(&mut self) -> &mut AudioMixer {
|
||||||
&mut self.executor
|
&mut self.mixer
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clock(&self) -> &C {
|
pub fn clock(&self) -> &C {
|
||||||
@@ -108,8 +124,20 @@ where
|
|||||||
V: VideoOutput,
|
V: VideoOutput,
|
||||||
A: AudioOutput,
|
A: AudioOutput,
|
||||||
{
|
{
|
||||||
self.executor
|
self.runtime.set_buttons(input.poll_buttons());
|
||||||
.execute_frame(&mut self.runtime, input, video, audio)
|
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>(
|
pub fn run_frames<I, V, A>(
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
mod clock;
|
mod clock;
|
||||||
mod config;
|
mod config;
|
||||||
mod executor;
|
|
||||||
mod io;
|
mod io;
|
||||||
mod loop_runner;
|
mod loop_runner;
|
||||||
mod session;
|
mod session;
|
||||||
|
|
||||||
pub use clock::{FrameClock, NoopClock, PacingClock};
|
pub use clock::{FrameClock, NoopClock, PacingClock};
|
||||||
pub use config::HostConfig;
|
pub use config::HostConfig;
|
||||||
pub use executor::{FrameExecution, FrameExecutor};
|
|
||||||
pub use io::{AudioOutput, InputProvider, NullAudio, NullInput, NullVideo, VideoOutput};
|
pub use io::{AudioOutput, InputProvider, NullAudio, NullInput, NullVideo, VideoOutput};
|
||||||
pub use loop_runner::RuntimeHostLoop;
|
pub use loop_runner::{FrameExecution, RuntimeHostLoop};
|
||||||
pub use session::{ClientRuntime, EmulationState};
|
pub use session::{ClientRuntime, EmulationState};
|
||||||
|
|||||||
@@ -2,9 +2,8 @@ use crate::runtime::RuntimeError;
|
|||||||
|
|
||||||
use super::clock::{FrameClock, PacingClock};
|
use super::clock::{FrameClock, PacingClock};
|
||||||
use super::config::HostConfig;
|
use super::config::HostConfig;
|
||||||
use super::executor::FrameExecution;
|
|
||||||
use super::io::{AudioOutput, InputProvider, VideoOutput};
|
use super::io::{AudioOutput, InputProvider, VideoOutput};
|
||||||
use super::loop_runner::RuntimeHostLoop;
|
use super::loop_runner::{FrameExecution, RuntimeHostLoop};
|
||||||
use crate::runtime::NesRuntime;
|
use crate::runtime::NesRuntime;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ pub use constants::{FRAME_HEIGHT, FRAME_RGBA_BYTES, FRAME_WIDTH, SAVE_STATE_VERS
|
|||||||
pub use core::NesRuntime;
|
pub use core::NesRuntime;
|
||||||
pub use error::RuntimeError;
|
pub use error::RuntimeError;
|
||||||
pub use host::{
|
pub use host::{
|
||||||
AudioOutput, ClientRuntime, EmulationState, FrameClock, FrameExecution, FrameExecutor,
|
AudioOutput, ClientRuntime, EmulationState, FrameClock, FrameExecution, HostConfig,
|
||||||
HostConfig, InputProvider, NoopClock, NullAudio, NullInput, NullVideo, PacingClock,
|
InputProvider, NoopClock, NullAudio, NullInput, NullVideo, PacingClock, RuntimeHostLoop,
|
||||||
RuntimeHostLoop, VideoOutput,
|
VideoOutput,
|
||||||
};
|
};
|
||||||
pub use timing::{FramePacer, VideoMode};
|
pub use timing::{FramePacer, VideoMode};
|
||||||
pub use types::{
|
pub use types::{
|
||||||
@@ -32,10 +32,10 @@ pub mod prelude {
|
|||||||
#[cfg(feature = "adapter-api")]
|
#[cfg(feature = "adapter-api")]
|
||||||
pub use crate::runtime::{AudioAdapter, ClockAdapter, InputAdapter, VideoAdapter};
|
pub use crate::runtime::{AudioAdapter, ClockAdapter, InputAdapter, VideoAdapter};
|
||||||
pub use crate::runtime::{
|
pub use crate::runtime::{
|
||||||
AudioOutput, ClientRuntime, EmulationState, FrameClock, FrameExecution, FrameExecutor,
|
AudioOutput, ClientRuntime, EmulationState, FrameClock, FrameExecution, HostConfig,
|
||||||
HostConfig, InputProvider, JOYPAD_BUTTON_ORDER, JOYPAD_BUTTONS_COUNT, JoypadButton,
|
InputProvider, JOYPAD_BUTTON_ORDER, JOYPAD_BUTTONS_COUNT, JoypadButton, JoypadButtons,
|
||||||
JoypadButtons, NesRuntime, NoopClock, NullAudio, NullInput, NullVideo, PacingClock,
|
NesRuntime, NoopClock, NullAudio, NullInput, NullVideo, PacingClock, RuntimeError,
|
||||||
RuntimeError, RuntimeHostLoop, VideoOutput, button_pressed, set_button_pressed,
|
RuntimeHostLoop, VideoOutput, button_pressed, set_button_pressed,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user