Skip to main content

gstreamer/
clock.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::{
4    cmp, ptr,
5    sync::{atomic, atomic::AtomicI32},
6};
7
8#[cfg(feature = "futures")]
9use futures_core::{Future, Stream};
10#[cfg(feature = "futures")]
11use std::{marker::Unpin, pin::Pin};
12
13use glib::{
14    ffi::{gboolean, gpointer},
15    prelude::*,
16    translate::*,
17};
18use libc::c_void;
19
20use crate::{
21    Clock, ClockEntryType, ClockError, ClockFlags, ClockReturn, ClockSuccess, ClockTime,
22    ClockTimeDiff, ffi, prelude::*,
23};
24
25glib::wrapper! {
26    #[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Hash)]
27    pub struct ClockId(Shared<c_void>);
28
29    match fn {
30        ref => |ptr| ffi::gst_clock_id_ref(ptr),
31        unref => |ptr| ffi::gst_clock_id_unref(ptr),
32    }
33}
34
35impl ClockId {
36    #[doc(alias = "get_time")]
37    #[doc(alias = "gst_clock_id_get_time")]
38    #[doc(alias = "GST_CLOCK_ENTRY_TIME")]
39    pub fn time(&self) -> ClockTime {
40        unsafe {
41            try_from_glib(ffi::gst_clock_id_get_time(self.to_glib_none().0))
42                .expect("undefined time")
43        }
44    }
45
46    #[doc(alias = "gst_clock_id_unschedule")]
47    pub fn unschedule(&self) {
48        unsafe { ffi::gst_clock_id_unschedule(self.to_glib_none().0) }
49    }
50
51    #[doc(alias = "gst_clock_id_wait")]
52    pub fn wait(&self) -> (Result<ClockSuccess, ClockError>, ClockTimeDiff) {
53        unsafe {
54            let mut jitter = 0;
55            let res = try_from_glib(ffi::gst_clock_id_wait(self.to_glib_none().0, &mut jitter));
56            (res, jitter)
57        }
58    }
59
60    #[doc(alias = "gst_clock_id_compare_func")]
61    pub fn compare_by_time(&self, other: &Self) -> cmp::Ordering {
62        unsafe {
63            let res = ffi::gst_clock_id_compare_func(self.to_glib_none().0, other.to_glib_none().0);
64            res.cmp(&0)
65        }
66    }
67
68    #[cfg(feature = "v1_16")]
69    #[cfg_attr(docsrs, doc(cfg(feature = "v1_16")))]
70    #[doc(alias = "get_clock")]
71    #[doc(alias = "gst_clock_id_get_clock")]
72    pub fn clock(&self) -> Option<Clock> {
73        unsafe { from_glib_full(ffi::gst_clock_id_get_clock(self.to_glib_none().0)) }
74    }
75
76    #[cfg(feature = "v1_16")]
77    #[cfg_attr(docsrs, doc(cfg(feature = "v1_16")))]
78    #[doc(alias = "gst_clock_id_uses_clock")]
79    pub fn uses_clock<P: IsA<Clock>>(&self, clock: &P) -> bool {
80        unsafe {
81            from_glib(ffi::gst_clock_id_uses_clock(
82                self.to_glib_none().0,
83                clock.as_ref().as_ptr(),
84            ))
85        }
86    }
87
88    #[doc(alias = "get_type")]
89    #[doc(alias = "GST_CLOCK_ENTRY_TYPE")]
90    pub fn type_(&self) -> ClockEntryType {
91        unsafe {
92            let ptr = self.as_ptr() as *mut ffi::GstClockEntry;
93            from_glib((*ptr).type_)
94        }
95    }
96
97    #[doc(alias = "get_status")]
98    #[doc(alias = "GST_CLOCK_ENTRY_STATUS")]
99    pub fn status(&self) -> &AtomicClockReturn {
100        unsafe {
101            let ptr = self.as_ptr() as *mut ffi::GstClockEntry;
102            &*((&(*ptr).status) as *const i32 as *const AtomicClockReturn)
103        }
104    }
105}
106
107#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)]
108pub struct SingleShotClockId(ClockId);
109
110impl std::ops::Deref for SingleShotClockId {
111    type Target = ClockId;
112
113    #[inline]
114    fn deref(&self) -> &Self::Target {
115        &self.0
116    }
117}
118
119impl From<SingleShotClockId> for ClockId {
120    #[inline]
121    fn from(id: SingleShotClockId) -> ClockId {
122        skip_assert_initialized!();
123        id.0
124    }
125}
126
127impl TryFrom<ClockId> for SingleShotClockId {
128    type Error = glib::BoolError;
129
130    #[inline]
131    fn try_from(id: ClockId) -> Result<SingleShotClockId, glib::BoolError> {
132        skip_assert_initialized!();
133        match id.type_() {
134            ClockEntryType::Single => Ok(SingleShotClockId(id)),
135            _ => Err(glib::bool_error!("Not a single-shot clock id")),
136        }
137    }
138}
139
140impl SingleShotClockId {
141    #[doc(alias = "gst_clock_id_compare_func")]
142    #[inline]
143    pub fn compare_by_time(&self, other: &Self) -> cmp::Ordering {
144        self.0.compare_by_time(&other.0)
145    }
146
147    #[doc(alias = "gst_clock_id_wait_async")]
148    pub fn wait_async<F>(&self, func: F) -> Result<ClockSuccess, ClockError>
149    where
150        F: FnOnce(&Clock, Option<ClockTime>, &ClockId) + Send + 'static,
151    {
152        unsafe extern "C" fn trampoline<
153            F: FnOnce(&Clock, Option<ClockTime>, &ClockId) + Send + 'static,
154        >(
155            clock: *mut ffi::GstClock,
156            time: ffi::GstClockTime,
157            id: gpointer,
158            func: gpointer,
159        ) -> gboolean {
160            unsafe {
161                let f: &mut Option<F> = &mut *(func as *mut Option<F>);
162                let f = f.take().unwrap();
163
164                f(
165                    &from_glib_borrow(clock),
166                    from_glib(time),
167                    &from_glib_borrow(id),
168                );
169
170                glib::ffi::GTRUE
171            }
172        }
173
174        unsafe extern "C" fn destroy_notify<
175            F: FnOnce(&Clock, Option<ClockTime>, &ClockId) + Send + 'static,
176        >(
177            ptr: gpointer,
178        ) {
179            unsafe {
180                let _ = Box::<Option<F>>::from_raw(ptr as *mut _);
181            }
182        }
183
184        let func: Box<Option<F>> = Box::new(Some(func));
185
186        unsafe {
187            try_from_glib(ffi::gst_clock_id_wait_async(
188                self.to_glib_none().0,
189                Some(trampoline::<F>),
190                Box::into_raw(func) as gpointer,
191                Some(destroy_notify::<F>),
192            ))
193        }
194    }
195
196    #[allow(clippy::type_complexity)]
197    #[cfg(feature = "futures")]
198    pub fn wait_async_future(
199        &self,
200    ) -> Result<
201        Pin<
202            Box<
203                dyn Future<Output = Result<(Option<ClockTime>, ClockId), ClockError>>
204                    + Send
205                    + 'static,
206            >,
207        >,
208        ClockError,
209    > {
210        use futures_channel::oneshot;
211
212        let (sender, receiver) = oneshot::channel();
213
214        self.wait_async(move |_clock, jitter, id| {
215            if sender.send((jitter, id.clone())).is_err() {
216                // Unschedule any future calls if the receiver end is disconnected
217                id.unschedule();
218            }
219        })?;
220
221        Ok(Box::pin(async move {
222            receiver.await.map_err(|_| ClockError::Unscheduled)
223        }))
224    }
225}
226
227#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Hash)]
228pub struct PeriodicClockId(ClockId);
229
230impl std::ops::Deref for PeriodicClockId {
231    type Target = ClockId;
232
233    #[inline]
234    fn deref(&self) -> &Self::Target {
235        &self.0
236    }
237}
238
239impl From<PeriodicClockId> for ClockId {
240    #[inline]
241    fn from(id: PeriodicClockId) -> ClockId {
242        skip_assert_initialized!();
243        id.0
244    }
245}
246
247impl TryFrom<ClockId> for PeriodicClockId {
248    type Error = glib::BoolError;
249
250    #[inline]
251    fn try_from(id: ClockId) -> Result<PeriodicClockId, glib::BoolError> {
252        skip_assert_initialized!();
253        match id.type_() {
254            ClockEntryType::Periodic => Ok(PeriodicClockId(id)),
255            _ => Err(glib::bool_error!("Not a periodic clock id")),
256        }
257    }
258}
259
260impl PeriodicClockId {
261    #[doc(alias = "get_interval")]
262    #[doc(alias = "GST_CLOCK_ENTRY_INTERVAL")]
263    #[inline]
264    pub fn interval(&self) -> ClockTime {
265        unsafe {
266            let ptr = self.as_ptr() as *mut ffi::GstClockEntry;
267            try_from_glib((*ptr).interval).expect("undefined interval")
268        }
269    }
270
271    #[doc(alias = "gst_clock_id_compare_func")]
272    #[inline]
273    pub fn compare_by_time(&self, other: &Self) -> cmp::Ordering {
274        self.0.compare_by_time(&other.0)
275    }
276
277    #[doc(alias = "gst_clock_id_wait_async")]
278    pub fn wait_async<F>(&self, func: F) -> Result<ClockSuccess, ClockError>
279    where
280        F: Fn(&Clock, Option<ClockTime>, &ClockId) + Send + 'static,
281    {
282        unsafe extern "C" fn trampoline<
283            F: Fn(&Clock, Option<ClockTime>, &ClockId) + Send + 'static,
284        >(
285            clock: *mut ffi::GstClock,
286            time: ffi::GstClockTime,
287            id: gpointer,
288            func: gpointer,
289        ) -> gboolean {
290            unsafe {
291                let f: &F = &*(func as *const F);
292                f(
293                    &from_glib_borrow(clock),
294                    from_glib(time),
295                    &from_glib_borrow(id),
296                );
297                glib::ffi::GTRUE
298            }
299        }
300
301        unsafe extern "C" fn destroy_notify<
302            F: Fn(&Clock, Option<ClockTime>, &ClockId) + Send + 'static,
303        >(
304            ptr: gpointer,
305        ) {
306            unsafe {
307                let _ = Box::<F>::from_raw(ptr as *mut _);
308            }
309        }
310
311        let func: Box<F> = Box::new(func);
312        unsafe {
313            try_from_glib(ffi::gst_clock_id_wait_async(
314                self.to_glib_none().0,
315                Some(trampoline::<F>),
316                Box::into_raw(func) as gpointer,
317                Some(destroy_notify::<F>),
318            ))
319        }
320    }
321
322    #[allow(clippy::type_complexity)]
323    #[cfg(feature = "futures")]
324    pub fn wait_async_stream(
325        &self,
326    ) -> Result<
327        Pin<Box<dyn Stream<Item = (Option<ClockTime>, ClockId)> + Unpin + Send + 'static>>,
328        ClockError,
329    > {
330        use futures_channel::mpsc;
331
332        let (sender, receiver) = mpsc::unbounded();
333
334        self.wait_async(move |_clock, jitter, id| {
335            if sender.unbounded_send((jitter, id.clone())).is_err() {
336                // Unschedule any future calls if the receiver end is disconnected
337                id.unschedule();
338            }
339        })?;
340
341        Ok(Box::pin(receiver))
342    }
343}
344
345#[repr(transparent)]
346#[derive(Debug)]
347pub struct AtomicClockReturn(AtomicI32);
348
349impl AtomicClockReturn {
350    #[inline]
351    pub fn load(&self) -> ClockReturn {
352        unsafe { from_glib(self.0.load(atomic::Ordering::SeqCst)) }
353    }
354
355    #[inline]
356    pub fn store(&self, val: ClockReturn) {
357        self.0.store(val.into_glib(), atomic::Ordering::SeqCst)
358    }
359
360    #[inline]
361    pub fn swap(&self, val: ClockReturn) -> ClockReturn {
362        unsafe { from_glib(self.0.swap(val.into_glib(), atomic::Ordering::SeqCst)) }
363    }
364
365    #[inline]
366    pub fn compare_exchange(
367        &self,
368        current: ClockReturn,
369        new: ClockReturn,
370    ) -> Result<ClockReturn, ClockReturn> {
371        unsafe {
372            self.0
373                .compare_exchange(
374                    current.into_glib(),
375                    new.into_glib(),
376                    atomic::Ordering::SeqCst,
377                    atomic::Ordering::SeqCst,
378                )
379                .map(|v| from_glib(v))
380                .map_err(|v| from_glib(v))
381        }
382    }
383}
384
385unsafe impl Send for ClockId {}
386unsafe impl Sync for ClockId {}
387
388impl Clock {
389    #[doc(alias = "gst_clock_adjust_with_calibration")]
390    pub fn adjust_with_calibration(
391        internal_target: ClockTime,
392        cinternal: ClockTime,
393        cexternal: ClockTime,
394        cnum: u64,
395        cdenom: u64,
396    ) -> ClockTime {
397        skip_assert_initialized!();
398        unsafe {
399            try_from_glib(ffi::gst_clock_adjust_with_calibration(
400                ptr::null_mut(),
401                internal_target.into_glib(),
402                cinternal.into_glib(),
403                cexternal.into_glib(),
404                cnum,
405                cdenom,
406            ))
407            .expect("undefined ClockTime")
408        }
409    }
410
411    #[doc(alias = "gst_clock_unadjust_with_calibration")]
412    pub fn unadjust_with_calibration(
413        external_target: ClockTime,
414        cinternal: ClockTime,
415        cexternal: ClockTime,
416        cnum: u64,
417        cdenom: u64,
418    ) -> ClockTime {
419        skip_assert_initialized!();
420        unsafe {
421            try_from_glib(ffi::gst_clock_unadjust_with_calibration(
422                ptr::null_mut(),
423                external_target.into_glib(),
424                cinternal.into_glib(),
425                cexternal.into_glib(),
426                cnum,
427                cdenom,
428            ))
429            .expect("undefined ClockTime")
430        }
431    }
432}
433
434pub trait ClockExtManual: IsA<Clock> + 'static {
435    /// Gets an ID from `self` to trigger a periodic notification.
436    /// The periodic notifications will start at time `start_time` and
437    /// will then be fired with the given `interval`.
438    /// ## `start_time`
439    /// the requested start time
440    /// ## `interval`
441    /// the requested interval
442    ///
443    /// # Returns
444    ///
445    /// a `GstClockID` that can be used to request the
446    ///  time notification.
447    #[doc(alias = "gst_clock_new_periodic_id")]
448    fn new_periodic_id(&self, start_time: ClockTime, interval: ClockTime) -> PeriodicClockId {
449        assert_ne!(interval, ClockTime::ZERO);
450
451        unsafe {
452            PeriodicClockId(from_glib_full(ffi::gst_clock_new_periodic_id(
453                self.as_ref().to_glib_none().0,
454                start_time.into_glib(),
455                interval.into_glib(),
456            )))
457        }
458    }
459
460    /// Reinitializes the provided periodic `id` to the provided start time and
461    /// interval. Does not modify the reference count.
462    /// ## `id`
463    /// a `GstClockID`
464    /// ## `start_time`
465    /// the requested start time
466    /// ## `interval`
467    /// the requested interval
468    ///
469    /// # Returns
470    ///
471    /// [`true`] if the GstClockID could be reinitialized to the provided
472    /// `time`, else [`false`].
473    #[doc(alias = "gst_clock_periodic_id_reinit")]
474    fn periodic_id_reinit(
475        &self,
476        id: &PeriodicClockId,
477        start_time: ClockTime,
478        interval: ClockTime,
479    ) -> Result<(), glib::BoolError> {
480        unsafe {
481            let res: bool = from_glib(ffi::gst_clock_periodic_id_reinit(
482                self.as_ref().to_glib_none().0,
483                id.to_glib_none().0,
484                start_time.into_glib(),
485                interval.into_glib(),
486            ));
487            if res {
488                Ok(())
489            } else {
490                Err(glib::bool_error!("Failed to reinit periodic clock id"))
491            }
492        }
493    }
494
495    /// Gets a `GstClockID` from `self` to trigger a single shot
496    /// notification at the requested time.
497    /// ## `time`
498    /// the requested time
499    ///
500    /// # Returns
501    ///
502    /// a `GstClockID` that can be used to request the
503    ///  time notification.
504    #[doc(alias = "gst_clock_new_single_shot_id")]
505    fn new_single_shot_id(&self, time: ClockTime) -> SingleShotClockId {
506        unsafe {
507            SingleShotClockId(from_glib_full(ffi::gst_clock_new_single_shot_id(
508                self.as_ref().to_glib_none().0,
509                time.into_glib(),
510            )))
511        }
512    }
513
514    /// Reinitializes the provided single shot `id` to the provided time. Does not
515    /// modify the reference count.
516    /// ## `id`
517    /// a `GstClockID`
518    /// ## `time`
519    /// The requested time.
520    ///
521    /// # Returns
522    ///
523    /// [`true`] if the GstClockID could be reinitialized to the provided
524    /// `time`, else [`false`].
525    #[doc(alias = "gst_clock_single_shot_id_reinit")]
526    fn single_shot_id_reinit(
527        &self,
528        id: &SingleShotClockId,
529        time: ClockTime,
530    ) -> Result<(), glib::BoolError> {
531        unsafe {
532            let res: bool = from_glib(ffi::gst_clock_single_shot_id_reinit(
533                self.as_ref().to_glib_none().0,
534                id.to_glib_none().0,
535                time.into_glib(),
536            ));
537            if res {
538                Ok(())
539            } else {
540                Err(glib::bool_error!("Failed to reinit single shot clock id"))
541            }
542        }
543    }
544
545    fn set_clock_flags(&self, flags: ClockFlags) {
546        unsafe {
547            let ptr: *mut ffi::GstObject = self.as_ptr() as *mut _;
548            let _guard = self.as_ref().object_lock();
549            (*ptr).flags |= flags.into_glib();
550        }
551    }
552
553    fn unset_clock_flags(&self, flags: ClockFlags) {
554        unsafe {
555            let ptr: *mut ffi::GstObject = self.as_ptr() as *mut _;
556            let _guard = self.as_ref().object_lock();
557            (*ptr).flags &= !flags.into_glib();
558        }
559    }
560
561    #[doc(alias = "get_clock_flags")]
562    fn clock_flags(&self) -> ClockFlags {
563        unsafe {
564            let ptr: *mut ffi::GstObject = self.as_ptr() as *mut _;
565            let _guard = self.as_ref().object_lock();
566            from_glib((*ptr).flags)
567        }
568    }
569
570    /// Gets the internal rate and reference time of `self`. See
571    /// [`set_calibration()`][Self::set_calibration()] for more information.
572    ///
573    /// `internal`, `external`, `rate_num`, and `rate_denom` can be left [`None`] if the
574    /// caller is not interested in the values.
575    ///
576    /// # Returns
577    ///
578    ///
579    /// ## `internal`
580    /// a location to store the internal time
581    ///
582    /// ## `external`
583    /// a location to store the external time
584    ///
585    /// ## `rate_num`
586    /// a location to store the rate numerator
587    ///
588    /// ## `rate_denom`
589    /// a location to store the rate denominator
590    #[doc(alias = "gst_clock_get_calibration")]
591    #[doc(alias = "get_calibration")]
592    fn calibration(&self) -> (ClockTime, ClockTime, u64, u64) {
593        unsafe {
594            let mut internal = std::mem::MaybeUninit::uninit();
595            let mut external = std::mem::MaybeUninit::uninit();
596            let mut rate_num = std::mem::MaybeUninit::uninit();
597            let mut rate_denom = std::mem::MaybeUninit::uninit();
598            ffi::gst_clock_get_calibration(
599                self.as_ref().to_glib_none().0,
600                internal.as_mut_ptr(),
601                external.as_mut_ptr(),
602                rate_num.as_mut_ptr(),
603                rate_denom.as_mut_ptr(),
604            );
605            (
606                try_from_glib(internal.assume_init()).expect("mandatory glib value is None"),
607                try_from_glib(external.assume_init()).expect("mandatory glib value is None"),
608                rate_num.assume_init(),
609                rate_denom.assume_init(),
610            )
611        }
612    }
613
614    /// Adjusts the rate and time of `self`. A rate of 1/1 is the normal speed of
615    /// the clock. Values bigger than 1/1 make the clock go faster.
616    ///
617    /// `internal` and `external` are calibration parameters that arrange that
618    /// [`ClockExt::time()`][crate::prelude::ClockExt::time()] should have been `external` at internal time `internal`.
619    /// This internal time should not be in the future; that is, it should be less
620    /// than the value of [`ClockExt::internal_time()`][crate::prelude::ClockExt::internal_time()] when this function is called.
621    ///
622    /// Subsequent calls to [`ClockExt::time()`][crate::prelude::ClockExt::time()] will return clock times computed as
623    /// follows:
624    ///
625    /// **⚠️ The following code is in  C ⚠️**
626    ///
627    /// ``` C
628    ///   time = (internal_time - internal) * rate_num / rate_denom + external
629    /// ```
630    ///
631    /// This formula is implemented in [`ClockExt::adjust_unlocked()`][crate::prelude::ClockExt::adjust_unlocked()]. Of course, it
632    /// tries to do the integer arithmetic as precisely as possible.
633    ///
634    /// Note that [`ClockExt::time()`][crate::prelude::ClockExt::time()] always returns increasing values so when you
635    /// move the clock backwards, [`ClockExt::time()`][crate::prelude::ClockExt::time()] will report the previous value
636    /// until the clock catches up.
637    /// ## `internal`
638    /// a reference internal time
639    /// ## `external`
640    /// a reference external time
641    /// ## `rate_num`
642    /// the numerator of the rate of the clock relative to its
643    ///  internal time
644    /// ## `rate_denom`
645    /// the denominator of the rate of the clock
646    #[doc(alias = "gst_clock_set_calibration")]
647    fn set_calibration(
648        &self,
649        internal: ClockTime,
650        external: ClockTime,
651        rate_num: u64,
652        rate_denom: u64,
653    ) {
654        unsafe {
655            ffi::gst_clock_set_calibration(
656                self.as_ref().to_glib_none().0,
657                internal.into_glib(),
658                external.into_glib(),
659                rate_num,
660                rate_denom,
661            );
662        }
663    }
664}
665
666impl<O: IsA<Clock>> ClockExtManual for O {}
667
668#[cfg(test)]
669mod tests {
670    use std::sync::mpsc::channel;
671
672    use super::*;
673    use crate::SystemClock;
674
675    #[test]
676    fn test_wait() {
677        crate::init().unwrap();
678
679        let clock = SystemClock::obtain();
680        let now = clock.time();
681        let id = clock.new_single_shot_id(now + 20 * ClockTime::MSECOND);
682        let (res, _) = id.wait();
683
684        assert!(res == Ok(ClockSuccess::Ok) || res == Err(ClockError::Early));
685    }
686
687    #[test]
688    fn test_wait_async() {
689        crate::init().unwrap();
690
691        let (sender, receiver) = channel();
692
693        let clock = SystemClock::obtain();
694        let now = clock.time();
695        let id = clock.new_single_shot_id(now + 20 * ClockTime::MSECOND);
696        let res = id.wait_async(move |_, _, _| {
697            sender.send(()).unwrap();
698        });
699
700        assert!(res == Ok(ClockSuccess::Ok));
701
702        assert_eq!(receiver.recv(), Ok(()));
703    }
704
705    #[test]
706    fn test_wait_periodic() {
707        crate::init().unwrap();
708
709        let clock = SystemClock::obtain();
710        let now = clock.time();
711        let id = clock.new_periodic_id(now + 20 * ClockTime::MSECOND, 20 * ClockTime::MSECOND);
712
713        let (res, _) = id.wait();
714        assert!(res == Ok(ClockSuccess::Ok) || res == Err(ClockError::Early));
715
716        let (res, _) = id.wait();
717        assert!(res == Ok(ClockSuccess::Ok) || res == Err(ClockError::Early));
718    }
719
720    #[test]
721    fn test_wait_async_periodic() {
722        crate::init().unwrap();
723
724        let (sender, receiver) = channel();
725
726        let clock = SystemClock::obtain();
727        let now = clock.time();
728        let id = clock.new_periodic_id(now + 20 * ClockTime::MSECOND, 20 * ClockTime::MSECOND);
729        let res = id.wait_async(move |_, _, _| {
730            let _ = sender.send(());
731        });
732
733        assert!(res == Ok(ClockSuccess::Ok));
734
735        assert_eq!(receiver.recv(), Ok(()));
736        assert_eq!(receiver.recv(), Ok(()));
737    }
738}