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/. */
7 #include "MediaTimer.h"
9 #include "mozilla/AwakeTimeStamp.h"
10 #include "mozilla/DebugOnly.h"
11 #include "mozilla/RefPtr.h"
12 #include "mozilla/SharedThreadPool.h"
13 #include "mozilla/Unused.h"
14 #include "nsComponentManagerUtils.h"
15 #include "nsThreadUtils.h"
21 MediaTimer
<T
>::MediaTimer(bool aFuzzy
)
22 : mMonitor("MediaTimer Monitor"),
23 mCreationTimeStamp(T::Now()),
24 mUpdateScheduled(false),
26 TIMER_LOG("MediaTimer::MediaTimer");
28 // Use the SharedThreadPool to create an nsIThreadPool with a maximum of one
29 // thread, which is equivalent to an nsIThread for our purposes.
30 RefPtr
<SharedThreadPool
> threadPool(
31 SharedThreadPool::Get("MediaTimer"_ns
, 1));
32 mThread
= threadPool
.get();
33 mTimer
= NS_NewTimer(mThread
);
37 void MediaTimer
<T
>::DispatchDestroy() {
38 // Hold a strong reference to the thread so that it doesn't get deleted in
39 // Destroy(), which may run completely before the stack if Dispatch() begins
41 nsCOMPtr
<nsIEventTarget
> thread
= mThread
;
43 thread
->Dispatch(NewNonOwningRunnableMethod("MediaTimer::Destroy", this,
44 &MediaTimer::Destroy
),
46 MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv
));
51 // Runs on the timer thread.
53 void MediaTimer
<T
>::Destroy() {
54 MOZ_ASSERT(OnMediaTimerThread());
55 TIMER_LOG("MediaTimer::Destroy");
57 // Reject any outstanding entries.
59 MonitorAutoLock
lock(mMonitor
);
62 // Cancel the timer if necessary.
70 bool MediaTimer
<T
>::OnMediaTimerThread() {
72 mThread
->IsOnCurrentThread(&rv
);
77 RefPtr
<MediaTimerPromise
> MediaTimer
<T
>::WaitFor(
78 const typename
T::DurationType
& aDuration
, StaticString aCallSite
) {
79 return WaitUntil(T::Now() + aDuration
, aCallSite
);
83 RefPtr
<MediaTimerPromise
> MediaTimer
<T
>::WaitUntil(const T
& aTimeStamp
,
84 StaticString aCallSite
) {
85 MonitorAutoLock
mon(mMonitor
);
86 TIMER_LOG("MediaTimer::WaitUntil %" PRId64
, RelativeMicroseconds(aTimeStamp
));
87 Entry
e(aTimeStamp
, aCallSite
);
88 RefPtr
<MediaTimerPromise
> p
= e
.mPromise
.get();
95 void MediaTimer
<T
>::Cancel() {
96 MonitorAutoLock
mon(mMonitor
);
97 TIMER_LOG("MediaTimer::Cancel");
101 template <typename T
>
102 void MediaTimer
<T
>::ScheduleUpdate() {
103 mMonitor
.AssertCurrentThreadOwns();
104 if (mUpdateScheduled
) {
107 mUpdateScheduled
= true;
109 nsresult rv
= mThread
->Dispatch(
110 NewRunnableMethod("MediaTimer::Update", this, &MediaTimer::Update
),
112 MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv
));
117 template <typename T
>
118 void MediaTimer
<T
>::Update() {
119 MonitorAutoLock
mon(mMonitor
);
123 template <typename T
>
124 bool MediaTimer
<T
>::IsExpired(const T
& aTarget
, const T
& aNow
) {
125 MOZ_ASSERT(OnMediaTimerThread());
126 mMonitor
.AssertCurrentThreadOwns();
127 // Treat this timer as expired in fuzzy mode even if it is fired
128 // slightly (< 1ms) before the schedule target. So we don't need to schedule
129 // a timer with very small timeout again when the client doesn't need a
131 T t
= mFuzzy
? aTarget
- T::DurationType::FromMilliseconds(1) : aTarget
;
135 template <typename T
>
136 void MediaTimer
<T
>::UpdateLocked() {
137 MOZ_ASSERT(OnMediaTimerThread());
138 mMonitor
.AssertCurrentThreadOwns();
139 mUpdateScheduled
= false;
141 TIMER_LOG("MediaTimer::UpdateLocked");
143 // Resolve all the promises whose time is up.
145 while (!mEntries
.empty() && IsExpired(mEntries
.top().mTimeStamp
, now
)) {
146 mEntries
.top().mPromise
->Resolve(true, __func__
);
147 DebugOnly
<T
> poppedTimeStamp
= mEntries
.top().mTimeStamp
;
149 MOZ_ASSERT_IF(!mEntries
.empty(),
150 *&poppedTimeStamp
<= mEntries
.top().mTimeStamp
);
153 // If we've got no more entries, cancel any pending timer and bail out.
154 if (mEntries
.empty()) {
155 CancelTimerIfArmed();
159 // We've got more entries - (re)arm the timer for the soonest one.
160 if (!TimerIsArmed() ||
161 mEntries
.top().mTimeStamp
< mCurrentTimerTarget
.value()) {
162 CancelTimerIfArmed();
163 ArmTimer(mEntries
.top().mTimeStamp
, now
);
167 template <typename T
>
168 void MediaTimer
<T
>::Reject() {
169 mMonitor
.AssertCurrentThreadOwns();
170 while (!mEntries
.empty()) {
171 mEntries
.top().mPromise
->Reject(false, __func__
);
176 template <typename T
>
177 /* static */ void MediaTimer
<T
>::TimerCallback(nsITimer
* aTimer
,
179 static_cast<MediaTimer
<T
>*>(aClosure
)->TimerFired();
182 template <typename T
>
183 void MediaTimer
<T
>::TimerFired() {
184 MonitorAutoLock
mon(mMonitor
);
185 MOZ_ASSERT(OnMediaTimerThread());
186 mCurrentTimerTarget
= Nothing();
190 template <typename T
>
191 void MediaTimer
<T
>::ArmTimer(const T
& aTarget
, const T
& aNow
) {
192 MOZ_DIAGNOSTIC_ASSERT(!TimerIsArmed());
193 MOZ_DIAGNOSTIC_ASSERT(aTarget
> aNow
);
195 const typename
T::DurationType delay
= aTarget
- aNow
;
196 TIMER_LOG("MediaTimer::ArmTimer delay=%.3fms", delay
.ToMilliseconds());
197 mCurrentTimerTarget
.emplace(aTarget
);
198 TimeDuration duration
=
199 TimeDuration::FromMicroseconds(delay
.ToMicroseconds());
200 MOZ_ALWAYS_SUCCEEDS(mTimer
->InitHighResolutionWithNamedFuncCallback(
201 &TimerCallback
, this, duration
, nsITimer::TYPE_ONE_SHOT
,
202 "MediaTimer::TimerCallback"));
205 template <typename T
>
206 bool MediaTimer
<T
>::TimerIsArmed() {
207 return mCurrentTimerTarget
.isSome();
210 template <typename T
>
211 void MediaTimer
<T
>::CancelTimerIfArmed() {
212 MOZ_ASSERT(OnMediaTimerThread());
213 if (TimerIsArmed()) {
214 TIMER_LOG("MediaTimer::CancelTimerIfArmed canceling timer");
216 mCurrentTimerTarget
= Nothing();
220 template class MediaTimer
<AwakeTimeStamp
>;
221 template class MediaTimer
<TimeStamp
>;
223 } // namespace mozilla