Revert 264226 "Reduce dependency of TiclInvalidationService on P..."
[chromium-blink-merge.git] / cc / scheduler / scheduler.cc
blob5d7001065f01dcdf1dbe0f1f9884e0544bfe8a22
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
3 // found in the LICENSE file.
5 #include "cc/scheduler/scheduler.h"
7 #include <algorithm>
8 #include "base/auto_reset.h"
9 #include "base/debug/trace_event.h"
10 #include "base/logging.h"
11 #include "cc/debug/devtools_instrumentation.h"
12 #include "cc/debug/traced_value.h"
13 #include "ui/gfx/frame_time.h"
15 namespace cc {
17 Scheduler::Scheduler(
18 SchedulerClient* client,
19 const SchedulerSettings& scheduler_settings,
20 int layer_tree_host_id,
21 const scoped_refptr<base::SequencedTaskRunner>& impl_task_runner)
22 : settings_(scheduler_settings),
23 client_(client),
24 layer_tree_host_id_(layer_tree_host_id),
25 impl_task_runner_(impl_task_runner),
26 last_set_needs_begin_frame_(false),
27 begin_retro_frame_posted_(false),
28 state_machine_(scheduler_settings),
29 inside_process_scheduled_actions_(false),
30 inside_action_(SchedulerStateMachine::ACTION_NONE),
31 weak_factory_(this) {
32 DCHECK(client_);
33 DCHECK(!state_machine_.BeginFrameNeeded());
34 if (settings_.main_frame_before_activation_enabled) {
35 DCHECK(settings_.main_frame_before_draw_enabled);
38 begin_retro_frame_closure_ =
39 base::Bind(&Scheduler::BeginRetroFrame, weak_factory_.GetWeakPtr());
40 begin_impl_frame_deadline_closure_ = base::Bind(
41 &Scheduler::OnBeginImplFrameDeadline, weak_factory_.GetWeakPtr());
42 poll_for_draw_triggers_closure_ = base::Bind(
43 &Scheduler::PollForAnticipatedDrawTriggers, weak_factory_.GetWeakPtr());
44 advance_commit_state_closure_ = base::Bind(
45 &Scheduler::PollToAdvanceCommitState, weak_factory_.GetWeakPtr());
48 Scheduler::~Scheduler() {}
50 void Scheduler::SetCanStart() {
51 state_machine_.SetCanStart();
52 ProcessScheduledActions();
55 void Scheduler::SetVisible(bool visible) {
56 state_machine_.SetVisible(visible);
57 ProcessScheduledActions();
60 void Scheduler::SetCanDraw(bool can_draw) {
61 state_machine_.SetCanDraw(can_draw);
62 ProcessScheduledActions();
65 void Scheduler::NotifyReadyToActivate() {
66 state_machine_.NotifyReadyToActivate();
67 ProcessScheduledActions();
70 void Scheduler::ActivatePendingTree() {
71 client_->ScheduledActionActivatePendingTree();
74 void Scheduler::SetNeedsCommit() {
75 state_machine_.SetNeedsCommit();
76 ProcessScheduledActions();
79 void Scheduler::SetNeedsForcedCommitForReadback() {
80 state_machine_.SetNeedsForcedCommitForReadback();
81 ProcessScheduledActions();
84 void Scheduler::SetNeedsRedraw() {
85 state_machine_.SetNeedsRedraw();
86 ProcessScheduledActions();
89 void Scheduler::SetNeedsManageTiles() {
90 DCHECK(!IsInsideAction(SchedulerStateMachine::ACTION_MANAGE_TILES));
91 state_machine_.SetNeedsManageTiles();
92 ProcessScheduledActions();
95 void Scheduler::SetSwapUsedIncompleteTile(bool used_incomplete_tile) {
96 state_machine_.SetSwapUsedIncompleteTile(used_incomplete_tile);
97 ProcessScheduledActions();
100 void Scheduler::SetSmoothnessTakesPriority(bool smoothness_takes_priority) {
101 state_machine_.SetSmoothnessTakesPriority(smoothness_takes_priority);
102 ProcessScheduledActions();
105 void Scheduler::NotifyReadyToCommit() {
106 TRACE_EVENT0("cc", "Scheduler::NotifyReadyToCommit");
107 state_machine_.NotifyReadyToCommit();
108 ProcessScheduledActions();
111 void Scheduler::BeginMainFrameAborted(bool did_handle) {
112 TRACE_EVENT0("cc", "Scheduler::BeginMainFrameAborted");
113 state_machine_.BeginMainFrameAborted(did_handle);
114 ProcessScheduledActions();
117 void Scheduler::DidManageTiles() {
118 state_machine_.DidManageTiles();
121 void Scheduler::DidLoseOutputSurface() {
122 TRACE_EVENT0("cc", "Scheduler::DidLoseOutputSurface");
123 state_machine_.DidLoseOutputSurface();
124 last_set_needs_begin_frame_ = false;
125 begin_retro_frame_args_.clear();
126 ProcessScheduledActions();
129 void Scheduler::DidCreateAndInitializeOutputSurface() {
130 TRACE_EVENT0("cc", "Scheduler::DidCreateAndInitializeOutputSurface");
131 DCHECK(!last_set_needs_begin_frame_);
132 DCHECK(begin_impl_frame_deadline_task_.IsCancelled());
133 state_machine_.DidCreateAndInitializeOutputSurface();
134 ProcessScheduledActions();
137 void Scheduler::NotifyBeginMainFrameStarted() {
138 TRACE_EVENT0("cc", "Scheduler::NotifyBeginMainFrameStarted");
139 state_machine_.NotifyBeginMainFrameStarted();
142 base::TimeTicks Scheduler::AnticipatedDrawTime() const {
143 if (!last_set_needs_begin_frame_ ||
144 begin_impl_frame_args_.interval <= base::TimeDelta())
145 return base::TimeTicks();
147 base::TimeTicks now = gfx::FrameTime::Now();
148 base::TimeTicks timebase = std::max(begin_impl_frame_args_.frame_time,
149 begin_impl_frame_args_.deadline);
150 int64 intervals = 1 + ((now - timebase) / begin_impl_frame_args_.interval);
151 return timebase + (begin_impl_frame_args_.interval * intervals);
154 base::TimeTicks Scheduler::LastBeginImplFrameTime() {
155 return begin_impl_frame_args_.frame_time;
158 void Scheduler::SetupNextBeginFrameIfNeeded() {
159 bool needs_begin_frame = state_machine_.BeginFrameNeeded();
161 bool at_end_of_deadline =
162 state_machine_.begin_impl_frame_state() ==
163 SchedulerStateMachine::BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE;
165 bool should_call_set_needs_begin_frame =
166 // Always request the BeginFrame immediately if it wasn't needed before.
167 (needs_begin_frame && !last_set_needs_begin_frame_) ||
168 // We always need to explicitly request our next BeginFrame.
169 at_end_of_deadline;
171 if (should_call_set_needs_begin_frame) {
172 client_->SetNeedsBeginFrame(needs_begin_frame);
173 last_set_needs_begin_frame_ = needs_begin_frame;
176 // Handle retroactive BeginFrames.
177 if (last_set_needs_begin_frame_)
178 PostBeginRetroFrameIfNeeded();
180 bool needs_advance_commit_state_timer = false;
181 // Setup PollForAnticipatedDrawTriggers if we need to monitor state but
182 // aren't expecting any more BeginFrames. This should only be needed by
183 // the synchronous compositor when BeginFrameNeeded is false.
184 if (state_machine_.ShouldPollForAnticipatedDrawTriggers()) {
185 DCHECK(!state_machine_.SupportsProactiveBeginFrame());
186 DCHECK(!needs_begin_frame);
187 if (poll_for_draw_triggers_task_.IsCancelled()) {
188 poll_for_draw_triggers_task_.Reset(poll_for_draw_triggers_closure_);
189 base::TimeDelta delay = begin_impl_frame_args_.IsValid()
190 ? begin_impl_frame_args_.interval
191 : BeginFrameArgs::DefaultInterval();
192 impl_task_runner_->PostDelayedTask(
193 FROM_HERE, poll_for_draw_triggers_task_.callback(), delay);
195 } else {
196 poll_for_draw_triggers_task_.Cancel();
198 // At this point we'd prefer to advance through the commit flow by
199 // drawing a frame, however it's possible that the frame rate controller
200 // will not give us a BeginFrame until the commit completes. See
201 // crbug.com/317430 for an example of a swap ack being held on commit. Thus
202 // we set a repeating timer to poll on ProcessScheduledActions until we
203 // successfully reach BeginFrame. Synchronous compositor does not use
204 // frame rate controller or have the circular wait in the bug.
205 if (IsBeginMainFrameSentOrStarted() &&
206 !settings_.using_synchronous_renderer_compositor) {
207 needs_advance_commit_state_timer = true;
211 if (needs_advance_commit_state_timer) {
212 if (advance_commit_state_task_.IsCancelled() &&
213 begin_impl_frame_args_.IsValid()) {
214 // Since we'd rather get a BeginImplFrame by the normal mechanism, we
215 // set the interval to twice the interval from the previous frame.
216 advance_commit_state_task_.Reset(advance_commit_state_closure_);
217 impl_task_runner_->PostDelayedTask(FROM_HERE,
218 advance_commit_state_task_.callback(),
219 begin_impl_frame_args_.interval * 2);
221 } else {
222 advance_commit_state_task_.Cancel();
226 // BeginFrame is the mechanism that tells us that now is a good time to start
227 // making a frame. Usually this means that user input for the frame is complete.
228 // If the scheduler is busy, we queue the BeginFrame to be handled later as
229 // a BeginRetroFrame.
230 void Scheduler::BeginFrame(const BeginFrameArgs& args) {
231 bool should_defer_begin_frame;
232 if (settings_.using_synchronous_renderer_compositor) {
233 should_defer_begin_frame = false;
234 } else {
235 should_defer_begin_frame =
236 !begin_retro_frame_args_.empty() || begin_retro_frame_posted_ ||
237 !last_set_needs_begin_frame_ ||
238 (state_machine_.begin_impl_frame_state() !=
239 SchedulerStateMachine::BEGIN_IMPL_FRAME_STATE_IDLE);
242 if (should_defer_begin_frame) {
243 begin_retro_frame_args_.push_back(args);
244 TRACE_EVENT_INSTANT0(
245 "cc", "Scheduler::BeginFrame deferred", TRACE_EVENT_SCOPE_THREAD);
246 return;
249 BeginImplFrame(args);
252 // BeginRetroFrame is called for BeginFrames that we've deferred because
253 // the scheduler was in the middle of processing a previous BeginFrame.
254 void Scheduler::BeginRetroFrame() {
255 TRACE_EVENT0("cc", "Scheduler::BeginRetroFrame");
256 DCHECK(begin_retro_frame_posted_);
257 DCHECK(!begin_retro_frame_args_.empty());
258 DCHECK(!settings_.using_synchronous_renderer_compositor);
260 // Discard expired BeginRetroFrames
261 // Today, we should always end up with at most one un-expired BeginRetroFrame
262 // because deadlines will not be greater than the next frame time. We don't
263 // DCHECK though because some systems don't always have monotonic timestamps.
264 // TODO(brianderson): In the future, long deadlines could result in us not
265 // draining the queue if we don't catch up. If we consistently can't catch
266 // up, our fallback should be to lower our frame rate.
267 base::TimeTicks now = gfx::FrameTime::Now();
268 base::TimeDelta draw_duration_estimate = client_->DrawDurationEstimate();
269 while (!begin_retro_frame_args_.empty() &&
270 now > AdjustedBeginImplFrameDeadline(begin_retro_frame_args_.front(),
271 draw_duration_estimate)) {
272 begin_retro_frame_args_.pop_front();
275 if (begin_retro_frame_args_.empty()) {
276 TRACE_EVENT_INSTANT0(
277 "cc", "Scheduler::BeginRetroFrames expired", TRACE_EVENT_SCOPE_THREAD);
278 } else {
279 BeginImplFrame(begin_retro_frame_args_.front());
280 begin_retro_frame_args_.pop_front();
283 begin_retro_frame_posted_ = false;
286 // There could be a race between the posted BeginRetroFrame and a new
287 // BeginFrame arriving via the normal mechanism. Scheduler::BeginFrame
288 // will check if there is a pending BeginRetroFrame to ensure we handle
289 // BeginFrames in FIFO order.
290 void Scheduler::PostBeginRetroFrameIfNeeded() {
291 if (begin_retro_frame_args_.empty() || begin_retro_frame_posted_)
292 return;
294 // begin_retro_frame_args_ should always be empty for the
295 // synchronous compositor.
296 DCHECK(!settings_.using_synchronous_renderer_compositor);
298 if (state_machine_.begin_impl_frame_state() !=
299 SchedulerStateMachine::BEGIN_IMPL_FRAME_STATE_IDLE)
300 return;
302 begin_retro_frame_posted_ = true;
303 impl_task_runner_->PostTask(FROM_HERE, begin_retro_frame_closure_);
306 // BeginImplFrame starts a compositor frame that will wait up until a deadline
307 // for a BeginMainFrame+activation to complete before it times out and draws
308 // any asynchronous animation and scroll/pinch updates.
309 void Scheduler::BeginImplFrame(const BeginFrameArgs& args) {
310 TRACE_EVENT0("cc", "Scheduler::BeginImplFrame");
311 DCHECK(state_machine_.begin_impl_frame_state() ==
312 SchedulerStateMachine::BEGIN_IMPL_FRAME_STATE_IDLE);
313 DCHECK(state_machine_.HasInitializedOutputSurface());
315 advance_commit_state_task_.Cancel();
317 base::TimeDelta draw_duration_estimate = client_->DrawDurationEstimate();
318 begin_impl_frame_args_ = args;
319 begin_impl_frame_args_.deadline -= draw_duration_estimate;
321 if (!state_machine_.smoothness_takes_priority() &&
322 state_machine_.MainThreadIsInHighLatencyMode() &&
323 CanCommitAndActivateBeforeDeadline()) {
324 state_machine_.SetSkipNextBeginMainFrameToReduceLatency();
327 client_->WillBeginImplFrame(begin_impl_frame_args_);
328 state_machine_.OnBeginImplFrame(begin_impl_frame_args_);
329 devtools_instrumentation::DidBeginFrame(layer_tree_host_id_);
331 ProcessScheduledActions();
333 if (!state_machine_.HasInitializedOutputSurface())
334 return;
336 state_machine_.OnBeginImplFrameDeadlinePending();
337 ScheduleBeginImplFrameDeadline(
338 AdjustedBeginImplFrameDeadline(args, draw_duration_estimate));
341 base::TimeTicks Scheduler::AdjustedBeginImplFrameDeadline(
342 const BeginFrameArgs& args,
343 base::TimeDelta draw_duration_estimate) const {
344 if (settings_.using_synchronous_renderer_compositor) {
345 // The synchronous compositor needs to draw right away.
346 return base::TimeTicks();
347 } else if (state_machine_.ShouldTriggerBeginImplFrameDeadlineEarly()) {
348 // We are ready to draw a new active tree immediately.
349 return base::TimeTicks();
350 } else if (state_machine_.needs_redraw()) {
351 // We have an animation or fast input path on the impl thread that wants
352 // to draw, so don't wait too long for a new active tree.
353 return args.deadline - draw_duration_estimate;
354 } else {
355 // The impl thread doesn't have anything it wants to draw and we are just
356 // waiting for a new active tree, so post the deadline for the next
357 // expected BeginImplFrame start. This allows us to draw immediately when
358 // there is a new active tree, instead of waiting for the next
359 // BeginImplFrame.
360 // TODO(brianderson): Handle long deadlines (that are past the next frame's
361 // frame time) properly instead of using this hack.
362 return args.frame_time + args.interval;
366 void Scheduler::ScheduleBeginImplFrameDeadline(base::TimeTicks deadline) {
367 if (settings_.using_synchronous_renderer_compositor) {
368 // The synchronous renderer compositor has to make its GL calls
369 // within this call.
370 // TODO(brianderson): Have the OutputSurface initiate the deadline tasks
371 // so the sychronous renderer compositor can take advantage of splitting
372 // up the BeginImplFrame and deadline as well.
373 OnBeginImplFrameDeadline();
374 return;
376 begin_impl_frame_deadline_task_.Cancel();
377 begin_impl_frame_deadline_task_.Reset(begin_impl_frame_deadline_closure_);
379 base::TimeDelta delta = deadline - gfx::FrameTime::Now();
380 if (delta <= base::TimeDelta())
381 delta = base::TimeDelta();
382 impl_task_runner_->PostDelayedTask(
383 FROM_HERE, begin_impl_frame_deadline_task_.callback(), delta);
386 void Scheduler::OnBeginImplFrameDeadline() {
387 TRACE_EVENT0("cc", "Scheduler::OnBeginImplFrameDeadline");
388 begin_impl_frame_deadline_task_.Cancel();
390 // We split the deadline actions up into two phases so the state machine
391 // has a chance to trigger actions that should occur durring and after
392 // the deadline separately. For example:
393 // * Sending the BeginMainFrame will not occur after the deadline in
394 // order to wait for more user-input before starting the next commit.
395 // * Creating a new OuputSurface will not occur during the deadline in
396 // order to allow the state machine to "settle" first.
397 state_machine_.OnBeginImplFrameDeadline();
398 ProcessScheduledActions();
399 state_machine_.OnBeginImplFrameIdle();
400 ProcessScheduledActions();
402 client_->DidBeginImplFrameDeadline();
405 void Scheduler::PollForAnticipatedDrawTriggers() {
406 TRACE_EVENT0("cc", "Scheduler::PollForAnticipatedDrawTriggers");
407 poll_for_draw_triggers_task_.Cancel();
408 state_machine_.DidEnterPollForAnticipatedDrawTriggers();
409 ProcessScheduledActions();
410 state_machine_.DidLeavePollForAnticipatedDrawTriggers();
413 void Scheduler::PollToAdvanceCommitState() {
414 TRACE_EVENT0("cc", "Scheduler::PollToAdvanceCommitState");
415 advance_commit_state_task_.Cancel();
416 ProcessScheduledActions();
419 bool Scheduler::IsBeginMainFrameSent() const {
420 return state_machine_.commit_state() ==
421 SchedulerStateMachine::COMMIT_STATE_BEGIN_MAIN_FRAME_SENT;
424 void Scheduler::DrawAndSwapIfPossible() {
425 DrawSwapReadbackResult result =
426 client_->ScheduledActionDrawAndSwapIfPossible();
427 state_machine_.DidDrawIfPossibleCompleted(result.draw_result);
430 void Scheduler::DrawAndSwapForced() {
431 client_->ScheduledActionDrawAndSwapForced();
434 void Scheduler::DrawAndReadback() {
435 DrawSwapReadbackResult result = client_->ScheduledActionDrawAndReadback();
436 DCHECK(!result.did_swap);
439 void Scheduler::ProcessScheduledActions() {
440 // We do not allow ProcessScheduledActions to be recursive.
441 // The top-level call will iteratively execute the next action for us anyway.
442 if (inside_process_scheduled_actions_)
443 return;
445 base::AutoReset<bool> mark_inside(&inside_process_scheduled_actions_, true);
447 SchedulerStateMachine::Action action;
448 do {
449 state_machine_.CheckInvariants();
450 action = state_machine_.NextAction();
451 TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("cc.debug.scheduler"),
452 "SchedulerStateMachine",
453 "state",
454 TracedValue::FromValue(StateAsValue().release()));
455 state_machine_.UpdateState(action);
456 base::AutoReset<SchedulerStateMachine::Action>
457 mark_inside_action(&inside_action_, action);
458 switch (action) {
459 case SchedulerStateMachine::ACTION_NONE:
460 break;
461 case SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME:
462 client_->ScheduledActionSendBeginMainFrame();
463 break;
464 case SchedulerStateMachine::ACTION_COMMIT:
465 client_->ScheduledActionCommit();
466 break;
467 case SchedulerStateMachine::ACTION_UPDATE_VISIBLE_TILES:
468 client_->ScheduledActionUpdateVisibleTiles();
469 break;
470 case SchedulerStateMachine::ACTION_ACTIVATE_PENDING_TREE:
471 ActivatePendingTree();
472 break;
473 case SchedulerStateMachine::ACTION_DRAW_AND_SWAP_IF_POSSIBLE:
474 DrawAndSwapIfPossible();
475 break;
476 case SchedulerStateMachine::ACTION_DRAW_AND_SWAP_FORCED:
477 DrawAndSwapForced();
478 break;
479 case SchedulerStateMachine::ACTION_DRAW_AND_SWAP_ABORT:
480 // No action is actually performed, but this allows the state machine to
481 // advance out of its waiting to draw state without actually drawing.
482 break;
483 case SchedulerStateMachine::ACTION_DRAW_AND_READBACK:
484 DrawAndReadback();
485 break;
486 case SchedulerStateMachine::ACTION_BEGIN_OUTPUT_SURFACE_CREATION:
487 client_->ScheduledActionBeginOutputSurfaceCreation();
488 break;
489 case SchedulerStateMachine::ACTION_MANAGE_TILES:
490 client_->ScheduledActionManageTiles();
491 break;
493 } while (action != SchedulerStateMachine::ACTION_NONE);
495 SetupNextBeginFrameIfNeeded();
496 client_->DidAnticipatedDrawTimeChange(AnticipatedDrawTime());
498 if (state_machine_.ShouldTriggerBeginImplFrameDeadlineEarly()) {
499 DCHECK(!settings_.using_synchronous_renderer_compositor);
500 ScheduleBeginImplFrameDeadline(base::TimeTicks());
504 bool Scheduler::WillDrawIfNeeded() const {
505 return !state_machine_.PendingDrawsShouldBeAborted();
508 scoped_ptr<base::Value> Scheduler::StateAsValue() const {
509 scoped_ptr<base::DictionaryValue> state(new base::DictionaryValue);
510 state->Set("state_machine", state_machine_.AsValue().release());
512 scoped_ptr<base::DictionaryValue> scheduler_state(new base::DictionaryValue);
513 scheduler_state->SetDouble(
514 "time_until_anticipated_draw_time_ms",
515 (AnticipatedDrawTime() - base::TimeTicks::Now()).InMillisecondsF());
516 scheduler_state->SetBoolean("last_set_needs_begin_frame_",
517 last_set_needs_begin_frame_);
518 scheduler_state->SetBoolean("begin_impl_frame_deadline_task_",
519 !begin_impl_frame_deadline_task_.IsCancelled());
520 scheduler_state->SetBoolean("poll_for_draw_triggers_task_",
521 !poll_for_draw_triggers_task_.IsCancelled());
522 scheduler_state->SetBoolean("advance_commit_state_task_",
523 !advance_commit_state_task_.IsCancelled());
524 state->Set("scheduler_state", scheduler_state.release());
526 scoped_ptr<base::DictionaryValue> client_state(new base::DictionaryValue);
527 client_state->SetDouble("draw_duration_estimate_ms",
528 client_->DrawDurationEstimate().InMillisecondsF());
529 client_state->SetDouble(
530 "begin_main_frame_to_commit_duration_estimate_ms",
531 client_->BeginMainFrameToCommitDurationEstimate().InMillisecondsF());
532 client_state->SetDouble(
533 "commit_to_activate_duration_estimate_ms",
534 client_->CommitToActivateDurationEstimate().InMillisecondsF());
535 state->Set("client_state", client_state.release());
536 return state.PassAs<base::Value>();
539 bool Scheduler::CanCommitAndActivateBeforeDeadline() const {
540 // Check if the main thread computation and commit can be finished before the
541 // impl thread's deadline.
542 base::TimeTicks estimated_draw_time =
543 begin_impl_frame_args_.frame_time +
544 client_->BeginMainFrameToCommitDurationEstimate() +
545 client_->CommitToActivateDurationEstimate();
547 TRACE_EVENT2(
548 TRACE_DISABLED_BY_DEFAULT("cc.debug.scheduler"),
549 "CanCommitAndActivateBeforeDeadline",
550 "time_left_after_drawing_ms",
551 (begin_impl_frame_args_.deadline - estimated_draw_time).InMillisecondsF(),
552 "state",
553 TracedValue::FromValue(StateAsValue().release()));
555 return estimated_draw_time < begin_impl_frame_args_.deadline;
558 bool Scheduler::IsBeginMainFrameSentOrStarted() const {
559 return (state_machine_.commit_state() ==
560 SchedulerStateMachine::COMMIT_STATE_BEGIN_MAIN_FRAME_SENT ||
561 state_machine_.commit_state() ==
562 SchedulerStateMachine::COMMIT_STATE_BEGIN_MAIN_FRAME_STARTED);
565 } // namespace cc