/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:set ts=2 sw=2 sts=2 et cindent: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #if !defined(MediaTimer_h_) # define MediaTimer_h_ # include # include "mozilla/AbstractThread.h" # include "mozilla/IntegerPrintfMacros.h" # include "mozilla/Monitor.h" # include "mozilla/MozPromise.h" # include "mozilla/RefPtr.h" # include "mozilla/TimeStamp.h" # include "mozilla/Unused.h" # include "nsITimer.h" namespace mozilla { extern LazyLogModule gMediaTimerLog; # define TIMER_LOG(x, ...) \ MOZ_ASSERT(gMediaTimerLog); \ MOZ_LOG(gMediaTimerLog, LogLevel::Debug, \ ("[MediaTimer=%p relative_t=%" PRId64 "]" x, this, \ RelativeMicroseconds(TimeStamp::Now()), ##__VA_ARGS__)) // This promise type is only exclusive because so far there isn't a reason for // it not to be. Feel free to change that. typedef MozPromise MediaTimerPromise; // Timers only know how to fire at a given thread, which creates an impedence // mismatch with code that operates with TaskQueues. This class solves // that mismatch with a dedicated (but shared) thread and a nice MozPromise-y // interface. class MediaTimer { public: explicit MediaTimer(bool aFuzzy = false); NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DESTROY(MediaTimer, DispatchDestroy()); RefPtr WaitFor(const TimeDuration& aDuration, const char* aCallSite); RefPtr WaitUntil(const TimeStamp& aTimeStamp, const char* aCallSite); void Cancel(); // Cancel and reject any unresolved promises with false. private: virtual ~MediaTimer() { MOZ_ASSERT(OnMediaTimerThread()); } void DispatchDestroy(); // Invoked by Release on an arbitrary thread. void Destroy(); // Runs on the timer thread. bool OnMediaTimerThread(); void ScheduleUpdate(); void Update(); void UpdateLocked(); bool IsExpired(const TimeStamp& aTarget, const TimeStamp& aNow); void Reject(); static void TimerCallback(nsITimer* aTimer, void* aClosure); void TimerFired(); void ArmTimer(const TimeStamp& aTarget, const TimeStamp& aNow); bool TimerIsArmed() { return !mCurrentTimerTarget.IsNull(); } void CancelTimerIfArmed() { MOZ_ASSERT(OnMediaTimerThread()); if (TimerIsArmed()) { TIMER_LOG("MediaTimer::CancelTimerIfArmed canceling timer"); mTimer->Cancel(); mCurrentTimerTarget = TimeStamp(); } } struct Entry { TimeStamp mTimeStamp; RefPtr mPromise; explicit Entry(const TimeStamp& aTimeStamp, const char* aCallSite) : mTimeStamp(aTimeStamp), mPromise(new MediaTimerPromise::Private(aCallSite)) {} // Define a < overload that reverses ordering because std::priority_queue // provides access to the largest element, and we want the smallest // (i.e. the soonest). bool operator<(const Entry& aOther) const { return mTimeStamp > aOther.mTimeStamp; } }; nsCOMPtr mThread; std::priority_queue mEntries; Monitor mMonitor; nsCOMPtr mTimer; TimeStamp mCurrentTimerTarget; // Timestamps only have relative meaning, so we need a base timestamp for // logging purposes. TimeStamp mCreationTimeStamp; int64_t RelativeMicroseconds(const TimeStamp& aTimeStamp) { return (int64_t)(aTimeStamp - mCreationTimeStamp).ToMicroseconds(); } bool mUpdateScheduled; const bool mFuzzy; }; // Class for managing delayed dispatches on target thread. class DelayedScheduler { public: explicit DelayedScheduler(nsISerialEventTarget* aTargetThread, bool aFuzzy = false) : mTargetThread(aTargetThread), mMediaTimer(new MediaTimer(aFuzzy)) { MOZ_ASSERT(mTargetThread); } bool IsScheduled() const { return !mTarget.IsNull(); } void Reset() { MOZ_ASSERT(mTargetThread->IsOnCurrentThread(), "Must be on target thread to disconnect"); mRequest.DisconnectIfExists(); mTarget = TimeStamp(); } template void Ensure(mozilla::TimeStamp& aTarget, ResolveFunc&& aResolver, RejectFunc&& aRejector) { MOZ_ASSERT(mTargetThread->IsOnCurrentThread()); if (IsScheduled() && mTarget <= aTarget) { return; } Reset(); mTarget = aTarget; mMediaTimer->WaitUntil(mTarget, __func__) ->Then(mTargetThread, __func__, std::forward(aResolver), std::forward(aRejector)) ->Track(mRequest); } void CompleteRequest() { MOZ_ASSERT(mTargetThread->IsOnCurrentThread()); mRequest.Complete(); mTarget = TimeStamp(); } private: nsCOMPtr mTargetThread; RefPtr mMediaTimer; MozPromiseRequestHolder mRequest; TimeStamp mTarget; }; } // namespace mozilla #endif