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