Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(196)

Side by Side Diff: cc/scheduler/delay_based_time_source.cc

Issue 12623026: cc: Chromify TimeSource, DelayBasedTimeSource and test (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 7 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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
OLDNEW
« no previous file with comments | « cc/scheduler/delay_based_time_source.h ('k') | cc/scheduler/delay_based_time_source_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698