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