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/Monitor.h"
11 #include "mozilla/MozPromise.h"
12 #include "mozilla/TimeStamp.h"
13 
14 #include <queue>
15 
16 #include "nsITimer.h"
17 #include "mozilla/RefPtr.h"
18 
19 namespace mozilla {
20 
21 extern LazyLogModule gMediaTimerLog;
22 
23 #define TIMER_LOG(x, ...) \
24   MOZ_ASSERT(gMediaTimerLog); \
25   MOZ_LOG(gMediaTimerLog, LogLevel::Debug, ("[MediaTimer=%p relative_t=%lld]" x, this, \
26                                         RelativeMicroseconds(TimeStamp::Now()), ##__VA_ARGS__))
27 
28 // This promise type is only exclusive because so far there isn't a reason for
29 // it not to be. Feel free to change that.
30 typedef MozPromise<bool, bool, /* IsExclusive = */ true> MediaTimerPromise;
31 
32 // Timers only know how to fire at a given thread, which creates an impedence
33 // mismatch with code that operates with TaskQueues. This class solves
34 // that mismatch with a dedicated (but shared) thread and a nice MozPromise-y
35 // interface.
36 class MediaTimer
37 {
38 public:
39   MediaTimer();
40 
41   // We use a release with a custom Destroy().
42   NS_IMETHOD_(MozExternalRefCountType) AddRef(void);
43   NS_IMETHOD_(MozExternalRefCountType) Release(void);
44 
45   RefPtr<MediaTimerPromise> WaitUntil(const TimeStamp& aTimeStamp, const char* aCallSite);
46 
47 private:
~MediaTimer()48   virtual ~MediaTimer() { MOZ_ASSERT(OnMediaTimerThread()); }
49 
50   void DispatchDestroy(); // Invoked by Release on an arbitrary thread.
51   void Destroy(); // Runs on the timer thread.
52 
53   bool OnMediaTimerThread();
54   void ScheduleUpdate();
55   void Update();
56   void UpdateLocked();
57 
58   static void TimerCallback(nsITimer* aTimer, void* aClosure);
59   void TimerFired();
60   void ArmTimer(const TimeStamp& aTarget, const TimeStamp& aNow);
61 
TimerIsArmed()62   bool TimerIsArmed()
63   {
64     return !mCurrentTimerTarget.IsNull();
65   }
66 
CancelTimerIfArmed()67   void CancelTimerIfArmed()
68   {
69     MOZ_ASSERT(OnMediaTimerThread());
70     if (TimerIsArmed()) {
71       TIMER_LOG("MediaTimer::CancelTimerIfArmed canceling timer");
72       mTimer->Cancel();
73       mCurrentTimerTarget = TimeStamp();
74     }
75   }
76 
77 
78   struct Entry
79   {
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 
88     // Define a < overload that reverses ordering because std::priority_queue
89     // provides access to the largest element, and we want the smallest
90     // (i.e. the soonest).
91     bool operator<(const Entry& aOther) const
92     {
93       return mTimeStamp > aOther.mTimeStamp;
94     }
95   };
96 
97   ThreadSafeAutoRefCnt mRefCnt;
98   NS_DECL_OWNINGTHREAD
99   nsCOMPtr<nsIEventTarget> mThread;
100   std::priority_queue<Entry> mEntries;
101   Monitor mMonitor;
102   nsCOMPtr<nsITimer> mTimer;
103   TimeStamp mCurrentTimerTarget;
104 
105   // Timestamps only have relative meaning, so we need a base timestamp for
106   // logging purposes.
107   TimeStamp mCreationTimeStamp;
RelativeMicroseconds(const TimeStamp & aTimeStamp)108   int64_t RelativeMicroseconds(const TimeStamp& aTimeStamp)
109   {
110     return (int64_t) (aTimeStamp - mCreationTimeStamp).ToMicroseconds();
111   }
112 
113   bool mUpdateScheduled;
114 };
115 
116 // Class for managing delayed dispatches on target thread.
117 class DelayedScheduler {
118 public:
DelayedScheduler(AbstractThread * aTargetThread)119   explicit DelayedScheduler(AbstractThread* aTargetThread)
120     : mTargetThread(aTargetThread), mMediaTimer(new MediaTimer())
121   {
122     MOZ_ASSERT(mTargetThread);
123   }
124 
IsScheduled()125   bool IsScheduled() const { return !mTarget.IsNull(); }
126 
Reset()127   void Reset()
128   {
129     MOZ_ASSERT(mTargetThread->IsCurrentThreadIn(),
130       "Must be on target thread to disconnect");
131     if (IsScheduled()) {
132       mRequest.Disconnect();
133       mTarget = TimeStamp();
134     }
135   }
136 
137   template <typename ResolveFunc, typename RejectFunc>
Ensure(mozilla::TimeStamp & aTarget,ResolveFunc && aResolver,RejectFunc && aRejector)138   void Ensure(mozilla::TimeStamp& aTarget,
139               ResolveFunc&& aResolver,
140               RejectFunc&& aRejector)
141   {
142     MOZ_ASSERT(mTargetThread->IsCurrentThreadIn());
143     if (IsScheduled() && mTarget <= aTarget) {
144       return;
145     }
146     Reset();
147     mTarget = aTarget;
148     mRequest.Begin(mMediaTimer->WaitUntil(mTarget, __func__)->Then(
149       mTargetThread, __func__,
150       Forward<ResolveFunc>(aResolver),
151       Forward<RejectFunc>(aRejector)));
152   }
153 
CompleteRequest()154   void CompleteRequest()
155   {
156     MOZ_ASSERT(mTargetThread->IsCurrentThreadIn());
157     mRequest.Complete();
158     mTarget = TimeStamp();
159   }
160 
161 private:
162   RefPtr<AbstractThread> mTargetThread;
163   RefPtr<MediaTimer> mMediaTimer;
164   MozPromiseRequestHolder<mozilla::MediaTimerPromise> mRequest;
165   TimeStamp mTarget;
166 };
167 
168 } // namespace mozilla
169 
170 #endif
171