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 #include "cc/scheduler/scheduler.h" | 4 #include "cc/scheduler/scheduler.h" |
5 | 5 |
6 #include <string> | 6 #include <string> |
7 #include <vector> | 7 #include <vector> |
8 | 8 |
9 #include "base/logging.h" | 9 #include "base/logging.h" |
10 #include "base/memory/scoped_vector.h" | 10 #include "base/memory/scoped_vector.h" |
(...skipping 22 matching lines...) Expand all Loading... |
33 namespace { | 33 namespace { |
34 | 34 |
35 class FakeSchedulerClient; | 35 class FakeSchedulerClient; |
36 | 36 |
37 void InitializeOutputSurfaceAndFirstCommit(Scheduler* scheduler, | 37 void InitializeOutputSurfaceAndFirstCommit(Scheduler* scheduler, |
38 FakeSchedulerClient* client); | 38 FakeSchedulerClient* client); |
39 | 39 |
40 class FakeSchedulerClient : public SchedulerClient { | 40 class FakeSchedulerClient : public SchedulerClient { |
41 public: | 41 public: |
42 FakeSchedulerClient() | 42 FakeSchedulerClient() |
43 : needs_begin_impl_frame_(false) { | 43 : needs_begin_impl_frame_(false), automatic_swap_ack_(true) { |
44 Reset(); | 44 Reset(); |
45 } | 45 } |
46 | 46 |
47 void Reset() { | 47 void Reset() { |
48 actions_.clear(); | 48 actions_.clear(); |
49 states_.clear(); | 49 states_.clear(); |
50 draw_will_happen_ = true; | 50 draw_will_happen_ = true; |
51 swap_will_happen_if_draw_happens_ = true; | 51 swap_will_happen_if_draw_happens_ = true; |
52 num_draws_ = 0; | 52 num_draws_ = 0; |
53 log_anticipated_draw_time_change_ = false; | 53 log_anticipated_draw_time_change_ = false; |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
85 bool HasAction(const char* action) const { | 85 bool HasAction(const char* action) const { |
86 return ActionIndex(action) >= 0; | 86 return ActionIndex(action) >= 0; |
87 } | 87 } |
88 | 88 |
89 void SetDrawWillHappen(bool draw_will_happen) { | 89 void SetDrawWillHappen(bool draw_will_happen) { |
90 draw_will_happen_ = draw_will_happen; | 90 draw_will_happen_ = draw_will_happen; |
91 } | 91 } |
92 void SetSwapWillHappenIfDrawHappens(bool swap_will_happen_if_draw_happens) { | 92 void SetSwapWillHappenIfDrawHappens(bool swap_will_happen_if_draw_happens) { |
93 swap_will_happen_if_draw_happens_ = swap_will_happen_if_draw_happens; | 93 swap_will_happen_if_draw_happens_ = swap_will_happen_if_draw_happens; |
94 } | 94 } |
| 95 void SetAutomaticSwapAck(bool automatic_swap_ack) { |
| 96 automatic_swap_ack_ = automatic_swap_ack; |
| 97 } |
95 | 98 |
96 // SchedulerClient implementation. | 99 // SchedulerClient implementation. |
97 virtual void SetNeedsBeginFrame(bool enable) OVERRIDE { | 100 virtual void SetNeedsBeginFrame(bool enable) OVERRIDE { |
98 actions_.push_back("SetNeedsBeginFrame"); | 101 actions_.push_back("SetNeedsBeginFrame"); |
99 states_.push_back(scheduler_->StateAsValue().release()); | 102 states_.push_back(scheduler_->StateAsValue().release()); |
100 needs_begin_impl_frame_ = enable; | 103 needs_begin_impl_frame_ = enable; |
101 } | 104 } |
102 virtual void WillBeginImplFrame(const BeginFrameArgs& args) OVERRIDE { | 105 virtual void WillBeginImplFrame(const BeginFrameArgs& args) OVERRIDE { |
103 actions_.push_back("WillBeginImplFrame"); | 106 actions_.push_back("WillBeginImplFrame"); |
104 states_.push_back(scheduler_->StateAsValue().release()); | 107 states_.push_back(scheduler_->StateAsValue().release()); |
105 } | 108 } |
106 virtual void ScheduledActionSendBeginMainFrame() OVERRIDE { | 109 virtual void ScheduledActionSendBeginMainFrame() OVERRIDE { |
107 actions_.push_back("ScheduledActionSendBeginMainFrame"); | 110 actions_.push_back("ScheduledActionSendBeginMainFrame"); |
108 states_.push_back(scheduler_->StateAsValue().release()); | 111 states_.push_back(scheduler_->StateAsValue().release()); |
109 } | 112 } |
110 virtual DrawSwapReadbackResult ScheduledActionDrawAndSwapIfPossible() | 113 virtual DrawSwapReadbackResult ScheduledActionDrawAndSwapIfPossible() |
111 OVERRIDE { | 114 OVERRIDE { |
112 actions_.push_back("ScheduledActionDrawAndSwapIfPossible"); | 115 actions_.push_back("ScheduledActionDrawAndSwapIfPossible"); |
113 states_.push_back(scheduler_->StateAsValue().release()); | 116 states_.push_back(scheduler_->StateAsValue().release()); |
114 num_draws_++; | 117 num_draws_++; |
115 bool did_readback = false; | 118 bool did_readback = false; |
116 DrawSwapReadbackResult::DrawResult result = | 119 DrawSwapReadbackResult::DrawResult result = |
117 draw_will_happen_ | 120 draw_will_happen_ |
118 ? DrawSwapReadbackResult::DRAW_SUCCESS | 121 ? DrawSwapReadbackResult::DRAW_SUCCESS |
119 : DrawSwapReadbackResult::DRAW_ABORTED_CHECKERBOARD_ANIMATIONS; | 122 : DrawSwapReadbackResult::DRAW_ABORTED_CHECKERBOARD_ANIMATIONS; |
| 123 bool swap_will_happen = |
| 124 draw_will_happen_ && swap_will_happen_if_draw_happens_; |
| 125 if (swap_will_happen) { |
| 126 scheduler_->DidSwapBuffers(); |
| 127 if (automatic_swap_ack_) |
| 128 scheduler_->OnSwapBuffersComplete(); |
| 129 } |
120 return DrawSwapReadbackResult( | 130 return DrawSwapReadbackResult( |
121 result, | 131 result, |
122 draw_will_happen_ && swap_will_happen_if_draw_happens_, | 132 draw_will_happen_ && swap_will_happen_if_draw_happens_, |
123 did_readback); | 133 did_readback); |
124 } | 134 } |
125 virtual DrawSwapReadbackResult ScheduledActionDrawAndSwapForced() OVERRIDE { | 135 virtual DrawSwapReadbackResult ScheduledActionDrawAndSwapForced() OVERRIDE { |
126 actions_.push_back("ScheduledActionDrawAndSwapForced"); | 136 actions_.push_back("ScheduledActionDrawAndSwapForced"); |
127 states_.push_back(scheduler_->StateAsValue().release()); | 137 states_.push_back(scheduler_->StateAsValue().release()); |
128 bool did_swap = swap_will_happen_if_draw_happens_; | 138 bool did_swap = swap_will_happen_if_draw_happens_; |
129 bool did_readback = false; | 139 bool did_readback = false; |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
175 virtual base::TimeDelta CommitToActivateDurationEstimate() OVERRIDE { | 185 virtual base::TimeDelta CommitToActivateDurationEstimate() OVERRIDE { |
176 return base::TimeDelta(); | 186 return base::TimeDelta(); |
177 } | 187 } |
178 | 188 |
179 virtual void DidBeginImplFrameDeadline() OVERRIDE {} | 189 virtual void DidBeginImplFrameDeadline() OVERRIDE {} |
180 | 190 |
181 protected: | 191 protected: |
182 bool needs_begin_impl_frame_; | 192 bool needs_begin_impl_frame_; |
183 bool draw_will_happen_; | 193 bool draw_will_happen_; |
184 bool swap_will_happen_if_draw_happens_; | 194 bool swap_will_happen_if_draw_happens_; |
| 195 bool automatic_swap_ack_; |
185 int num_draws_; | 196 int num_draws_; |
186 bool log_anticipated_draw_time_change_; | 197 bool log_anticipated_draw_time_change_; |
187 base::TimeTicks posted_begin_impl_frame_deadline_; | 198 base::TimeTicks posted_begin_impl_frame_deadline_; |
188 std::vector<const char*> actions_; | 199 std::vector<const char*> actions_; |
189 ScopedVector<base::Value> states_; | 200 ScopedVector<base::Value> states_; |
190 scoped_ptr<Scheduler> scheduler_; | 201 scoped_ptr<Scheduler> scheduler_; |
191 scoped_refptr<base::TestSimpleTaskRunner> task_runner_; | 202 scoped_refptr<base::TestSimpleTaskRunner> task_runner_; |
192 }; | 203 }; |
193 | 204 |
194 void InitializeOutputSurfaceAndFirstCommit(Scheduler* scheduler, | 205 void InitializeOutputSurfaceAndFirstCommit(Scheduler* scheduler, |
(...skipping 1106 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1301 scheduler->SetNeedsRedraw(); | 1312 scheduler->SetNeedsRedraw(); |
1302 | 1313 |
1303 BeginFrameArgs frame_args = BeginFrameArgs::CreateForTesting(); | 1314 BeginFrameArgs frame_args = BeginFrameArgs::CreateForTesting(); |
1304 frame_args.interval = base::TimeDelta::FromMilliseconds(1000); | 1315 frame_args.interval = base::TimeDelta::FromMilliseconds(1000); |
1305 scheduler->BeginFrame(frame_args); | 1316 scheduler->BeginFrame(frame_args); |
1306 | 1317 |
1307 EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending()); | 1318 EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending()); |
1308 client.task_runner().RunPendingTasks(); // Run posted deadline. | 1319 client.task_runner().RunPendingTasks(); // Run posted deadline. |
1309 EXPECT_FALSE(scheduler->BeginImplFrameDeadlinePending()); | 1320 EXPECT_FALSE(scheduler->BeginImplFrameDeadlinePending()); |
1310 | 1321 |
| 1322 scheduler->DidSwapBuffers(); |
| 1323 scheduler->OnSwapBuffersComplete(); |
| 1324 |
1311 // At this point, we've drawn a frame. Start another commit, but hold off on | 1325 // At this point, we've drawn a frame. Start another commit, but hold off on |
1312 // the NotifyReadyToCommit for now. | 1326 // the NotifyReadyToCommit for now. |
1313 EXPECT_FALSE(scheduler->CommitPending()); | 1327 EXPECT_FALSE(scheduler->CommitPending()); |
1314 scheduler->SetNeedsCommit(); | 1328 scheduler->SetNeedsCommit(); |
1315 scheduler->BeginFrame(frame_args); | 1329 scheduler->BeginFrame(frame_args); |
1316 EXPECT_TRUE(scheduler->CommitPending()); | 1330 EXPECT_TRUE(scheduler->CommitPending()); |
1317 | 1331 |
| 1332 // Draw and swap the frame, but don't ack the swap to simulate the Browser |
| 1333 // blocking on the renderer. |
| 1334 EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending()); |
| 1335 client.task_runner().RunPendingTasks(); // Run posted deadline. |
| 1336 EXPECT_FALSE(scheduler->BeginImplFrameDeadlinePending()); |
| 1337 scheduler->DidSwapBuffers(); |
| 1338 |
1318 // Spin the event loop a few times and make sure we get more | 1339 // Spin the event loop a few times and make sure we get more |
1319 // DidAnticipateDrawTimeChange calls every time. | 1340 // DidAnticipateDrawTimeChange calls every time. |
1320 int actions_so_far = client.num_actions_(); | 1341 int actions_so_far = client.num_actions_(); |
1321 | 1342 |
1322 // Does three iterations to make sure that the timer is properly repeating. | 1343 // Does three iterations to make sure that the timer is properly repeating. |
1323 for (int i = 0; i < 3; ++i) { | 1344 for (int i = 0; i < 3; ++i) { |
1324 EXPECT_EQ((frame_args.interval * 2).InMicroseconds(), | 1345 EXPECT_EQ((frame_args.interval * 2).InMicroseconds(), |
1325 client.task_runner().NextPendingTaskDelay().InMicroseconds()) | 1346 client.task_runner().NextPendingTaskDelay().InMicroseconds()) |
1326 << *scheduler->StateAsValue(); | 1347 << *scheduler->StateAsValue(); |
1327 client.task_runner().RunPendingTasks(); | 1348 client.task_runner().RunPendingTasks(); |
(...skipping 10 matching lines...) Expand all Loading... |
1338 client.task_runner().NextPendingTaskDelay().InMicroseconds()) | 1359 client.task_runner().NextPendingTaskDelay().InMicroseconds()) |
1339 << *scheduler->StateAsValue(); | 1360 << *scheduler->StateAsValue(); |
1340 client.task_runner().RunPendingTasks(); | 1361 client.task_runner().RunPendingTasks(); |
1341 EXPECT_GT(client.num_actions_(), actions_so_far); | 1362 EXPECT_GT(client.num_actions_(), actions_so_far); |
1342 EXPECT_STREQ(client.Action(client.num_actions_() - 1), | 1363 EXPECT_STREQ(client.Action(client.num_actions_() - 1), |
1343 "DidAnticipatedDrawTimeChange"); | 1364 "DidAnticipatedDrawTimeChange"); |
1344 actions_so_far = client.num_actions_(); | 1365 actions_so_far = client.num_actions_(); |
1345 } | 1366 } |
1346 } | 1367 } |
1347 | 1368 |
1348 TEST(SchedulerTest, BeginRetroFrame) { | 1369 TEST(SchedulerTest, BeginRetroFrameBasic) { |
1349 FakeSchedulerClient client; | 1370 FakeSchedulerClient client; |
1350 SchedulerSettings scheduler_settings; | 1371 SchedulerSettings scheduler_settings; |
1351 Scheduler* scheduler = client.CreateScheduler(scheduler_settings); | 1372 Scheduler* scheduler = client.CreateScheduler(scheduler_settings); |
1352 scheduler->SetCanStart(); | 1373 scheduler->SetCanStart(); |
1353 scheduler->SetVisible(true); | 1374 scheduler->SetVisible(true); |
1354 scheduler->SetCanDraw(true); | 1375 scheduler->SetCanDraw(true); |
1355 InitializeOutputSurfaceAndFirstCommit(scheduler, &client); | 1376 InitializeOutputSurfaceAndFirstCommit(scheduler, &client); |
1356 | 1377 |
1357 // SetNeedsCommit should begin the frame on the next BeginImplFrame. | 1378 // SetNeedsCommit should begin the frame on the next BeginImplFrame. |
1358 client.Reset(); | 1379 client.Reset(); |
1359 scheduler->SetNeedsCommit(); | 1380 scheduler->SetNeedsCommit(); |
1360 EXPECT_TRUE(client.needs_begin_impl_frame()); | 1381 EXPECT_TRUE(client.needs_begin_impl_frame()); |
1361 EXPECT_SINGLE_ACTION("SetNeedsBeginFrame", client); | 1382 EXPECT_SINGLE_ACTION("SetNeedsBeginFrame", client); |
1362 client.Reset(); | 1383 client.Reset(); |
1363 | 1384 |
1364 // Create a BeginFrame with a long deadline to avoid race conditions. | 1385 // Create a BeginFrame with a long deadline to avoid race conditions. |
1365 // This is the first BeginFrame, which will be handled immediately. | 1386 // This is the first BeginFrame, which will be handled immediately. |
1366 BeginFrameArgs args = BeginFrameArgs::CreateForTesting(); | 1387 BeginFrameArgs args = BeginFrameArgs::CreateForTesting(); |
1367 args.deadline += base::TimeDelta::FromHours(1); | 1388 args.deadline += base::TimeDelta::FromHours(1); |
1368 scheduler->BeginFrame(args); | 1389 scheduler->BeginFrame(args); |
1369 | |
1370 EXPECT_ACTION("WillBeginImplFrame", client, 0, 2); | 1390 EXPECT_ACTION("WillBeginImplFrame", client, 0, 2); |
1371 EXPECT_ACTION("ScheduledActionSendBeginMainFrame", client, 1, 2); | 1391 EXPECT_ACTION("ScheduledActionSendBeginMainFrame", client, 1, 2); |
1372 EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending()); | 1392 EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending()); |
1373 EXPECT_TRUE(client.needs_begin_impl_frame()); | 1393 EXPECT_TRUE(client.needs_begin_impl_frame()); |
1374 client.Reset(); | 1394 client.Reset(); |
1375 | 1395 |
1376 // Queue BeginFrames while we are still handling the previous BeginFrame. | 1396 // Queue BeginFrames while we are still handling the previous BeginFrame. |
1377 args.frame_time += base::TimeDelta::FromSeconds(1); | 1397 args.frame_time += base::TimeDelta::FromSeconds(1); |
1378 scheduler->BeginFrame(args); | 1398 scheduler->BeginFrame(args); |
1379 args.frame_time += base::TimeDelta::FromSeconds(1); | 1399 args.frame_time += base::TimeDelta::FromSeconds(1); |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1415 EXPECT_SINGLE_ACTION("WillBeginImplFrame", client); | 1435 EXPECT_SINGLE_ACTION("WillBeginImplFrame", client); |
1416 EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending()); | 1436 EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending()); |
1417 client.Reset(); | 1437 client.Reset(); |
1418 | 1438 |
1419 client.task_runner().RunPendingTasks(); // Run posted deadline. | 1439 client.task_runner().RunPendingTasks(); // Run posted deadline. |
1420 EXPECT_SINGLE_ACTION("SetNeedsBeginFrame", client); | 1440 EXPECT_SINGLE_ACTION("SetNeedsBeginFrame", client); |
1421 EXPECT_FALSE(client.needs_begin_impl_frame()); | 1441 EXPECT_FALSE(client.needs_begin_impl_frame()); |
1422 client.Reset(); | 1442 client.Reset(); |
1423 } | 1443 } |
1424 | 1444 |
| 1445 TEST(SchedulerTest, BeginRetroFrame_SwapThrottled) { |
| 1446 FakeSchedulerClient client; |
| 1447 SchedulerSettings scheduler_settings; |
| 1448 Scheduler* scheduler = client.CreateScheduler(scheduler_settings); |
| 1449 scheduler->SetCanStart(); |
| 1450 scheduler->SetVisible(true); |
| 1451 scheduler->SetCanDraw(true); |
| 1452 InitializeOutputSurfaceAndFirstCommit(scheduler, &client); |
| 1453 |
| 1454 // To test swap ack throttling, this test disables automatic swap acks. |
| 1455 scheduler->SetMaxSwapsPending(1); |
| 1456 client.SetAutomaticSwapAck(false); |
| 1457 |
| 1458 // SetNeedsCommit should begin the frame on the next BeginImplFrame. |
| 1459 client.Reset(); |
| 1460 scheduler->SetNeedsCommit(); |
| 1461 EXPECT_TRUE(client.needs_begin_impl_frame()); |
| 1462 EXPECT_SINGLE_ACTION("SetNeedsBeginFrame", client); |
| 1463 client.Reset(); |
| 1464 |
| 1465 // Create a BeginFrame with a long deadline to avoid race conditions. |
| 1466 // This is the first BeginFrame, which will be handled immediately. |
| 1467 BeginFrameArgs args = BeginFrameArgs::CreateForTesting(); |
| 1468 args.deadline += base::TimeDelta::FromHours(1); |
| 1469 scheduler->BeginFrame(args); |
| 1470 EXPECT_ACTION("WillBeginImplFrame", client, 0, 2); |
| 1471 EXPECT_ACTION("ScheduledActionSendBeginMainFrame", client, 1, 2); |
| 1472 EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending()); |
| 1473 EXPECT_TRUE(client.needs_begin_impl_frame()); |
| 1474 client.Reset(); |
| 1475 |
| 1476 // Queue BeginFrame while we are still handling the previous BeginFrame. |
| 1477 EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending()); |
| 1478 args.frame_time += base::TimeDelta::FromSeconds(1); |
| 1479 scheduler->BeginFrame(args); |
| 1480 EXPECT_EQ(0, client.num_actions_()); |
| 1481 EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending()); |
| 1482 client.Reset(); |
| 1483 |
| 1484 // NotifyReadyToCommit should trigger the pending commit and draw. |
| 1485 scheduler->NotifyBeginMainFrameStarted(); |
| 1486 scheduler->NotifyReadyToCommit(); |
| 1487 EXPECT_SINGLE_ACTION("ScheduledActionCommit", client); |
| 1488 EXPECT_TRUE(client.needs_begin_impl_frame()); |
| 1489 client.Reset(); |
| 1490 |
| 1491 // Swapping will put us into a swap throttled state. |
| 1492 client.task_runner().RunPendingTasks(); // Run posted deadline. |
| 1493 EXPECT_ACTION("ScheduledActionDrawAndSwapIfPossible", client, 0, 2); |
| 1494 EXPECT_ACTION("SetNeedsBeginFrame", client, 1, 2); |
| 1495 EXPECT_FALSE(scheduler->BeginImplFrameDeadlinePending()); |
| 1496 EXPECT_TRUE(client.needs_begin_impl_frame()); |
| 1497 client.Reset(); |
| 1498 |
| 1499 // While swap throttled, BeginRetroFrames should trigger BeginImplFrames |
| 1500 // but not a BeginMainFrame or draw. |
| 1501 scheduler->SetNeedsCommit(); |
| 1502 client.task_runner().RunPendingTasks(); // Run posted BeginRetroFrame. |
| 1503 EXPECT_ACTION("WillBeginImplFrame", client, 0, 1); |
| 1504 EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending()); |
| 1505 EXPECT_TRUE(client.needs_begin_impl_frame()); |
| 1506 client.Reset(); |
| 1507 |
| 1508 // Queue BeginFrame while we are still handling the previous BeginFrame. |
| 1509 args.frame_time += base::TimeDelta::FromSeconds(1); |
| 1510 scheduler->BeginFrame(args); |
| 1511 EXPECT_EQ(0, client.num_actions_()); |
| 1512 EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending()); |
| 1513 EXPECT_TRUE(client.needs_begin_impl_frame()); |
| 1514 client.Reset(); |
| 1515 |
| 1516 // Take us out of a swap throttled state. |
| 1517 scheduler->OnSwapBuffersComplete(); |
| 1518 EXPECT_ACTION("ScheduledActionSendBeginMainFrame", client, 0, 1); |
| 1519 EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending()); |
| 1520 EXPECT_TRUE(client.needs_begin_impl_frame()); |
| 1521 client.Reset(); |
| 1522 |
| 1523 // BeginImplFrame deadline should draw. |
| 1524 scheduler->SetNeedsRedraw(); |
| 1525 client.task_runner().RunPendingTasks(); // Run posted deadline. |
| 1526 EXPECT_ACTION("ScheduledActionDrawAndSwapIfPossible", client, 0, 2); |
| 1527 EXPECT_ACTION("SetNeedsBeginFrame", client, 1, 2); |
| 1528 EXPECT_FALSE(scheduler->BeginImplFrameDeadlinePending()); |
| 1529 EXPECT_TRUE(client.needs_begin_impl_frame()); |
| 1530 client.Reset(); |
| 1531 } |
| 1532 |
1425 } // namespace | 1533 } // namespace |
1426 } // namespace cc | 1534 } // namespace cc |
OLD | NEW |