Initial commit: NES emulator with GTK4 desktop frontend
Some checks failed
CI / rust (push) Has been cancelled
Some checks failed
CI / rust (push) Has been cancelled
Full NES emulation: CPU, PPU, APU, 47 mappers, iNES/NES 2.0 parsing. GTK4 desktop client with HeaderBar, pixel-perfect Cairo rendering, drag-and-drop ROM loading, and keyboard shortcuts. 187 tests covering core emulation, mappers, and runtime.
This commit is contained in:
154
src/runtime/host/loop_runner.rs
Normal file
154
src/runtime/host/loop_runner.rs
Normal file
@@ -0,0 +1,154 @@
|
||||
use crate::runtime::{NesRuntime, RuntimeError};
|
||||
|
||||
use super::clock::{FrameClock, NoopClock, PacingClock};
|
||||
use super::config::HostConfig;
|
||||
use super::executor::{FrameExecution, FrameExecutor};
|
||||
use super::io::{AudioOutput, InputProvider, VideoOutput};
|
||||
|
||||
pub struct RuntimeHostLoop<C = PacingClock> {
|
||||
runtime: NesRuntime,
|
||||
executor: FrameExecutor,
|
||||
clock: C,
|
||||
}
|
||||
|
||||
impl RuntimeHostLoop<PacingClock> {
|
||||
pub fn new(runtime: NesRuntime, sample_rate: u32) -> Self {
|
||||
let executor = FrameExecutor::from_runtime(&runtime, sample_rate);
|
||||
let clock = PacingClock::from_runtime(&runtime);
|
||||
Self {
|
||||
runtime,
|
||||
executor,
|
||||
clock,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_config(
|
||||
runtime: NesRuntime,
|
||||
config: HostConfig,
|
||||
) -> RuntimeHostLoop<Box<dyn FrameClock>> {
|
||||
let executor = FrameExecutor::from_runtime(&runtime, config.sample_rate);
|
||||
let clock: Box<dyn FrameClock> = if config.pacing {
|
||||
Box::new(PacingClock::from_runtime(&runtime))
|
||||
} else {
|
||||
Box::new(NoopClock)
|
||||
};
|
||||
RuntimeHostLoop {
|
||||
runtime,
|
||||
executor,
|
||||
clock,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> RuntimeHostLoop<C>
|
||||
where
|
||||
C: FrameClock,
|
||||
{
|
||||
pub fn with_clock(runtime: NesRuntime, sample_rate: u32, clock: C) -> Self {
|
||||
let executor = FrameExecutor::from_runtime(&runtime, sample_rate);
|
||||
Self {
|
||||
runtime,
|
||||
executor,
|
||||
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 executor(&self) -> &FrameExecutor {
|
||||
&self.executor
|
||||
}
|
||||
|
||||
pub fn executor_mut(&mut self) -> &mut FrameExecutor {
|
||||
&mut self.executor
|
||||
}
|
||||
|
||||
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.executor
|
||||
.execute_frame(&mut self.runtime, input, video, audio)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user