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 <queue>
11 
12 #  include "mozilla/AbstractThread.h"
13 #  include "mozilla/IntegerPrintfMacros.h"
14 #  include "mozilla/Monitor.h"
15 #  include "mozilla/MozPromise.h"
16 #  include "mozilla/RefPtr.h"
17 #  include "mozilla/TimeStamp.h"
18 #  include "mozilla/Unused.h"
19 #  include "nsITimer.h"
20 
21 namespace mozilla {
22 
23 extern LazyLogModule gMediaTimerLog;
24 
25 #  define TIMER_LOG(x, ...)                                    \
26     MOZ_ASSERT(gMediaTimerLog);                                \
27     MOZ_LOG(gMediaTimerLog, LogLevel::Debug,                   \
28             ("[MediaTimer=%p relative_t=%" PRId64 "]" x, this, \
29              RelativeMicroseconds(TimeStamp::Now()), ##__VA_ARGS__))
30 
31 // This promise type is only exclusive because so far there isn't a reason for
32 // it not to be. Feel free to change that.
33 typedef MozPromise<bool, bool, /* IsExclusive = */ true> MediaTimerPromise;
34 
35 // Timers only know how to fire at a given thread, which creates an impedence
36 // mismatch with code that operates with TaskQueues. This class solves
37 // that mismatch with a dedicated (but shared) thread and a nice MozPromise-y
38 // interface.
39 class MediaTimer {
40  public:
41   explicit MediaTimer(bool aFuzzy = false);
42 
43   NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DESTROY(MediaTimer,
44                                                      DispatchDestroy());
45 
46   RefPtr<MediaTimerPromise> WaitFor(const TimeDuration& aDuration,
47                                     const char* aCallSite);
48   RefPtr<MediaTimerPromise> WaitUntil(const TimeStamp& aTimeStamp,
49                                       const char* aCallSite);
50   void Cancel();  // Cancel and reject any unresolved promises with false.
51 
52  private:
~MediaTimer()53   virtual ~MediaTimer() { MOZ_ASSERT(OnMediaTimerThread()); }
54 
55   void DispatchDestroy();  // Invoked by Release on an arbitrary thread.
56   void Destroy();          // Runs on the timer thread.
57 
58   bool OnMediaTimerThread();
59   void ScheduleUpdate();
60   void Update();
61   void UpdateLocked();
62   bool IsExpired(const TimeStamp& aTarget, const TimeStamp& aNow);
63   void Reject();
64 
65   static void TimerCallback(nsITimer* aTimer, void* aClosure);
66   void TimerFired();
67   void ArmTimer(const TimeStamp& aTarget, const TimeStamp& aNow);
68 
TimerIsArmed()69   bool TimerIsArmed() { return !mCurrentTimerTarget.IsNull(); }
70 
CancelTimerIfArmed()71   void CancelTimerIfArmed() {
72     MOZ_ASSERT(OnMediaTimerThread());
73     if (TimerIsArmed()) {
74       TIMER_LOG("MediaTimer::CancelTimerIfArmed canceling timer");
75       mTimer->Cancel();
76       mCurrentTimerTarget = TimeStamp();
77     }
78   }
79 
80   struct Entry {
81     TimeStamp mTimeStamp;
82     RefPtr<MediaTimerPromise::Private> mPromise;
83 
EntryEntry84     explicit Entry(const TimeStamp& aTimeStamp, const char* aCallSite)
85         : mTimeStamp(aTimeStamp),
86           mPromise(new MediaTimerPromise::Private(aCallSite)) {}
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       return mTimeStamp > aOther.mTimeStamp;
93     }
94   };
95 
96   nsCOMPtr<nsIEventTarget> mThread;
97   std::priority_queue<Entry> mEntries;
98   Monitor mMonitor;
99   nsCOMPtr<nsITimer> mTimer;
100   TimeStamp mCurrentTimerTarget;
101 
102   // Timestamps only have relative meaning, so we need a base timestamp for
103   // logging purposes.
104   TimeStamp mCreationTimeStamp;
RelativeMicroseconds(const TimeStamp & aTimeStamp)105   int64_t RelativeMicroseconds(const TimeStamp& aTimeStamp) {
106     return (int64_t)(aTimeStamp - mCreationTimeStamp).ToMicroseconds();
107   }
108 
109   bool mUpdateScheduled;
110   const bool mFuzzy;
111 };
112 
113 // Class for managing delayed dispatches on target thread.
114 class DelayedScheduler {
115  public:
116   explicit DelayedScheduler(nsISerialEventTarget* aTargetThread,
117                             bool aFuzzy = false)
mTargetThread(aTargetThread)118       : mTargetThread(aTargetThread), mMediaTimer(new MediaTimer(aFuzzy)) {
119     MOZ_ASSERT(mTargetThread);
120   }
121 
IsScheduled()122   bool IsScheduled() const { return !mTarget.IsNull(); }
123 
Reset()124   void Reset() {
125     MOZ_ASSERT(mTargetThread->IsOnCurrentThread(),
126                "Must be on target thread to disconnect");
127     mRequest.DisconnectIfExists();
128     mTarget = TimeStamp();
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->IsOnCurrentThread());
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->IsOnCurrentThread());
148     mRequest.Complete();
149     mTarget = TimeStamp();
150   }
151 
152  private:
153   nsCOMPtr<nsISerialEventTarget> mTargetThread;
154   RefPtr<MediaTimer> mMediaTimer;
155   MozPromiseRequestHolder<mozilla::MediaTimerPromise> mRequest;
156   TimeStamp mTarget;
157 };
158 
159 }  // namespace mozilla
160 
161 #endif
162