From d113228f1be7c6e92edc46042b857704034b3420 Mon Sep 17 00:00:00 2001 From: "se.cherkasov" Date: Wed, 18 Mar 2026 15:11:56 +0300 Subject: [PATCH] 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. --- src/native_core/bus/joypad.rs | 21 +++++--- src/native_core/bus/tests/mapper_timing.rs | 4 +- src/runtime/core.rs | 10 ++-- src/runtime/host/executor.rs | 63 ---------------------- src/runtime/host/loop_runner.rs | 58 ++++++++++++++------ src/runtime/host/mod.rs | 4 +- src/runtime/host/session.rs | 3 +- src/runtime/mod.rs | 14 ++--- 8 files changed, 73 insertions(+), 104 deletions(-) delete mode 100644 src/runtime/host/executor.rs diff --git a/src/native_core/bus/joypad.rs b/src/native_core/bus/joypad.rs index 05774f0..0948c0e 100644 --- a/src/native_core/bus/joypad.rs +++ b/src/native_core/bus/joypad.rs @@ -2,6 +2,20 @@ use super::NativeBus; impl NativeBus { 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; if buttons[4] { state |= 1 << 0; // A @@ -27,12 +41,7 @@ impl NativeBus { if buttons[3] { state |= 1 << 7; // Right } - self.joypad_state = state; - self.joypad2_state = 0; - if self.joypad_strobe { - self.joypad_shift = self.joypad_state; - self.joypad2_shift = self.joypad2_state; - } + state } pub(super) fn joypad_read(&mut self) -> u8 { diff --git a/src/native_core/bus/tests/mapper_timing.rs b/src/native_core/bus/tests/mapper_timing.rs index 21bb9d7..6bf07e2 100644 --- a/src/native_core/bus/tests/mapper_timing.rs +++ b/src/native_core/bus/tests/mapper_timing.rs @@ -5,8 +5,8 @@ fn prerender_scanline_still_clocks_mapper_scanline_irq() { let mut bus = NativeBus::new(Box::new(ScanlineIrqMapper { irq_pending: false })); bus.write(0x2001, 0x18); // enable rendering - bus.ppu_dot = PPU_PRERENDER_SCANLINE * PPU_DOTS_PER_SCANLINE + 259; - bus.clock_ppu_dot(); // now at dot 260 + bus.ppu_dot = PPU_PRERENDER_SCANLINE * PPU_DOTS_PER_SCANLINE + 1; + bus.clock_ppu_dot(); // now at dot 2, triggers clock_scanline assert!(bus.poll_irq()); } diff --git a/src/runtime/core.rs b/src/runtime/core.rs index 685d442..775d5ce 100644 --- a/src/runtime/core.rs +++ b/src/runtime/core.rs @@ -62,6 +62,10 @@ impl NesRuntime { self.video_mode } + pub fn set_video_mode(&mut self, mode: VideoMode) { + self.video_mode = mode; + } + pub fn default_frame_pacer(&self) -> FramePacer { FramePacer::new(self.video_mode) } @@ -95,12 +99,6 @@ impl NesRuntime { 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( &mut self, mixer: &mut AudioMixer, diff --git a/src/runtime/host/executor.rs b/src/runtime/host/executor.rs deleted file mode 100644 index 942afa3..0000000 --- a/src/runtime/host/executor.rs +++ /dev/null @@ -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, - audio_buffer: Vec, -} - -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( - &mut self, - runtime: &mut NesRuntime, - input: &mut I, - video: &mut V, - audio: &mut A, - ) -> Result - 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(), - }) - } -} diff --git a/src/runtime/host/loop_runner.rs b/src/runtime/host/loop_runner.rs index cc8eb4b..ec94b3e 100644 --- a/src/runtime/host/loop_runner.rs +++ b/src/runtime/host/loop_runner.rs @@ -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::config::HostConfig; -use super::executor::{FrameExecution, FrameExecutor}; 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 { runtime: NesRuntime, - executor: FrameExecutor, + mixer: AudioMixer, + frame_buffer: Vec, + audio_buffer: Vec, clock: C, } impl RuntimeHostLoop { 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); Self { runtime, - executor, + mixer, + frame_buffer: vec![0; FRAME_RGBA_BYTES], + audio_buffer: Vec::new(), clock, } } @@ -26,7 +38,7 @@ impl RuntimeHostLoop { runtime: NesRuntime, config: HostConfig, ) -> RuntimeHostLoop> { - let executor = FrameExecutor::from_runtime(&runtime, config.sample_rate); + let mixer = runtime.default_audio_mixer(config.sample_rate); let clock: Box = if config.pacing { Box::new(PacingClock::from_runtime(&runtime)) } else { @@ -34,7 +46,9 @@ impl RuntimeHostLoop { }; RuntimeHostLoop { runtime, - executor, + mixer, + frame_buffer: vec![0; FRAME_RGBA_BYTES], + audio_buffer: Vec::new(), clock, } } @@ -45,10 +59,12 @@ where C: FrameClock, { 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 { runtime, - executor, + mixer, + frame_buffer: vec![0; FRAME_RGBA_BYTES], + audio_buffer: Vec::new(), clock, } } @@ -65,12 +81,12 @@ where self.runtime } - pub fn executor(&self) -> &FrameExecutor { - &self.executor + pub fn mixer(&self) -> &AudioMixer { + &self.mixer } - pub fn executor_mut(&mut self) -> &mut FrameExecutor { - &mut self.executor + pub fn mixer_mut(&mut self) -> &mut AudioMixer { + &mut self.mixer } pub fn clock(&self) -> &C { @@ -108,8 +124,20 @@ where V: VideoOutput, A: AudioOutput, { - self.executor - .execute_frame(&mut self.runtime, input, video, audio) + 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( diff --git a/src/runtime/host/mod.rs b/src/runtime/host/mod.rs index 909cfff..3b5f3db 100644 --- a/src/runtime/host/mod.rs +++ b/src/runtime/host/mod.rs @@ -1,13 +1,11 @@ mod clock; mod config; -mod executor; mod io; mod loop_runner; mod session; pub use clock::{FrameClock, NoopClock, PacingClock}; pub use config::HostConfig; -pub use executor::{FrameExecution, FrameExecutor}; 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}; diff --git a/src/runtime/host/session.rs b/src/runtime/host/session.rs index da049cb..55d2032 100644 --- a/src/runtime/host/session.rs +++ b/src/runtime/host/session.rs @@ -2,9 +2,8 @@ use crate::runtime::RuntimeError; use super::clock::{FrameClock, PacingClock}; use super::config::HostConfig; -use super::executor::FrameExecution; use super::io::{AudioOutput, InputProvider, VideoOutput}; -use super::loop_runner::RuntimeHostLoop; +use super::loop_runner::{FrameExecution, RuntimeHostLoop}; use crate::runtime::NesRuntime; #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/src/runtime/mod.rs b/src/runtime/mod.rs index fc92fdf..7edf647 100644 --- a/src/runtime/mod.rs +++ b/src/runtime/mod.rs @@ -18,9 +18,9 @@ pub use constants::{FRAME_HEIGHT, FRAME_RGBA_BYTES, FRAME_WIDTH, SAVE_STATE_VERS pub use core::NesRuntime; pub use error::RuntimeError; pub use host::{ - AudioOutput, ClientRuntime, EmulationState, FrameClock, FrameExecution, FrameExecutor, - HostConfig, InputProvider, NoopClock, NullAudio, NullInput, NullVideo, PacingClock, - RuntimeHostLoop, VideoOutput, + AudioOutput, ClientRuntime, EmulationState, FrameClock, FrameExecution, HostConfig, + InputProvider, NoopClock, NullAudio, NullInput, NullVideo, PacingClock, RuntimeHostLoop, + VideoOutput, }; pub use timing::{FramePacer, VideoMode}; pub use types::{ @@ -32,10 +32,10 @@ pub mod prelude { #[cfg(feature = "adapter-api")] pub use crate::runtime::{AudioAdapter, ClockAdapter, InputAdapter, VideoAdapter}; pub use crate::runtime::{ - AudioOutput, ClientRuntime, EmulationState, FrameClock, FrameExecution, FrameExecutor, - HostConfig, InputProvider, JOYPAD_BUTTON_ORDER, JOYPAD_BUTTONS_COUNT, JoypadButton, - JoypadButtons, NesRuntime, NoopClock, NullAudio, NullInput, NullVideo, PacingClock, - RuntimeError, RuntimeHostLoop, VideoOutput, button_pressed, set_button_pressed, + AudioOutput, ClientRuntime, EmulationState, FrameClock, FrameExecution, HostConfig, + InputProvider, JOYPAD_BUTTON_ORDER, JOYPAD_BUTTONS_COUNT, JoypadButton, JoypadButtons, + NesRuntime, NoopClock, NullAudio, NullInput, NullVideo, PacingClock, RuntimeError, + RuntimeHostLoop, VideoOutput, button_pressed, set_button_pressed, }; }