use core::cmp::{Ordering, Ord, PartialEq, PartialOrd};
use core::convert::{TryInto, TryFrom};
use core::fmt::Debug;
use core::hash::{Hash, Hasher};
use core::marker::PhantomData;
use core::num::{NonZeroU8, NonZeroU16};
use core::ops::{Deref, DerefMut};
use z80emu::{Clock, host::cycles::*};
#[cfg(feature = "snapshot")]
use serde::{Serialize, Deserialize};
use crate::video::VideoFrame;
mod packed;
mod ops;
pub use packed::*;
pub use ops::*;
pub type FTs = i32;
pub type Ts = i16;
#[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct VideoTs {
pub vc: Ts,
pub hc: Ts,
}
#[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "snapshot", serde(try_from="FTs", into="FTs"))]
#[cfg_attr(feature = "snapshot", serde(bound = "V: VideoFrame"))]
#[derive(Copy, Debug)]
pub struct VFrameTs<V> {
pub ts: VideoTs,
_vframe: PhantomData<V>,
}
pub trait MemoryContention: Copy + Debug {
fn is_contended_address(self, address: u16) -> bool;
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct VFrameTsCounter<V, C> {
pub vts: VFrameTs<V>,
pub contention: C,
}
pub const HALT_VC_THRESHOLD: i16 = i16::max_value() >> 1;
const WAIT_STATES_THRESHOLD: u16 = i16::max_value() as u16 - 256;
impl VideoTs {
#[inline]
pub const fn new(vc: Ts, hc: Ts) -> Self {
VideoTs { vc, hc }
}
}
impl <V: VideoFrame> VFrameTs<V> {
pub const EOF: VFrameTs<V> = VFrameTs { ts: VideoTs {
vc: V::VSL_COUNT,
hc: 0
},
_vframe: PhantomData };
#[inline]
pub fn new(vc: Ts, hc: Ts) -> Self {
VFrameTs { ts: VideoTs::new(vc, hc), _vframe: PhantomData }
}
#[inline]
pub fn is_normalized(self) -> bool {
V::HTS_RANGE.contains(&self.ts.hc)
}
#[inline]
pub fn normalized(self) -> Self {
let VideoTs { mut vc, mut hc } = self.ts;
if hc < V::HTS_RANGE.start || hc >= V::HTS_RANGE.end {
let fhc: FTs = hc as FTs - if hc < 0 {
V::HTS_RANGE.end
}
else {
V::HTS_RANGE.start
} as FTs;
vc = vc.checked_add((fhc / V::HTS_COUNT as FTs) as Ts)
.expect("video timestamp overflow");
hc = fhc.rem_euclid(V::HTS_COUNT as FTs) as Ts + V::HTS_RANGE.start;
}
VFrameTs::new(vc, hc)
}
#[inline]
pub fn saturating_normalized(self) -> Self {
let VideoTs { mut vc, mut hc } = self.ts;
if hc < V::HTS_RANGE.start || hc >= V::HTS_RANGE.end {
let fhc: FTs = hc as FTs - if hc < 0 {
V::HTS_RANGE.end
}
else {
V::HTS_RANGE.start
} as FTs;
let dvc = (fhc / V::HTS_COUNT as FTs) as Ts;
if let Some(vc1) = vc.checked_add(dvc) {
vc = vc1;
hc = fhc.rem_euclid(V::HTS_COUNT as FTs) as Ts + V::HTS_RANGE.start;
}
else {
return if dvc < 0 { Self::min_value() } else { Self::max_value() };
}
}
VFrameTs::new(vc, hc)
}
#[inline(always)]
pub fn max_value() -> Self {
VFrameTs { ts: VideoTs { vc: Ts::max_value(), hc: V::HTS_RANGE.end - 1 },
_vframe: PhantomData }
}
#[inline(always)]
pub fn min_value() -> Self {
VFrameTs { ts: VideoTs { vc: Ts::min_value(), hc: V::HTS_RANGE.start },
_vframe: PhantomData }
}
#[inline(always)]
pub fn is_eof(self) -> bool {
self.vc >= V::VSL_COUNT
}
#[inline(always)]
pub fn wrap_frame(&mut self) {
self.ts.vc %= V::VSL_COUNT
}
#[inline]
pub fn saturating_sub_frame(self) -> Self {
let VideoTs { vc, hc } = self.ts;
let vc = vc.saturating_sub(V::VSL_COUNT);
VFrameTs::new(vc, hc)
}
#[inline]
pub fn from_tstates(ts: FTs) -> Self {
Self::try_from_tstates(ts).expect("video timestamp overflow")
}
#[inline]
pub fn try_from_tstates(ts: FTs) -> Option<Self> {
let mut vc = match (ts / V::HTS_COUNT as FTs).try_into() {
Ok(vc) => vc,
Err(..) => return None
};
let mut hc: Ts = (ts % V::HTS_COUNT as FTs) as Ts;
if hc >= V::HTS_RANGE.end {
hc -= V::HTS_COUNT;
vc += 1;
}
else if hc < V::HTS_RANGE.start {
hc += V::HTS_COUNT;
vc -= 1;
}
Some(VFrameTs::new(vc, hc))
}
#[inline]
pub fn into_tstates(self) -> FTs {
let VideoTs { vc, hc } = self.ts;
V::vc_hc_to_tstates(vc, hc)
}
#[inline]
pub fn into_frame_tstates(self, frames: u64) -> (u64, FTs) {
let ts = TimestampOps::into_tstates(self);
let frmdlt = ts / V::FRAME_TSTATES_COUNT;
let ufrmdlt = if ts < 0 { frmdlt - 1 } else { frmdlt } as u64;
let frames = frames.wrapping_add(ufrmdlt);
let ts = ts.rem_euclid(V::FRAME_TSTATES_COUNT);
(frames, ts)
}
#[inline]
fn set_hc_after_small_increment(&mut self, mut hc: Ts) {
if hc >= V::HTS_RANGE.end {
hc -= V::HTS_COUNT as Ts;
self.ts.vc += 1;
}
self.ts.hc = hc;
}
}
impl<V, C> VFrameTsCounter<V, C>
where V: VideoFrame,
C: MemoryContention
{
#[inline]
pub fn new(vc: Ts, hc: Ts, contention: C) -> Self {
let vts = VFrameTs::new(vc, hc).normalized();
VFrameTsCounter { vts, contention }
}
#[inline]
pub fn from_tstates(ts: FTs, contention: C) -> Self {
let vts = TimestampOps::from_tstates(ts);
VFrameTsCounter { vts, contention }
}
#[inline]
pub fn from_video_ts(vts: VideoTs, contention: C) -> Self {
let vts = VFrameTs::from(vts).normalized();
VFrameTsCounter { vts, contention }
}
#[inline]
pub fn from_vframe_ts(vfts: VFrameTs<V>, contention: C) -> Self {
let vts = vfts.normalized();
VFrameTsCounter { vts, contention }
}
#[inline]
pub fn is_contended_address(self, address: u16) -> bool {
self.contention.is_contended_address(address)
}
}
#[macro_export]
macro_rules! ula_io_contention {
($mc:expr, $port:expr, $hc:ident, $contention:path) => {
{
use $crate::z80emu::host::cycles::*;
if $mc.is_contended_address($port) {
$hc = $contention($hc) + IO_IORQ_LOW_TS as Ts;
if $port & 1 == 0 { $contention($hc) + (IO_CYCLE_TS - IO_IORQ_LOW_TS) as Ts
}
else { let mut hc1 = $hc;
for _ in 0..(IO_CYCLE_TS - IO_IORQ_LOW_TS) {
hc1 = $contention(hc1) + 1;
}
hc1
}
}
else {
$hc += IO_IORQ_LOW_TS as Ts;
if $port & 1 == 0 { $contention($hc) + (IO_CYCLE_TS - IO_IORQ_LOW_TS) as Ts
}
else { $hc + (IO_CYCLE_TS - IO_IORQ_LOW_TS) as Ts
}
}
}
};
}
impl<V: VideoFrame, C: MemoryContention> Clock for VFrameTsCounter<V, C> {
type Limit = Ts;
type Timestamp = VideoTs;
#[inline(always)]
fn is_past_limit(&self, limit: Self::Limit) -> bool {
self.vc >= limit
}
fn add_irq(&mut self, _pc: u16) -> Self::Timestamp {
self.vts.set_hc_after_small_increment(self.hc + IRQ_ACK_CYCLE_TS as Ts);
self.as_timestamp()
}
#[inline(always)]
fn add_no_mreq(&mut self, address: u16, add_ts: NonZeroU8) {
let mut hc = self.hc;
if V::is_contended_line_no_mreq(self.vc) && self.contention.is_contended_address(address) {
for _ in 0..add_ts.get() {
hc = V::contention(hc) + 1;
}
}
else {
hc += add_ts.get() as Ts;
}
self.vts.set_hc_after_small_increment(hc);
}
#[inline(always)]
fn add_m1(&mut self, address: u16) -> Self::Timestamp {
let hc = if V::is_contended_line_mreq(self.vc) && self.contention.is_contended_address(address) {
V::contention(self.hc)
}
else {
self.hc
};
self.vts.set_hc_after_small_increment(hc + M1_CYCLE_TS as Ts);
self.as_timestamp()
}
#[inline(always)]
fn add_mreq(&mut self, address: u16) -> Self::Timestamp {
let hc = if V::is_contended_line_mreq(self.vc) && self.contention.is_contended_address(address) {
V::contention(self.hc)
}
else {
self.hc
};
self.vts.set_hc_after_small_increment(hc + MEMRW_CYCLE_TS as Ts);
self.as_timestamp()
}
fn add_io(&mut self, port: u16) -> Self::Timestamp {
let VideoTs{ vc, mut hc } = self.as_timestamp();
let hc1 = if V::is_contended_line_no_mreq(vc) {
ula_io_contention!(self.contention, port, hc, V::contention)
}
else {
hc += IO_IORQ_LOW_TS as Ts;
hc + (IO_CYCLE_TS - IO_IORQ_LOW_TS) as Ts
};
let mut vtsc = *self;
vtsc.vts.set_hc_after_small_increment(hc);
self.vts.set_hc_after_small_increment(hc1);
vtsc.as_timestamp()
}
fn add_wait_states(&mut self, _bus: u16, wait_states: NonZeroU16) {
let ws = wait_states.get();
if ws > WAIT_STATES_THRESHOLD {
self.vc += HALT_VC_THRESHOLD;
}
else if ws < V::HTS_COUNT as u16 {
self.vts.set_hc_after_small_increment(self.hc + ws as i16);
}
else {
*self += ws as u32;
}
}
#[inline(always)]
fn as_timestamp(&self) -> Self::Timestamp {
***self
}
}
impl<V> Default for VFrameTs<V> {
fn default() -> Self {
VFrameTs::from(VideoTs::default())
}
}
impl<V> Clone for VFrameTs<V> {
fn clone(&self) -> Self {
VFrameTs::from(self.ts)
}
}
impl<V> Hash for VFrameTs<V> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.ts.hash(state);
}
}
impl<V> Eq for VFrameTs<V> {}
impl<V> PartialEq for VFrameTs<V> {
#[inline(always)]
fn eq(&self, other: &Self) -> bool {
self.ts == other.ts
}
}
impl<V> Ord for VFrameTs<V> {
#[inline(always)]
fn cmp(&self, other: &Self) -> Ordering {
self.ts.cmp(other)
}
}
impl<V> PartialOrd for VFrameTs<V> {
#[inline(always)]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<V: VideoFrame> From<VFrameTs<V>> for FTs {
#[inline(always)]
fn from(vfts: VFrameTs<V>) -> FTs {
VFrameTs::into_tstates(vfts)
}
}
impl<V: VideoFrame> TryFrom<FTs> for VFrameTs<V> {
type Error = &'static str;
fn try_from(ts: FTs) -> Result<Self, Self::Error> {
VFrameTs::try_from_tstates(ts).ok_or(
"out of range video timestamp conversion attempted")
}
}
impl<V> From<VFrameTs<V>> for VideoTs {
#[inline(always)]
fn from(vfts: VFrameTs<V>) -> VideoTs {
vfts.ts
}
}
impl<V> From<VideoTs> for VFrameTs<V> {
#[inline(always)]
fn from(ts: VideoTs) -> Self {
VFrameTs { ts, _vframe: PhantomData }
}
}
impl<V, C> From<VFrameTsCounter<V, C>> for VideoTs {
#[inline(always)]
fn from(vftsc: VFrameTsCounter<V, C>) -> VideoTs {
vftsc.vts.ts
}
}
impl<V, C> From<VFrameTsCounter<V, C>> for VFrameTs<V> {
#[inline(always)]
fn from(vftsc: VFrameTsCounter<V, C>) -> VFrameTs<V> {
vftsc.vts
}
}
impl<V> Deref for VFrameTs<V> {
type Target = VideoTs;
#[inline(always)]
fn deref(&self) -> &Self::Target {
&self.ts
}
}
impl<V> DerefMut for VFrameTs<V> {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.ts
}
}
impl<V, C> Deref for VFrameTsCounter<V, C> {
type Target = VFrameTs<V>;
#[inline(always)]
fn deref(&self) -> &Self::Target {
&self.vts
}
}
impl<V, C> DerefMut for VFrameTsCounter<V, C> {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.vts
}
}