use core::time::{Duration};
use super::clock::{TClock, Ts};
use super::bus::BusDevice;
use z80emu::{Cpu, Memory, Io, Clock, BreakCause};
#[allow(unused_imports)]
use log::{error, warn, info, debug, trace, Level};
pub struct FrameRunner<const EXT_CLOCK_HZ: u32, const TIME_FRAME_HZ: u32> {
pub(crate) clock: TClock,
pub(crate) limit: Ts,
pub(crate) frame_tstates: Ts,
}
pub const fn frame_duration(frame_hz: u32) -> core::time::Duration {
Duration::from_nanos(1e9 as u64 / frame_hz as u64)
}
impl<const EXT_HZ: u32, const FRAME_HZ: u32> FrameRunner<EXT_HZ, FRAME_HZ> {
pub const EXT_CLOCK_HZ: u32 = EXT_HZ;
pub const TIME_FRAME_HZ: u32 = FRAME_HZ;
pub const FRAME_DURATION: Duration = frame_duration(FRAME_HZ);
pub const fn clock_is_valid(clock_hz: Ts) -> bool {
clock_hz % (EXT_HZ * 2) == 0 && clock_hz >= EXT_HZ * 10
}
pub fn external_clock_tstates(&self) -> u32 {
self.clock.clock_hz() / EXT_HZ
}
pub fn new(clock_hz: Ts) -> Self {
assert!(Self::clock_is_valid(clock_hz));
let clock = TClock::new(clock_hz);
let frame_tstates: u32 = clock.clock_hz() / FRAME_HZ;
info!("frame: {} T-states", frame_tstates);
FrameRunner { clock, limit: 0, frame_tstates }
}
pub fn frame_duration() -> Duration {
Self::FRAME_DURATION
}
pub fn start<C: Cpu, M>(&mut self, cpu: &mut C, bus: &mut M)
where M: BusDevice<Timestamp=Ts>
{
self.clock.reset();
cpu.reset();
bus.reset(self.clock.as_timestamp());
self.limit = self.clock.as_timestamp();
}
pub fn step<C: Cpu, M>(&mut self, cpu: &mut C, bus: &mut M) -> Ts
where M: Memory<Timestamp=Ts>
+ Io<Timestamp=Ts>
+ BusDevice<Timestamp=Ts>
{
if self.clock.check_wrap_second() {
let clock_hz = self.clock.clock_hz();
self.limit -= clock_hz;
bus.next_second(clock_hz);
}
let start_ts = self.clock.as_timestamp();
self.limit += self.frame_tstates;
loop {
match cpu.execute_with_limit(bus, &mut self.clock, self.limit) {
Ok(()) => break,
Err(BreakCause::Halt) => {
}
Err(cause) => {
panic!("no break request was expected: {}", cause);
}
}
}
bus.frame_end(self.clock.as_timestamp());
self.clock.as_timestamp().wrapping_sub(start_ts)
}
pub fn reset<C: Cpu, M>(&mut self, cpu: &mut C, bus: &mut M)
where M: BusDevice<Timestamp=Ts>
{
cpu.reset();
bus.reset(self.clock.as_timestamp());
}
pub fn nmi<C: Cpu, M>(&mut self, cpu: &mut C, bus: &mut M) -> Option<Ts>
where M: Memory<Timestamp=Ts> + Io<Timestamp=Ts>
{
let start_ts = self.clock.as_timestamp();
if cpu.nmi(bus, &mut self.clock) {
return Some(self.clock.as_timestamp().wrapping_sub(start_ts))
}
None
}
}