diff options
Diffstat (limited to 'dom/media/MediaTimer.h')
-rw-r--r-- | dom/media/MediaTimer.h | 170 |
1 files changed, 170 insertions, 0 deletions
diff --git a/dom/media/MediaTimer.h b/dom/media/MediaTimer.h new file mode 100644 index 000000000..3d6269936 --- /dev/null +++ b/dom/media/MediaTimer.h @@ -0,0 +1,170 @@ +/* -*- 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 "mozilla/Monitor.h" +#include "mozilla/MozPromise.h" +#include "mozilla/TimeStamp.h" + +#include <queue> + +#include "nsITimer.h" +#include "mozilla/RefPtr.h" + +namespace mozilla { + +extern LazyLogModule gMediaTimerLog; + +#define TIMER_LOG(x, ...) \ + MOZ_ASSERT(gMediaTimerLog); \ + MOZ_LOG(gMediaTimerLog, LogLevel::Debug, ("[MediaTimer=%p relative_t=%lld]" 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<bool, bool, /* IsExclusive = */ true> 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: + MediaTimer(); + + // We use a release with a custom Destroy(). + NS_IMETHOD_(MozExternalRefCountType) AddRef(void); + NS_IMETHOD_(MozExternalRefCountType) Release(void); + + RefPtr<MediaTimerPromise> WaitUntil(const TimeStamp& aTimeStamp, const char* aCallSite); + +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(); + + 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<MediaTimerPromise::Private> 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; + } + }; + + ThreadSafeAutoRefCnt mRefCnt; + NS_DECL_OWNINGTHREAD + nsCOMPtr<nsIEventTarget> mThread; + std::priority_queue<Entry> mEntries; + Monitor mMonitor; + nsCOMPtr<nsITimer> 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; +}; + +// Class for managing delayed dispatches on target thread. +class DelayedScheduler { +public: + explicit DelayedScheduler(AbstractThread* aTargetThread) + : mTargetThread(aTargetThread), mMediaTimer(new MediaTimer()) + { + MOZ_ASSERT(mTargetThread); + } + + bool IsScheduled() const { return !mTarget.IsNull(); } + + void Reset() + { + MOZ_ASSERT(mTargetThread->IsCurrentThreadIn(), + "Must be on target thread to disconnect"); + if (IsScheduled()) { + mRequest.Disconnect(); + mTarget = TimeStamp(); + } + } + + template <typename ResolveFunc, typename RejectFunc> + void Ensure(mozilla::TimeStamp& aTarget, + ResolveFunc&& aResolver, + RejectFunc&& aRejector) + { + MOZ_ASSERT(mTargetThread->IsCurrentThreadIn()); + if (IsScheduled() && mTarget <= aTarget) { + return; + } + Reset(); + mTarget = aTarget; + mRequest.Begin(mMediaTimer->WaitUntil(mTarget, __func__)->Then( + mTargetThread, __func__, + Forward<ResolveFunc>(aResolver), + Forward<RejectFunc>(aRejector))); + } + + void CompleteRequest() + { + MOZ_ASSERT(mTargetThread->IsCurrentThreadIn()); + mRequest.Complete(); + mTarget = TimeStamp(); + } + +private: + RefPtr<AbstractThread> mTargetThread; + RefPtr<MediaTimer> mMediaTimer; + MozPromiseRequestHolder<mozilla::MediaTimerPromise> mRequest; + TimeStamp mTarget; +}; + +} // namespace mozilla + +#endif |