1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #if !defined(MediaTimer_h_)
8 #  define MediaTimer_h_
9 
10 #  include "mozilla/AbstractThread.h"
11 #  include "mozilla/IntegerPrintfMacros.h"
12 #  include "mozilla/Monitor.h"
13 #  include "mozilla/MozPromise.h"
14 #  include "mozilla/RefPtr.h"
15 #  include "mozilla/TimeStamp.h"
16 #  include "mozilla/Unused.h"
17 #  include "nsITimer.h"
18 #  include <queue>
19 
20 namespace mozilla {
21 
22 extern LazyLogModule gMediaTimerLog;
23 
24 #  define TIMER_LOG(x, ...)                                    \
25     MOZ_ASSERT(gMediaTimerLog);                                \
26     MOZ_LOG(gMediaTimerLog, LogLevel::Debug,                   \
27             ("[MediaTimer=%p relative_t=%" PRId64 "]" x, this, \
28              RelativeMicroseconds(TimeStamp::Now()), ##__VA_ARGS__))
29 
30 // This promise type is only exclusive because so far there isn't a reason for
31 // it not to be. Feel free to change that.
32 typedef MozPromise<bool, bool, /* IsExclusive = */ true> MediaTimerPromise;
33 
34 // Timers only know how to fire at a given thread, which creates an impedence
35 // mismatch with code that operates with TaskQueues. This class solves
36 // that mismatch with a dedicated (but shared) thread and a nice MozPromise-y
37 // interface.
38 class MediaTimer {
39  public:
40   explicit MediaTimer(bool aFuzzy = false);
41 
42   NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DESTROY(MediaTimer,
43                                                      DispatchDestroy());
44 
45   RefPtr<MediaTimerPromise> WaitFor(const TimeDuration& aDuration,
46                                     const char* aCallSite);
47   RefPtr<MediaTimerPromise> WaitUntil(const TimeStamp& aTimeStamp,
48                                       const char* aCallSite);
49   void Cancel();  // Cancel and reject any unresolved promises with false.
50 
51  private:
~MediaTimer()52   virtual ~MediaTimer() { MOZ_ASSERT(OnMediaTimerThread()); }
53 
54   void DispatchDestroy();  // Invoked by Release on an arbitrary thread.
55   void Destroy();          // Runs on the timer thread.
56 
57   bool OnMediaTimerThread();
58   void ScheduleUpdate();
59   void Update();
60   void UpdateLocked();
61   bool IsExpired(const TimeStamp& aTarget, const TimeStamp& aNow);
62   void Reject();
63 
64   static void TimerCallback(nsITimer* aTimer, void* aClosure);
65   void TimerFired();
66   void ArmTimer(const TimeStamp& aTarget, const TimeStamp& aNow);
67 
TimerIsArmed()68   bool TimerIsArmed() { return !mCurrentTimerTarget.IsNull(); }
69 
CancelTimerIfArmed()70   void CancelTimerIfArmed() {
71     MOZ_ASSERT(OnMediaTimerThread());
72     if (TimerIsArmed()) {
73       TIMER_LOG("MediaTimer::CancelTimerIfArmed canceling timer");
74       mTimer->Cancel();
75       mCurrentTimerTarget = TimeStamp();
76     }
77   }
78 
79   struct Entry {
80     TimeStamp mTimeStamp;
81     RefPtr<MediaTimerPromise::Private> mPromise;
82 
EntryEntry83     explicit Entry(const TimeStamp& aTimeStamp, const char* aCallSite)
84         : mTimeStamp(aTimeStamp),
85           mPromise(new MediaTimerPromise::Private(aCallSite)) {}
86 
87     // Define a < overload that reverses ordering because std::priority_queue
88     // provides access to the largest element, and we want the smallest
89     // (i.e. the soonest).
90     bool operator<(const Entry& aOther) const {
91       return mTimeStamp > aOther.mTimeStamp;
92     }
93   };
94 
95   nsCOMPtr<nsIEventTarget> mThread;
96   std::priority_queue<Entry> mEntries;
97   Monitor mMonitor;
98   nsCOMPtr<nsITimer> mTimer;
99   TimeStamp mCurrentTimerTarget;
100 
101   // Timestamps only have relative meaning, so we need a base timestamp for
102   // logging purposes.
103   TimeStamp mCreationTimeStamp;
RelativeMicroseconds(const TimeStamp & aTimeStamp)104   int64_t RelativeMicroseconds(const TimeStamp& aTimeStamp) {
105     return (int64_t)(aTimeStamp - mCreationTimeStamp).ToMicroseconds();
106   }
107 
108   bool mUpdateScheduled;
109   const bool mFuzzy;
110 };
111 
112 // Class for managing delayed dispatches on target thread.
113 class DelayedScheduler {
114  public:
115   explicit DelayedScheduler(AbstractThread* aTargetThread, bool aFuzzy = false)
mTargetThread(aTargetThread)116       : mTargetThread(aTargetThread), mMediaTimer(new MediaTimer(aFuzzy)) {
117     MOZ_ASSERT(mTargetThread);
118   }
119 
IsScheduled()120   bool IsScheduled() const { return !mTarget.IsNull(); }
121 
Reset()122   void Reset() {
123     MOZ_ASSERT(mTargetThread->IsCurrentThreadIn(),
124                "Must be on target thread to disconnect");
125     if (IsScheduled()) {
126       mRequest.Disconnect();
127       mTarget = TimeStamp();
128     }
129   }
130 
131   template <typename ResolveFunc, typename RejectFunc>
Ensure(mozilla::TimeStamp & aTarget,ResolveFunc && aResolver,RejectFunc && aRejector)132   void Ensure(mozilla::TimeStamp& aTarget, ResolveFunc&& aResolver,
133               RejectFunc&& aRejector) {
134     MOZ_ASSERT(mTargetThread->IsCurrentThreadIn());
135     if (IsScheduled() && mTarget <= aTarget) {
136       return;
137     }
138     Reset();
139     mTarget = aTarget;
140     mMediaTimer->WaitUntil(mTarget, __func__)
141         ->Then(mTargetThread, __func__, std::forward<ResolveFunc>(aResolver),
142                std::forward<RejectFunc>(aRejector))
143         ->Track(mRequest);
144   }
145 
CompleteRequest()146   void CompleteRequest() {
147     MOZ_ASSERT(mTargetThread->IsCurrentThreadIn());
148     mRequest.Complete();
149     mTarget = TimeStamp();
150   }
151 
152  private:
153   RefPtr<AbstractThread> mTargetThread;
154   RefPtr<MediaTimer> mMediaTimer;
155   MozPromiseRequestHolder<mozilla::MediaTimerPromise> mRequest;
156   TimeStamp mTarget;
157 };
158 
159 }  // namespace mozilla
160 
161 #endif
162