OLD | NEW |
---|---|
1 // Copyright 2011 The Chromium Authors. All rights reserved. | 1 // Copyright 2011 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "cc/scheduler/delay_based_time_source.h" | 5 #include "cc/scheduler/delay_based_time_source.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
8 #include <cmath> | 8 #include <cmath> |
9 | 9 |
10 #include "base/debug/trace_event.h" | 10 #include "base/debug/trace_event.h" |
11 #include "base/logging.h" | 11 #include "base/logging.h" |
12 #include "base/message_loop.h" | 12 #include "base/message_loop.h" |
13 #include "cc/base/thread.h" | 13 #include "cc/base/thread.h" |
14 | 14 |
15 namespace cc { | 15 namespace cc { |
16 | 16 |
17 namespace { | 17 namespace { |
18 | 18 |
19 // doubleTickThreshold prevents ticks from running within the specified fraction of an interval. | 19 // kDoubleTickThreshold prevents ticks from running within the specified |
20 // This helps account for jitter in the timebase as well as quick timer reactiva tion. | 20 // fraction of an interval. This helps account for jitter in the timebase as |
21 const double doubleTickThreshold = 0.25; | 21 // well as quick timer reactivation. |
22 | 22 static const float kDoubleTickThreshold = 0.25; |
jamesr
2013/03/20 04:41:24
well this isn't a double at all. can you update th
enne (OOO)
2013/03/20 04:51:55
I am not 100% sure on this, but I think "double" h
jamesr
2013/03/20 04:59:29
Ooh, ok. 'f' it up then
enne (OOO)
2013/03/20 05:06:20
Done.
| |
23 // intervalChangeThreshold is the fraction of the interval that will trigger an immediate interval change. | 23 |
24 // phaseChangeThreshold is the fraction of the interval that will trigger an imm ediate phase change. | 24 // kIntervalChangeThreshold is the fraction of the interval that will trigger an |
25 // If the changes are within the thresholds, the change will take place on the n ext tick. | 25 // immediate interval change. kPhaseChangeThreshold is the fraction of the |
26 // If either change is outside the thresholds, the next tick will be canceled an d reissued immediately. | 26 // interval that will trigger an immediate phase change. If the changes are |
27 const double intervalChangeThreshold = 0.25; | 27 // within the thresholds, the change will take place on the next tick. If |
28 const double phaseChangeThreshold = 0.25; | 28 // either change is outside the thresholds, the next tick will be canceled and |
29 // reissued immediately. | |
30 static const float kIntervalChangeThreshold = 0.25; | |
31 static const float kPhaseChangeThreshold = 0.25; | |
29 | 32 |
30 } // namespace | 33 } // namespace |
31 | 34 |
32 scoped_refptr<DelayBasedTimeSource> DelayBasedTimeSource::create(base::TimeDelta interval, Thread* thread) | 35 scoped_refptr<DelayBasedTimeSource> DelayBasedTimeSource::Create( |
33 { | 36 base::TimeDelta interval, |
34 return make_scoped_refptr(new DelayBasedTimeSource(interval, thread)); | 37 Thread* thread) { |
35 } | 38 return make_scoped_refptr(new DelayBasedTimeSource(interval, thread)); |
36 | 39 } |
37 DelayBasedTimeSource::DelayBasedTimeSource(base::TimeDelta interval, Thread* thr ead) | 40 |
38 : m_client(0) | 41 DelayBasedTimeSource::DelayBasedTimeSource(base::TimeDelta interval, |
39 , m_hasTickTarget(false) | 42 Thread* thread) |
40 , m_currentParameters(interval, base::TimeTicks()) | 43 : client_(NULL), |
41 , m_nextParameters(interval, base::TimeTicks()) | 44 has_tick_target_(false), |
42 , m_state(STATE_INACTIVE) | 45 current_parameters_(interval, base::TimeTicks()), |
43 , m_thread(thread) | 46 next_parameters_(interval, base::TimeTicks()), |
44 , m_weakFactory(ALLOW_THIS_IN_INITIALIZER_LIST(this)) | 47 state_(STATE_INACTIVE), |
45 { | 48 thread_(thread), |
46 } | 49 weak_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) {} |
47 | 50 |
48 DelayBasedTimeSource::~DelayBasedTimeSource() | 51 DelayBasedTimeSource::~DelayBasedTimeSource() {} |
49 { | 52 |
50 } | 53 void DelayBasedTimeSource::SetActive(bool active) { |
51 | 54 TRACE_EVENT1("cc", "DelayBasedTimeSource::setActive", "active", active); |
52 void DelayBasedTimeSource::setActive(bool active) | 55 if (!active) { |
53 { | 56 state_ = STATE_INACTIVE; |
54 TRACE_EVENT1("cc", "DelayBasedTimeSource::setActive", "active", active); | 57 weak_factory_.InvalidateWeakPtrs(); |
55 if (!active) { | 58 return; |
56 m_state = STATE_INACTIVE; | 59 } |
57 m_weakFactory.InvalidateWeakPtrs(); | 60 |
58 return; | 61 if (state_ == STATE_STARTING || state_ == STATE_ACTIVE) |
59 } | 62 return; |
60 | 63 |
61 if (m_state == STATE_STARTING || m_state == STATE_ACTIVE) | 64 if (!has_tick_target_) { |
62 return; | 65 // Becoming active the first time is deferred: we post a 0-delay task. |
63 | 66 // When it runs, we use that to establish the timebase, become truly |
64 if (!m_hasTickTarget) { | 67 // active, and fire the first tick. |
65 // Becoming active the first time is deferred: we post a 0-delay task. W hen | 68 state_ = STATE_STARTING; |
66 // it runs, we use that to establish the timebase, become truly active, and | 69 thread_->PostTask(base::Bind(&DelayBasedTimeSource::OnTimerFired, |
67 // fire the first tick. | 70 weak_factory_.GetWeakPtr())); |
68 m_state = STATE_STARTING; | 71 return; |
69 m_thread->PostTask(base::Bind(&DelayBasedTimeSource::onTimerFired, m_wea kFactory.GetWeakPtr())); | 72 } |
70 return; | 73 |
71 } | 74 state_ = STATE_ACTIVE; |
72 | 75 |
73 m_state = STATE_ACTIVE; | 76 PostNextTickTask(Now()); |
74 | 77 } |
75 postNextTickTask(now()); | 78 |
76 } | 79 bool DelayBasedTimeSource::Active() const { return state_ != STATE_INACTIVE; } |
77 | 80 |
78 bool DelayBasedTimeSource::active() const | 81 base::TimeTicks DelayBasedTimeSource::LastTickTime() { return last_tick_time_; } |
79 { | 82 |
80 return m_state != STATE_INACTIVE; | 83 base::TimeTicks DelayBasedTimeSource::NextTickTime() { |
81 } | 84 return Active() ? current_parameters_.tick_target : base::TimeTicks(); |
82 | 85 } |
83 base::TimeTicks DelayBasedTimeSource::lastTickTime() | 86 |
84 { | 87 void DelayBasedTimeSource::OnTimerFired() { |
85 return m_lastTickTime; | 88 DCHECK(state_ != STATE_INACTIVE); |
86 } | 89 |
87 | 90 base::TimeTicks now = this->Now(); |
88 base::TimeTicks DelayBasedTimeSource::nextTickTime() | 91 last_tick_time_ = now; |
89 { | 92 |
90 return active() ? m_currentParameters.tickTarget : base::TimeTicks(); | 93 if (state_ == STATE_STARTING) { |
91 } | 94 SetTimebaseAndInterval(now, current_parameters_.interval); |
92 | 95 state_ = STATE_ACTIVE; |
93 void DelayBasedTimeSource::onTimerFired() | 96 } |
94 { | 97 |
95 DCHECK(m_state != STATE_INACTIVE); | 98 PostNextTickTask(now); |
96 | 99 |
97 base::TimeTicks now = this->now(); | 100 // Fire the tick. |
98 m_lastTickTime = now; | 101 if (client_) |
99 | 102 client_->OnTimerTick(); |
100 if (m_state == STATE_STARTING) { | 103 } |
101 setTimebaseAndInterval(now, m_currentParameters.interval); | 104 |
102 m_state = STATE_ACTIVE; | 105 void DelayBasedTimeSource::SetClient(TimeSourceClient* client) { |
103 } | 106 client_ = client; |
104 | 107 } |
105 postNextTickTask(now); | 108 |
106 | 109 void DelayBasedTimeSource::SetTimebaseAndInterval(base::TimeTicks timebase, |
107 // Fire the tick | 110 base::TimeDelta interval) { |
108 if (m_client) | 111 next_parameters_.interval = interval; |
109 m_client->onTimerTick(); | 112 next_parameters_.tick_target = timebase; |
110 } | 113 has_tick_target_ = true; |
111 | 114 |
112 void DelayBasedTimeSource::setClient(TimeSourceClient* client) | 115 if (state_ != STATE_ACTIVE) { |
113 { | 116 // If we aren't active, there's no need to reset the timer. |
114 m_client = client; | 117 return; |
115 } | 118 } |
116 | 119 |
117 void DelayBasedTimeSource::setTimebaseAndInterval(base::TimeTicks timebase, base ::TimeDelta interval) | 120 // If the change in interval is larger than the change threshold, |
118 { | 121 // request an immediate reset. |
119 m_nextParameters.interval = interval; | 122 float interval_delta = |
120 m_nextParameters.tickTarget = timebase; | 123 std::abs((interval - current_parameters_.interval).InSecondsF()); |
121 m_hasTickTarget = true; | 124 float interval_change = interval_delta / interval.InSecondsF(); |
122 | 125 if (interval_change > kIntervalChangeThreshold) { |
123 if (m_state != STATE_ACTIVE) { | 126 SetActive(false); |
124 // If we aren't active, there's no need to reset the timer. | 127 SetActive(true); |
125 return; | 128 return; |
126 } | 129 } |
127 | 130 |
128 // If the change in interval is larger than the change threshold, | 131 // If the change in phase is greater than the change threshold in either |
129 // request an immediate reset. | 132 // direction, request an immediate reset. This logic might result in a false |
130 double intervalDelta = std::abs((interval - m_currentParameters.interval).In SecondsF()); | 133 // negative if there is a simultaneous small change in the interval and the |
131 double intervalChange = intervalDelta / interval.InSecondsF(); | 134 // fmod just happens to return something near zero. Assuming the timebase |
132 if (intervalChange > intervalChangeThreshold) { | 135 // is very recent though, which it should be, we'll still be ok because the |
133 setActive(false); | 136 // old clock and new clock just happen to line up. |
134 setActive(true); | 137 float target_delta = |
135 return; | 138 std::abs((timebase - current_parameters_.tick_target).InSecondsF()); |
136 } | 139 float phase_change = |
137 | 140 fmod(target_delta, interval.InSecondsF()) / interval.InSecondsF(); |
138 // If the change in phase is greater than the change threshold in either | 141 if (phase_change > kPhaseChangeThreshold && |
139 // direction, request an immediate reset. This logic might result in a false | 142 phase_change < (1.f - kPhaseChangeThreshold)) { |
140 // negative if there is a simultaneous small change in the interval and the | 143 SetActive(false); |
141 // fmod just happens to return something near zero. Assuming the timebase | 144 SetActive(true); |
142 // is very recent though, which it should be, we'll still be ok because the | 145 return; |
143 // old clock and new clock just happen to line up. | 146 } |
144 double targetDelta = std::abs((timebase - m_currentParameters.tickTarget).In SecondsF()); | 147 } |
145 double phaseChange = fmod(targetDelta, interval.InSecondsF()) / interval.InS econdsF(); | 148 |
146 if (phaseChange > phaseChangeThreshold && phaseChange < (1.0 - phaseChangeTh reshold)) { | 149 base::TimeTicks DelayBasedTimeSource::Now() const { |
147 setActive(false); | 150 return base::TimeTicks::Now(); |
148 setActive(true); | 151 } |
149 return; | 152 |
150 } | 153 // This code tries to achieve an average tick rate as close to interval_ as |
151 } | 154 // possible. To do this, it has to deal with a few basic issues: |
152 | 155 // 1. postDelayedTask can delay only at a millisecond granularity. So, 16.666 |
153 base::TimeTicks DelayBasedTimeSource::now() const | 156 // has to posted as 16 or 17. |
154 { | 157 // 2. A delayed task may come back a bit late (a few ms), or really late |
155 return base::TimeTicks::Now(); | 158 // (frames later) |
156 } | 159 // |
157 | 160 // The basic idea with this scheduler here is to keep track of where we *want* |
158 // This code tries to achieve an average tick rate as close to m_interval as pos sible. | 161 // to run in tick_target_. We update this with the exact interval. |
159 // To do this, it has to deal with a few basic issues: | 162 // |
160 // 1. postDelayedTask can delay only at a millisecond granularity. So, 16.666 has to | 163 // Then, when we post our task, we take the floor of (tick_target_ and Now()). |
161 // posted as 16 or 17. | 164 // If we started at now=0, and 60FPs (all times in milliseconds): |
162 // 2. A delayed task may come back a bit late (a few ms), or really late (fram es later) | |
163 // | |
164 // The basic idea with this scheduler here is to keep track of where we *want* t o run in | |
165 // m_tickTarget. We update this with the exact interval. | |
166 // | |
167 // Then, when we post our task, we take the floor of (m_tickTarget and now()). I f we | |
168 // started at now=0, and 60FPs (all times in milliseconds): | |
169 // now=0 target=16.667 postDelayedTask(16) | 165 // now=0 target=16.667 postDelayedTask(16) |
170 // | 166 // |
171 // When our callback runs, we figure out how far off we were from that goal. Bec ause of the flooring | 167 // When our callback runs, we figure out how far off we were from that goal. |
172 // operation, and assuming our timer runs exactly when it should, this yields: | 168 // Because of the flooring operation, and assuming our timer runs exactly when |
169 // it should, this yields: | |
173 // now=16 target=16.667 | 170 // now=16 target=16.667 |
174 // | 171 // |
175 // Since we can't post a 0.667 ms task to get to now=16, we just treat this as a tick. Then, | 172 // Since we can't post a 0.667 ms task to get to now=16, we just treat this as a |
176 // we update target to be 33.333. We now post another task based on the differen ce between our target | 173 // tick. Then, we update target to be 33.333. We now post another task based on |
177 // and now: | 174 // the difference between our target and now: |
178 // now=16 tickTarget=16.667 newTarget=33.333 --> postDelayedTask(floor (33.333 - 16)) --> postDelayedTask(17) | 175 // now=16 tick_target=16.667 newTarget=33.333 --> |
176 // postDelayedTask(floor(33.333 - 16)) --> postDelayedTask(17) | |
179 // | 177 // |
180 // Over time, with no late tasks, this leads to us posting tasks like this: | 178 // Over time, with no late tasks, this leads to us posting tasks like this: |
181 // now=0 tickTarget=0 newTarget=16.667 --> tick(), postDelayedTa sk(16) | 179 // now=0 tick_target=0 newTarget=16.667 --> |
182 // now=16 tickTarget=16.667 newTarget=33.333 --> tick(), postDelayedTa sk(17) | 180 // tick(), postDelayedTask(16) |
183 // now=33 tickTarget=33.333 newTarget=50.000 --> tick(), postDelayedTa sk(17) | 181 // now=16 tick_target=16.667 newTarget=33.333 --> |
184 // now=50 tickTarget=50.000 newTarget=66.667 --> tick(), postDelayedTa sk(16) | 182 // tick(), postDelayedTask(17) |
185 // | 183 // now=33 tick_target=33.333 newTarget=50.000 --> |
186 // We treat delays in tasks differently depending on the amount of delay we enco unter. Suppose we | 184 // tick(), postDelayedTask(17) |
187 // posted a task with a target=16.667: | 185 // now=50 tick_target=50.000 newTarget=66.667 --> |
186 // tick(), postDelayedTask(16) | |
187 // | |
188 // We treat delays in tasks differently depending on the amount of delay we | |
189 // encounter. Suppose we posted a task with a target=16.667: | |
188 // Case 1: late but not unrecoverably-so | 190 // Case 1: late but not unrecoverably-so |
189 // now=18 tickTarget=16.667 | 191 // now=18 tick_target=16.667 |
190 // | 192 // |
191 // Case 2: so late we obviously missed the tick | 193 // Case 2: so late we obviously missed the tick |
192 // now=25.0 tickTarget=16.667 | 194 // now=25.0 tick_target=16.667 |
193 // | 195 // |
194 // We treat the first case as a tick anyway, and assume the delay was | 196 // We treat the first case as a tick anyway, and assume the delay was unusual. |
195 // unusual. Thus, we compute the newTarget based on the old timebase: | 197 // Thus, we compute the newTarget based on the old timebase: |
196 // now=18 tickTarget=16.667 newTarget=33.333 --> tick(), postDelayedTa sk(floor(33.333-18)) --> postDelayedTask(15) | 198 // now=18 tick_target=16.667 newTarget=33.333 --> |
197 // This brings us back to 18+15 = 33, which was where we would have been if the task hadn't been late. | 199 // tick(), postDelayedTask(floor(33.333-18)) --> postDelayedTask(15) |
198 // | 200 // This brings us back to 18+15 = 33, which was where we would have been if the |
199 // For the really late delay, we we move to the next logical tick. The timebase is not reset. | 201 // task hadn't been late. |
200 // now=37 tickTarget=16.667 newTarget=50.000 --> tick(), postDelayedTas k(floor(50.000-37)) --> postDelayedTask(13) | 202 // |
201 base::TimeTicks DelayBasedTimeSource::nextTickTarget(base::TimeTicks now) | 203 // For the really late delay, we we move to the next logical tick. The timebase |
202 { | 204 // is not reset. |
203 base::TimeDelta newInterval = m_nextParameters.interval; | 205 // now=37 tick_target=16.667 newTarget=50.000 --> |
204 int intervalsElapsed = static_cast<int>(floor((now - m_nextParameters.tickTa rget).InSecondsF() / newInterval.InSecondsF())); | 206 // tick(), postDelayedTask(floor(50.000-37)) --> postDelayedTask(13) |
205 base::TimeTicks lastEffectiveTick = m_nextParameters.tickTarget + newInterva l * intervalsElapsed; | 207 base::TimeTicks DelayBasedTimeSource::NextTickTarget(base::TimeTicks now) { |
206 base::TimeTicks newTickTarget = lastEffectiveTick + newInterval; | 208 base::TimeDelta new_interval = next_parameters_.interval; |
207 DCHECK(newTickTarget > now); | 209 int intervals_elapsed = |
208 | 210 static_cast<int>(floor((now - next_parameters_.tick_target).InSecondsF() / |
209 // Avoid double ticks when: | 211 new_interval.InSecondsF())); |
210 // 1) Turning off the timer and turning it right back on. | 212 base::TimeTicks last_effective_tick = |
211 // 2) Jittery data is passed to setTimebaseAndInterval(). | 213 next_parameters_.tick_target + new_interval * intervals_elapsed; |
212 if (newTickTarget - m_lastTickTime <= newInterval / static_cast<int>(1.0 / d oubleTickThreshold)) | 214 base::TimeTicks new_tick_target = last_effective_tick + new_interval; |
213 newTickTarget += newInterval; | 215 DCHECK(new_tick_target > now); |
214 | 216 |
215 return newTickTarget; | 217 // Avoid double ticks when: |
216 } | 218 // 1) Turning off the timer and turning it right back on. |
217 | 219 // 2) Jittery data is passed to SetTimebaseAndInterval(). |
218 void DelayBasedTimeSource::postNextTickTask(base::TimeTicks now) | 220 if (new_tick_target - last_tick_time_ <= |
219 { | 221 new_interval / static_cast<int>(1.f / kDoubleTickThreshold)) |
220 base::TimeTicks newTickTarget = nextTickTarget(now); | 222 new_tick_target += new_interval; |
221 | 223 |
222 // Post another task *before* the tick and update state | 224 return new_tick_target; |
223 base::TimeDelta delay = newTickTarget - now; | 225 } |
224 DCHECK(delay.InMillisecondsF() <= | 226 |
225 m_nextParameters.interval.InMillisecondsF() * (1.0 + doubleTickThresh old)); | 227 void DelayBasedTimeSource::PostNextTickTask(base::TimeTicks now) { |
226 m_thread->PostDelayedTask(base::Bind(&DelayBasedTimeSource::onTimerFired, | 228 base::TimeTicks new_tick_target = NextTickTarget(now); |
227 m_weakFactory.GetWeakPtr()), | 229 |
228 delay); | 230 // Post another task *before* the tick and update state |
229 | 231 base::TimeDelta delay = new_tick_target - now; |
230 m_nextParameters.tickTarget = newTickTarget; | 232 DCHECK(delay.InMillisecondsF() <= |
231 m_currentParameters = m_nextParameters; | 233 next_parameters_.interval.InMillisecondsF() * |
234 (1.f + kDoubleTickThreshold)); | |
235 thread_->PostDelayedTask(base::Bind(&DelayBasedTimeSource::OnTimerFired, | |
236 weak_factory_.GetWeakPtr()), | |
237 delay); | |
238 | |
239 next_parameters_.tick_target = new_tick_target; | |
240 current_parameters_ = next_parameters_; | |
232 } | 241 } |
233 | 242 |
234 } // namespace cc | 243 } // namespace cc |
OLD | NEW |