spectrusty_core/
clock.rs

1/*
2    Copyright (C) 2020-2022  Rafal Michalski
3
4    This file is part of SPECTRUSTY, a Rust library for building emulators.
5
6    For the full copyright notice, see the lib.rs file.
7*/
8//! T-state timestamp types and counters.
9use core::cmp::{Ordering, Ord, PartialEq, PartialOrd};
10use core::convert::{TryInto, TryFrom};
11use core::fmt::Debug;
12use core::hash::{Hash, Hasher};
13use core::marker::PhantomData;
14use core::num::{NonZeroU8, NonZeroU16};
15use core::ops::{Deref, DerefMut};
16
17use z80emu::{Clock, host::cycles::*};
18#[cfg(feature = "snapshot")]
19use serde::{Serialize, Deserialize};
20
21use crate::video::VideoFrame;
22
23mod packed;
24mod ops;
25pub use packed::*;
26pub use ops::*;
27
28/// A linear T-state timestamp type.
29pub type FTs = i32;
30/// A type used for a horizontal T-state timestamp or a video scanline index for [VideoTs].
31pub type Ts = i16;
32
33/// A timestamp type that consists of two video counters: vertical and horizontal.
34///
35/// `VideoTs { vc: 0, hc: 0 }` marks the start of the video frame.
36#[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
37#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
38pub struct VideoTs {
39    /// A vertical counter - a video scan-line index.
40    pub vc: Ts,
41    /// A horizontal counter - measured in T-states.
42    pub hc: Ts,
43}
44
45/// A [VideoTs] timestamp wrapper with a constraint to the `V:` [VideoFrame],
46/// implementing methods and traits for timestamp calculations.
47#[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
48#[cfg_attr(feature = "snapshot", serde(try_from="FTs", into="FTs"))]
49#[cfg_attr(feature = "snapshot", serde(bound = "V: VideoFrame"))]
50#[derive(Copy, Debug)]
51pub struct VFrameTs<V> {
52    /// The current value of the timestamp.
53    pub ts: VideoTs,
54    _vframe: PhantomData<V>,
55}
56
57/// A trait used by [VFrameTsCounter] for checking if an `address` is a contended one.
58pub trait MemoryContention: Copy + Debug {
59    fn is_contended_address(self, address: u16) -> bool;
60}
61
62/// A generic [`VFrameTs<V>`][VFrameTs] based T-states counter.
63///
64/// Implements [Clock] for counting cycles when code is being executed by [z80emu::Cpu].
65///
66/// Inserts additional T-states according to the contention model specified by generic
67/// parameters: `V:` [VideoFrame] and `C:` [MemoryContention].
68///
69/// [Clock]: /z80emu/%2A/z80emu/host/trait.Clock.html
70/// [z80emu::Cpu]: /z80emu/%2A/z80emu/trait.Cpu.html
71#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
72pub struct VFrameTsCounter<V, C>  {
73    /// The current value of the counter.
74    pub vts: VFrameTs<V>,
75    /// An instance implementing a [MemoryContention] trait.
76    pub contention: C,
77}
78
79/// If a vertical counter of [VideoTs] exceeds this value, it signals the control unit
80/// to emulate hanging CPU indefinitely.
81pub const HALT_VC_THRESHOLD: i16 = i16::max_value() >> 1;
82
83const WAIT_STATES_THRESHOLD: u16 = i16::max_value() as u16 - 256;
84
85impl VideoTs {
86    #[inline]
87    pub const fn new(vc: Ts, hc: Ts) -> Self {
88        VideoTs { vc, hc }
89    }
90}
91
92impl <V: VideoFrame> VFrameTs<V> {
93    /// The end-of-frame timestamp, equal to the total number of T-states per frame.
94    pub const EOF: VFrameTs<V> = VFrameTs { ts: VideoTs {
95                                                vc: V::VSL_COUNT,
96                                                hc: 0
97                                            },
98                                             _vframe: PhantomData };
99    /// Constructs a new `VFrameTs` from the given vertical and horizontal counter values.
100    ///
101    /// __Note__: The returned `VFrameTs` is not normalized.
102    #[inline]
103    pub fn new(vc: Ts, hc: Ts) -> Self {
104        VFrameTs { ts: VideoTs::new(vc, hc), _vframe: PhantomData }
105    }
106    /// Returns `true` if a video timestamp is normalized. Otherwise returns `false`.
107    #[inline]
108    pub fn is_normalized(self) -> bool {
109        V::HTS_RANGE.contains(&self.ts.hc)
110    }
111    /// Normalizes self with a horizontal counter within the allowed range and a scan line
112    /// counter adjusted accordingly.
113    ///
114    /// # Panics
115    /// Panics when an attempt to normalize leads to an overflow of the capacity of [VideoTs].
116    #[inline]
117    pub fn normalized(self) -> Self {
118        let VideoTs { mut vc, mut hc } = self.ts;
119        if hc < V::HTS_RANGE.start || hc >= V::HTS_RANGE.end {
120            let fhc: FTs = hc as FTs - if hc < 0 {
121                V::HTS_RANGE.end
122            }
123            else {
124                V::HTS_RANGE.start
125            } as FTs;
126            vc = vc.checked_add((fhc / V::HTS_COUNT as FTs) as Ts)
127                   .expect("video timestamp overflow");
128            hc = fhc.rem_euclid(V::HTS_COUNT as FTs) as Ts + V::HTS_RANGE.start;
129        }
130        VFrameTs::new(vc, hc)
131    }
132    /// Returns a video timestamp with a horizontal counter within the allowed range and a scan line
133    /// counter adjusted accordingly. Saturates at [VFrameTs::min_value] or [VFrameTs::max_value].
134    #[inline]
135    pub fn saturating_normalized(self) -> Self {
136        let VideoTs { mut vc, mut hc } = self.ts;
137        if hc < V::HTS_RANGE.start || hc >= V::HTS_RANGE.end {
138            let fhc: FTs = hc as FTs - if hc < 0 {
139                V::HTS_RANGE.end
140            }
141            else {
142                V::HTS_RANGE.start
143            } as FTs;
144            let dvc = (fhc / V::HTS_COUNT as FTs) as Ts;
145            if let Some(vc1) = vc.checked_add(dvc) {
146                vc = vc1;
147                hc = fhc.rem_euclid(V::HTS_COUNT as FTs) as Ts + V::HTS_RANGE.start;
148            }
149            else {
150                return if dvc < 0 { Self::min_value() } else { Self::max_value() };
151            }
152        }
153        VFrameTs::new(vc, hc)
154    }
155    /// Returns the largest value that can be represented by a normalized timestamp.
156    #[inline(always)]
157    pub fn max_value() -> Self {
158        VFrameTs { ts: VideoTs { vc: Ts::max_value(), hc: V::HTS_RANGE.end - 1 },
159                   _vframe: PhantomData }
160    }
161    /// Returns the smallest value that can be represented by a normalized timestamp.
162    #[inline(always)]
163    pub fn min_value() -> Self {
164        VFrameTs { ts: VideoTs { vc: Ts::min_value(), hc: V::HTS_RANGE.start },
165                   _vframe: PhantomData }
166    }
167    /// Returns `true` if the counter value is past or near the end of a frame. Otherwise returns `false`.
168    ///
169    /// Specifically, the condition is met if the vertical counter is equal to or greater than [VideoFrame::VSL_COUNT].
170    #[inline(always)]
171    pub fn is_eof(self) -> bool {
172        self.vc >= V::VSL_COUNT
173    }
174    /// Ensures the vertical counter is in the range: `(-VSL_COUNT, VSL_COUNT)` by calculating
175    /// a remainder of the division of the vertical counter by [VideoFrame::VSL_COUNT].
176    #[inline(always)]
177    pub fn wrap_frame(&mut self) {
178        self.ts.vc %= V::VSL_COUNT
179    }
180    /// Returns a video timestamp after subtracting the total number of frame video scanlines
181    /// from the scan line counter.
182    #[inline]
183    pub fn saturating_sub_frame(self) -> Self {
184        let VideoTs { vc, hc } = self.ts;
185        let vc = vc.saturating_sub(V::VSL_COUNT);
186        VFrameTs::new(vc, hc)
187    }
188    /// Returns a normalized timestamp from the given number of T-states.
189    ///
190    /// # Panics
191    /// Panics when the given `ts` overflows the capacity of the timestamp.
192    #[inline]
193    pub fn from_tstates(ts: FTs) -> Self {
194        Self::try_from_tstates(ts).expect("video timestamp overflow")
195    }
196    /// On success returns a normalized timestamp from the given number of T-states.
197    ///
198    /// Returns `None` when the given `ts` overflows the capacity of the timestamp.
199    #[inline]
200    pub fn try_from_tstates(ts: FTs) -> Option<Self> {
201        let mut vc = match (ts / V::HTS_COUNT as FTs).try_into() {
202            Ok(vc) => vc,
203            Err(..) => return None
204        };
205        let mut hc: Ts = (ts % V::HTS_COUNT as FTs) as Ts;
206        if hc >= V::HTS_RANGE.end {
207            hc -= V::HTS_COUNT;
208            vc += 1;
209        }
210        else if hc < V::HTS_RANGE.start {
211            hc += V::HTS_COUNT;
212            vc -= 1;
213        }
214        Some(VFrameTs::new(vc, hc))
215    }
216    /// Converts the timestamp to FTs.
217    #[inline]
218    pub fn into_tstates(self) -> FTs {
219        let VideoTs { vc, hc } = self.ts;
220        V::vc_hc_to_tstates(vc, hc)
221    }
222    /// Returns a tuple with an adjusted frame counter and with the frame-normalized timestamp as
223    /// the number of T-states measured from the start of the frame.
224    ///
225    /// The frame starts when the horizontal and vertical counter are both 0.
226    ///
227    /// The returned timestamp value is in the range [0, [VideoFrame::FRAME_TSTATES_COUNT]).
228    #[inline]
229    pub fn into_frame_tstates(self, frames: u64) -> (u64, FTs) {
230        let ts = TimestampOps::into_tstates(self);
231        let frmdlt = ts / V::FRAME_TSTATES_COUNT;
232        let ufrmdlt = if ts < 0 { frmdlt - 1 } else { frmdlt } as u64;
233        let frames = frames.wrapping_add(ufrmdlt);
234        let ts = ts.rem_euclid(V::FRAME_TSTATES_COUNT);
235        (frames, ts)
236    }
237
238    #[inline]
239    fn set_hc_after_small_increment(&mut self, mut hc: Ts) {
240        if hc >= V::HTS_RANGE.end {
241            hc -= V::HTS_COUNT as Ts;
242            self.ts.vc += 1;
243        }
244        self.ts.hc = hc;
245    }
246}
247
248impl<V, C> VFrameTsCounter<V, C>
249    where V: VideoFrame,
250          C: MemoryContention
251{
252    /// Constructs a new and normalized `VFrameTsCounter` from the given vertical and horizontal counter values.
253    ///
254    /// # Panics
255    /// Panics when the given values lead to an overflow of the capacity of [VideoTs].
256    #[inline]
257    pub fn new(vc: Ts, hc: Ts, contention: C) -> Self {
258        let vts = VFrameTs::new(vc, hc).normalized();
259        VFrameTsCounter { vts, contention }
260    }
261    /// Builds a normalized [VFrameTsCounter] from the given count of T-states.
262    ///
263    /// # Panics
264    ///
265    /// Panics when the given `ts` overflows the capacity of [VideoTs].
266    #[inline]
267    pub fn from_tstates(ts: FTs, contention: C) -> Self {
268        let vts = TimestampOps::from_tstates(ts);
269        VFrameTsCounter { vts, contention }
270    }
271    /// Builds a normalized [VFrameTsCounter] from the given count of T-states.
272    ///
273    /// # Panics
274    ///
275    /// Panics when the given `ts` overflows the capacity of [VideoTs].
276    #[inline]
277    pub fn from_video_ts(vts: VideoTs, contention: C) -> Self {
278        let vts = VFrameTs::from(vts).normalized();
279        VFrameTsCounter { vts, contention }
280    }
281    /// Builds a normalized [VFrameTsCounter] from the given count of T-states.
282    ///
283    /// # Panics
284    ///
285    /// Panics when the given `ts` overflows the capacity of [VideoTs].
286    #[inline]
287    pub fn from_vframe_ts(vfts: VFrameTs<V>, contention: C) -> Self {
288        let vts = vfts.normalized();
289        VFrameTsCounter { vts, contention }
290    }
291
292    #[inline]
293    pub fn is_contended_address(self, address: u16) -> bool {
294        self.contention.is_contended_address(address)
295    }
296}
297
298/// This macro is used to implement the ULA I/O contention scheme, for [z80emu::Clock::add_io] method of
299/// [VFrameTsCounter].
300/// It's being exported for the purpose of performing FUSE tests.
301///
302/// * $mc should be a type implementing [MemoryContention] trait.
303/// * $port is a port address.
304/// * $hc is an identifier of a mutable variable containing the `hc` property of a `VideoTs` timestamp.
305/// * $contention should be a path to the [VideoFrame::contention] function.
306///
307/// The macro returns a horizontal timestamp pointing after the whole I/O cycle is over.
308/// The `hc` variable is modified to contain a horizontal timestamp indicating when the data R/W operation 
309/// takes place.
310#[macro_export]
311macro_rules! ula_io_contention {
312    ($mc:expr, $port:expr, $hc:ident, $contention:path) => {
313        {
314            use $crate::z80emu::host::cycles::*;
315            if $mc.is_contended_address($port) {
316                $hc = $contention($hc) + IO_IORQ_LOW_TS as Ts;
317                if $port & 1 == 0 { // C:1, C:3
318                    $contention($hc) + (IO_CYCLE_TS - IO_IORQ_LOW_TS) as Ts
319                }
320                else { // C:1, C:1, C:1, C:1
321                    let mut hc1 = $hc;
322                    for _ in 0..(IO_CYCLE_TS - IO_IORQ_LOW_TS) {
323                        hc1 = $contention(hc1) + 1;
324                    }
325                    hc1
326                }
327            }
328            else {
329                $hc += IO_IORQ_LOW_TS as Ts;
330                if $port & 1 == 0 { // N:1 C:3
331                    $contention($hc) + (IO_CYCLE_TS - IO_IORQ_LOW_TS) as Ts
332                }
333                else { // N:4
334                    $hc + (IO_CYCLE_TS - IO_IORQ_LOW_TS) as Ts
335                }
336            }
337        }
338    };
339}
340/*
341impl<V: VideoFrame> Clock for VFrameTs<V> {
342    type Limit = Ts;
343    type Timestamp = VideoTs;
344
345    #[inline(always)]
346    fn is_past_limit(&self, limit: Self::Limit) -> bool {
347        self.vc >= limit
348    }
349
350    fn add_irq(&mut self, _pc: u16) -> Self::Timestamp {
351        self.set_hc_after_small_increment(self.hc + IRQ_ACK_CYCLE_TS as Ts);
352        self.as_timestamp()
353    }
354
355    fn add_no_mreq(&mut self, _address: u16, add_ts: NonZeroU8) {
356        let hc = self.hc + add_ts.get() as Ts;
357        self.set_hc_after_small_increment(hc);
358    }
359
360    fn add_m1(&mut self, _address: u16) -> Self::Timestamp {
361        self.set_hc_after_small_increment(self.hc + M1_CYCLE_TS as Ts);
362        self.as_timestamp()
363    }
364
365    fn add_mreq(&mut self, _address: u16) -> Self::Timestamp {
366        self.set_hc_after_small_increment(self.hc + MEMRW_CYCLE_TS as Ts);
367        self.as_timestamp()
368    }
369
370    fn add_io(&mut self, _port: u16) -> Self::Timestamp {
371        let hc = self.hc + IO_IORQ_LOW_TS as Ts;
372        let hc1 = hc + (IO_CYCLE_TS - IO_IORQ_LOW_TS) as Ts;
373
374        let mut tsc = *self;
375        tsc.set_hc_after_small_increment(hc);
376        self.set_hc_after_small_increment(hc1);
377        tsc.as_timestamp()
378    }
379
380    fn add_wait_states(&mut self, _bus: u16, wait_states: NonZeroU16) {
381        let ws = wait_states.get();
382        if ws > WAIT_STATES_THRESHOLD {
383            // emulate hanging the Spectrum
384            self.vc += HALT_VC_THRESHOLD;
385        }
386        else if ws < V::HTS_COUNT as u16 {
387            self.set_hc_after_small_increment(self.hc + ws as i16);
388        }
389        else {
390            *self += ws as u32;
391        }
392    }
393
394    #[inline(always)]
395    fn as_timestamp(&self) -> Self::Timestamp {
396        self.ts
397    }
398}
399*/
400impl<V: VideoFrame, C: MemoryContention> Clock for VFrameTsCounter<V, C> {
401    type Limit = Ts;
402    type Timestamp = VideoTs;
403
404    #[inline(always)]
405    fn is_past_limit(&self, limit: Self::Limit) -> bool {
406        self.vc >= limit
407    }
408
409    fn add_irq(&mut self, _pc: u16) -> Self::Timestamp {
410        self.vts.set_hc_after_small_increment(self.hc + IRQ_ACK_CYCLE_TS as Ts);
411        self.as_timestamp()
412    }
413
414    #[inline(always)]
415    fn add_no_mreq(&mut self, address: u16, add_ts: NonZeroU8) {
416        let mut hc = self.hc;
417        if V::is_contended_line_no_mreq(self.vc) && self.contention.is_contended_address(address) {
418            for _ in 0..add_ts.get() {
419                hc = V::contention(hc) + 1;
420            }
421        }
422        else {
423            hc += add_ts.get() as Ts;
424        }
425        self.vts.set_hc_after_small_increment(hc);
426    }
427
428    #[inline(always)]
429    fn add_m1(&mut self, address: u16) -> Self::Timestamp {
430        // match address {
431        //     // 0x8043 => println!("0x{:04x}: {} {:?}", address, self.as_tstates(), self.tsc),
432        //     0x806F..=0x8078 => println!("0x{:04x}: {} {:?}", address, self.as_tstates(), self.tsc),
433        //     // 0xC008..=0xC011 => println!("0x{:04x}: {} {:?}", address, self.as_tstates(), self.tsc),
434        //     _ => {}
435        // }
436        let hc = if V::is_contended_line_mreq(self.vc) && self.contention.is_contended_address(address) {
437            V::contention(self.hc)
438        }
439        else {
440            self.hc
441        };
442        self.vts.set_hc_after_small_increment(hc + M1_CYCLE_TS as Ts);
443        self.as_timestamp()
444    }
445
446    #[inline(always)]
447    fn add_mreq(&mut self, address: u16) -> Self::Timestamp {
448        let hc = if V::is_contended_line_mreq(self.vc) && self.contention.is_contended_address(address) {
449            V::contention(self.hc)
450        }
451        else {
452            self.hc
453        };
454        self.vts.set_hc_after_small_increment(hc + MEMRW_CYCLE_TS as Ts);
455        self.as_timestamp()
456    }
457
458    // fn add_io(&mut self, port: u16) -> Self::Timestamp {
459    //     let VideoTs{ vc, hc } = self.tsc;
460    //     let hc = if V::is_contended_line_no_mreq(vc) {
461    //         if self.contention.is_contended_address(port) {
462    //             let hc = V::contention(hc) + 1;
463    //             if port & 1 == 0 { // C:1, C:3
464    //                 V::contention(hc) + (IO_CYCLE_TS - 1) as Ts
465    //             }
466    //             else { // C:1, C:1, C:1, C:1
467    //                 let mut hc1 = hc;
468    //                 for _ in 1..IO_CYCLE_TS {
469    //                     hc1 = V::contention(hc1) + 1;
470    //                 }
471    //                 hc1
472    //             }
473    //         }
474    //         else {
475    //             if port & 1 == 0 { // N:1 C:3
476    //                 V::contention(hc + 1) + (IO_CYCLE_TS - 1) as Ts
477    //             }
478    //             else { // N:4
479    //                 hc + IO_CYCLE_TS as Ts
480    //             }
481    //         }
482    //     }
483    //     else { // N:4
484    //         hc + IO_CYCLE_TS as Ts
485    //     };
486    //     self.vts.set_hc_after_small_increment(hc);
487    //     Self::new(vc, hc - 1).as_timestamp() // data read at last cycle
488    // }
489
490    fn add_io(&mut self, port: u16) -> Self::Timestamp {
491        let VideoTs{ vc, mut hc } = self.as_timestamp();
492        // if port == 0x7ffd {
493        //     println!("0x{:04x}: {} {:?}", port, self.as_tstates(), self.tsc);
494        // }
495        let hc1 = if V::is_contended_line_no_mreq(vc) {
496            ula_io_contention!(self.contention, port, hc, V::contention)
497            // if is_contended_address(self.contention_mask, port) {
498            //     hc = V::contention(hc) + IO_IORQ_LOW_TS as Ts;
499            //     if port & 1 == 0 { // C:1, C:3
500            //         V::contention(hc) + (IO_CYCLE_TS - IO_IORQ_LOW_TS) as Ts
501            //     }
502            //     else { // C:1, C:1, C:1, C:1
503            //         let mut hc1 = hc;
504            //         for _ in 0..(IO_CYCLE_TS - IO_IORQ_LOW_TS) {
505            //             hc1 = V::contention(hc1) + 1;
506            //         }
507            //         hc1
508            //     }
509            // }
510            // else {
511            //     hc += IO_IORQ_LOW_TS as Ts;
512            //     if port & 1 == 0 { // N:1 C:3
513            //         V::contention(hc) + (IO_CYCLE_TS - IO_IORQ_LOW_TS) as Ts
514            //     }
515            //     else { // N:4
516            //         hc + (IO_CYCLE_TS - IO_IORQ_LOW_TS) as Ts
517            //     }
518            // }
519        }
520        else {
521            hc += IO_IORQ_LOW_TS as Ts;
522            hc + (IO_CYCLE_TS - IO_IORQ_LOW_TS) as Ts
523        };
524        let mut vtsc = *self;
525        vtsc.vts.set_hc_after_small_increment(hc);
526        self.vts.set_hc_after_small_increment(hc1);
527        vtsc.as_timestamp()
528    }
529
530    fn add_wait_states(&mut self, _bus: u16, wait_states: NonZeroU16) {
531        let ws = wait_states.get();
532        if ws > WAIT_STATES_THRESHOLD {
533            // emulate hanging the Spectrum
534            self.vc += HALT_VC_THRESHOLD;
535        }
536        else if ws < V::HTS_COUNT as u16 {
537            self.vts.set_hc_after_small_increment(self.hc + ws as i16);
538        }
539        else {
540            *self += ws as u32;
541        }
542    }
543
544    #[inline(always)]
545    fn as_timestamp(&self) -> Self::Timestamp {
546        ***self
547    }
548}
549
550impl<V> Default for VFrameTs<V> {
551    fn default() -> Self {
552        VFrameTs::from(VideoTs::default())
553    }
554}
555
556impl<V> Clone for VFrameTs<V> {
557    fn clone(&self) -> Self {
558        VFrameTs::from(self.ts)
559    }
560}
561
562impl<V> Hash for VFrameTs<V> {
563    fn hash<H: Hasher>(&self, state: &mut H) {
564        self.ts.hash(state);
565    }
566}
567
568impl<V> Eq for VFrameTs<V> {}
569
570impl<V> PartialEq for VFrameTs<V> {
571    #[inline(always)]
572    fn eq(&self, other: &Self) -> bool {
573        self.ts == other.ts
574    }
575}
576
577impl<V> Ord for VFrameTs<V> {
578    #[inline(always)]
579    fn cmp(&self, other: &Self) -> Ordering {
580        self.ts.cmp(other)
581    }
582}
583
584impl<V> PartialOrd for VFrameTs<V> {
585    #[inline(always)]
586    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
587        Some(self.cmp(other))
588    }
589}
590
591impl<V: VideoFrame> From<VFrameTs<V>> for FTs {
592    #[inline(always)]
593    fn from(vfts: VFrameTs<V>) -> FTs {
594        VFrameTs::into_tstates(vfts)
595    }
596}
597
598impl<V: VideoFrame> TryFrom<FTs> for VFrameTs<V> {
599    type Error = &'static str;
600
601    fn try_from(ts: FTs) -> Result<Self, Self::Error> {
602        VFrameTs::try_from_tstates(ts).ok_or(
603            "out of range video timestamp conversion attempted")
604    }
605}
606
607impl<V> From<VFrameTs<V>> for VideoTs {
608    #[inline(always)]
609    fn from(vfts: VFrameTs<V>) -> VideoTs {
610        vfts.ts
611    }
612}
613
614impl<V> From<VideoTs> for VFrameTs<V> {
615    /// Returns a [VFrameTs] from the given [VideoTs].
616    /// A returned `VFrameTs` is not being normalized.
617    ///
618    /// # Panics
619    ///
620    /// Panics when the given `ts` overflows the capacity of [VideoTs].
621    #[inline(always)]
622    fn from(ts: VideoTs) -> Self {
623        VFrameTs { ts, _vframe: PhantomData }
624    }
625}
626
627impl<V, C> From<VFrameTsCounter<V, C>> for VideoTs {
628    #[inline(always)]
629    fn from(vftsc: VFrameTsCounter<V, C>) -> VideoTs {
630        vftsc.vts.ts
631    }
632}
633
634impl<V, C> From<VFrameTsCounter<V, C>> for VFrameTs<V> {
635    #[inline(always)]
636    fn from(vftsc: VFrameTsCounter<V, C>) -> VFrameTs<V> {
637        vftsc.vts
638    }
639}
640
641impl<V> Deref for VFrameTs<V> {
642    type Target = VideoTs;
643
644    #[inline(always)]
645    fn deref(&self) -> &Self::Target {
646        &self.ts
647    }
648}
649
650impl<V> DerefMut for VFrameTs<V> {
651    #[inline(always)]
652    fn deref_mut(&mut self) -> &mut Self::Target {
653        &mut self.ts
654    }
655}
656
657impl<V, C> Deref for VFrameTsCounter<V, C> {
658    type Target = VFrameTs<V>;
659
660    #[inline(always)]
661    fn deref(&self) -> &Self::Target {
662        &self.vts
663    }
664}
665
666impl<V, C> DerefMut for VFrameTsCounter<V, C> {
667    #[inline(always)]
668    fn deref_mut(&mut self) -> &mut Self::Target {
669        &mut self.vts
670    }
671}