MediaTimer.h (5426B)
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/AwakeTimeStamp.h" 14 # include "mozilla/Monitor.h" 15 # include "mozilla/MozPromise.h" 16 # include "mozilla/RefPtr.h" 17 # include "mozilla/SharedThreadPool.h" 18 # include "mozilla/TimeStamp.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(T::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 using MediaTimerPromise = MozPromise<bool, bool, true>; 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 template <typename T> 40 class MediaTimer { 41 public: 42 explicit MediaTimer(bool aFuzzy = false); 43 44 NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DESTROY(MediaTimer, 45 DispatchDestroy()); 46 47 RefPtr<MediaTimerPromise> WaitFor(const typename T::DurationType& aDuration, 48 StaticString aCallSite); 49 50 RefPtr<MediaTimerPromise> WaitUntil(const T& aTimeStamp, 51 StaticString aCallSite); 52 53 // Cancel and reject any unresolved promises with false. 54 void Cancel(); 55 56 private: 57 virtual ~MediaTimer() { MOZ_ASSERT(OnMediaTimerThread()); } 58 59 void DispatchDestroy(); 60 // Runs on the timer thread. 61 void Destroy(); 62 bool OnMediaTimerThread(); 63 void ScheduleUpdate(); 64 void Update(); 65 void UpdateLocked(); 66 bool IsExpired(const T& aTarget, const T& aNow); 67 void Reject(); 68 /* 69 * We use a callback function, rather than a callback method, to ensure that 70 * the nsITimer does not artifically keep the refcount of the MediaTimer above 71 * zero. When the MediaTimer is destroyed, it safely cancels the nsITimer so 72 * that we never fire against a dangling closure. 73 */ 74 static void TimerCallback(nsITimer* aTimer, void* aClosure); 75 void TimerFired(); 76 void ArmTimer(const T& aTarget, const T& aNow); 77 78 bool TimerIsArmed(); 79 void CancelTimerIfArmed(); 80 81 struct Entry { 82 T mTimeStamp; 83 RefPtr<MediaTimerPromise::Private> mPromise; 84 85 explicit Entry(const T& aTimeStamp, StaticString aCallSite) 86 : mTimeStamp(aTimeStamp), 87 mPromise(new MediaTimerPromise::Private(aCallSite)) {} 88 89 // Define a < overload that reverses ordering because std::priority_queue 90 // provides access to the largest element, and we want the smallest 91 // (i.e. the soonest). 92 bool operator<(const Entry& aOther) const { 93 return mTimeStamp > aOther.mTimeStamp; 94 } 95 }; 96 97 nsCOMPtr<nsIEventTarget> mThread; 98 std::priority_queue<Entry> mEntries; 99 Monitor mMonitor MOZ_UNANNOTATED; 100 nsCOMPtr<nsITimer> mTimer; 101 Maybe<T> mCurrentTimerTarget; 102 103 // Timestamps only have relative meaning, so we need a base timestamp for 104 // logging purposes. 105 T mCreationTimeStamp; 106 int64_t RelativeMicroseconds(const T& aTimeStamp) { 107 return (int64_t)(aTimeStamp - mCreationTimeStamp).ToMicroseconds(); 108 } 109 110 bool mUpdateScheduled; 111 const bool mFuzzy; 112 }; 113 114 // Class for managing delayed dispatches on target thread. 115 template <typename T> 116 class DelayedScheduler { 117 public: 118 explicit DelayedScheduler(nsISerialEventTarget* aTargetThread, 119 bool aFuzzy = false) 120 : mTargetThread(aTargetThread), mMediaTimer(new MediaTimer<T>(aFuzzy)) { 121 MOZ_ASSERT(mTargetThread); 122 } 123 124 bool IsScheduled() const { return mTarget.isSome(); } 125 126 void Reset() { 127 MOZ_ASSERT(mTargetThread->IsOnCurrentThread(), 128 "Must be on target thread to disconnect"); 129 mRequest.DisconnectIfExists(); 130 mTarget = Nothing(); 131 } 132 133 template <typename ResolveFunc, typename RejectFunc> 134 void Ensure(T& aTarget, ResolveFunc&& aResolver, RejectFunc&& aRejector) { 135 MOZ_ASSERT(mTargetThread->IsOnCurrentThread()); 136 if (IsScheduled() && mTarget.value() <= aTarget) { 137 return; 138 } 139 Reset(); 140 mTarget.emplace(aTarget); 141 mMediaTimer->WaitUntil(mTarget.value(), __func__) 142 ->Then(mTargetThread, __func__, std::forward<ResolveFunc>(aResolver), 143 std::forward<RejectFunc>(aRejector)) 144 ->Track(mRequest); 145 } 146 147 void CompleteRequest() { 148 MOZ_ASSERT(mTargetThread->IsOnCurrentThread()); 149 mRequest.Complete(); 150 mTarget = Nothing(); 151 } 152 153 private: 154 nsCOMPtr<nsISerialEventTarget> mTargetThread; 155 RefPtr<MediaTimer<T>> mMediaTimer; 156 Maybe<T> mTarget; 157 MozPromiseRequestHolder<mozilla::MediaTimerPromise> mRequest; 158 }; 159 160 using MediaTimerTimeStamp = MediaTimer<TimeStamp>; 161 using MediaTimerAwakeTimeStamp = MediaTimer<AwakeTimeStamp>; 162 163 } // namespace mozilla 164 165 #endif