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