@samitouri / QOS-React / commits / 43c4e5f348

Add method for forcing a lower framerate

Nathan Schloss committed Jan 31, 2019 at 12:11 UTC 43c4e5f348eb5704464986e2dc3221e347041b82
18 files changed +158 -141
fixtures/scheduler/index.html
+103 -35
@@ -105,6 +105,20 @@
105 <div><b>Actual:</b></div>
106 <div id="test-8"></div>
107 </li>
108 + <li>
109 + <p>Can force a specific framerate</p>
110 + <p><b>IMPORTANT:</b> This test may be flaky if other tests have been run in this js instance. To get a clean test refresh the page before running test 9</p>
111 + <button onClick="runTestNine()">Run Test 9</button>
112 + <div><b>Expected:</b></div>
113 + <div id="test-9-expected">
114 + </div>
115 + <div> -------------------------------------------------</div>
116 + <div> If you see the same above and below it's correct.
117 + <div> -------------------------------------------------</div>
118 + <div><b>Actual:</b></div>
119 + <div id="test-9"></div>
120 + </div>
121 + </li>
122 </ol>
123 <script src="../../build/node_modules/react/umd/react.development.js"></script>
124 <script src="../../build/node_modules/scheduler/umd/scheduler.development.js"></script>
@@ -117,6 +131,9 @@ const {
131 unstable_getFirstCallbackNode: getFirstCallbackNode,
132 unstable_pauseExecution: pauseExecution,
133 unstable_continueExecution: continueExecution,
134 + unstable_forceFrameRate: forceFrameRate,
135 + unstable_shouldYield: shouldYield,
136 + unstable_NormalPriority: NormalPriority,
137 } = Scheduler;
138 function displayTestResult(testNumber) {
139 const expectationNode = document.getElementById('test-' + testNumber + '-expected');
@@ -188,7 +205,7 @@ const expectedResults = [
205 [
206 'scheduled Cb1',
207 'frame 1 started',
191 - 'cb1 called with argument of {"didTimeout":false}',
208 + 'cb1 called with argument of false',
209 'frame 2 started',
210 'frame 3 started... we stop counting now.',
211 ],
@@ -197,8 +214,8 @@ const expectedResults = [
214 'scheduled CbA',
215 'scheduled CbB',
216 'frame 1 started',
200 - 'cbA called with argument of {"didTimeout":false}',
201 - 'cbB called with argument of {"didTimeout":false}',
217 + 'cbA called with argument of false',
218 + 'cbB called with argument of false',
219 'frame 2 started',
220 'frame 3 started... we stop counting now.',
221 ],
@@ -208,9 +225,9 @@ const expectedResults = [
225 'scheduled CbB',
226 'frame 1 started',
227 'scheduled CbA again',
211 - 'cbA0 called with argument of {"didTimeout":false}',
212 - 'cbB called with argument of {"didTimeout":false}',
213 - 'cbA1 called with argument of {"didTimeout":false}',
228 + 'cbA0 called with argument of false',
229 + 'cbB called with argument of false',
230 + 'cbA1 called with argument of false',
231 'frame 2 started',
232 'frame 3 started... we stop counting now.',
233 ],
@@ -222,11 +239,11 @@ const expectedResults = [
239 'scheduled cbD',
240 'frame 1 started',
241 'cbC called with argument of {"didTimeout":true}',
225 - 'cbA called with argument of {"didTimeout":false}',
242 + 'cbA called with argument of false',
243 'cbA running and taking some time',
244 'frame 2 started',
228 - 'cbB called with argument of {"didTimeout":false}',
229 - 'cbD called with argument of {"didTimeout":false}',
245 + 'cbB called with argument of false',
246 + 'cbD called with argument of false',
247 'frame 3 started... we stop counting now.',
248 ],
249 // test 5
@@ -243,6 +260,13 @@ const expectedResults = [
260 'Finishing...',
261 'Done!',
262 ],
263 + // test 9
264 + [
265 + 'Forcing new frame times...',
266 + 'Using new frame time!',
267 + 'Using new frame time!',
268 + 'Finished!',
269 + ],
270 ];
271 function runTestOne() {
272 // Test 1
@@ -253,7 +277,7 @@ function runTestOne() {
277 const cb1 = (x) => {
278 updateTestResult(1, 'cb1 called with argument of ' + JSON.stringify(x));
279 }
256 - scheduleCallback(cb1);
280 + scheduleCallback(NormalPriority, cb1);
281 updateTestResult(1, 'scheduled Cb1');
282 logWhenFramesStart(1, () => {
283 displayTestResult(1);
@@ -271,9 +295,9 @@ function runTestTwo() {
295 const cbB = (x) => {
296 updateTestResult(2, 'cbB called with argument of ' + JSON.stringify(x));
297 }
274 - scheduleCallback(cbA);
298 + scheduleCallback(NormalPriority, cbA);
299 updateTestResult(2, 'scheduled CbA');
276 - scheduleCallback(cbB);
300 + scheduleCallback(NormalPriority, cbB);
301 updateTestResult(2, 'scheduled CbB');
302 logWhenFramesStart(2, () => {
303 displayTestResult(2);
@@ -288,7 +312,7 @@ function runTestThree() {
312 let callbackAIterations = 0;
313 const cbA = (x) => {
314 if (callbackAIterations < 1) {
291 - scheduleCallback(cbA);
315 + scheduleCallback(NormalPriority, cbA);
316 updateTestResult(3, 'scheduled CbA again');
317 }
318 updateTestResult(3, 'cbA' + callbackAIterations + ' called with argument of ' + JSON.stringify(x));
@@ -297,9 +321,9 @@ function runTestThree() {
321 const cbB = (x) => {
322 updateTestResult(3, 'cbB called with argument of ' + JSON.stringify(x));
323 }
300 - scheduleCallback(cbA);
324 + scheduleCallback(NormalPriority, cbA);
325 updateTestResult(3, 'scheduled CbA');
302 - scheduleCallback(cbB);
326 + scheduleCallback(NormalPriority, cbB);
327 updateTestResult(3, 'scheduled CbB');
328 logWhenFramesStart(3, () => {
329 displayTestResult(3);
@@ -333,13 +357,13 @@ function runTestFour() {
357 const cbD = (x) => {
358 updateTestResult(4, 'cbD called with argument of ' + JSON.stringify(x));
359 }
336 - scheduleCallback(cbA); // won't time out
360 + scheduleCallback(NormalPriority, cbA); // won't time out
361 updateTestResult(4, 'scheduled cbA');
338 - scheduleCallback(cbB, {timeout: 100}); // times out later
362 + scheduleCallback(NormalPriority, cbB, {timeout: 100}); // times out later
363 updateTestResult(4, 'scheduled cbB');
340 - scheduleCallback(cbC, {timeout: 1}); // will time out fast
364 + scheduleCallback(NormalPriority, cbC, {timeout: 1}); // will time out fast
365 updateTestResult(4, 'scheduled cbC');
342 - scheduleCallback(cbD); // won't time out
366 + scheduleCallback(NormalPriority, cbD); // won't time out
367 updateTestResult(4, 'scheduled cbD');
368
369 // should have run in order of C, A, B, D
@@ -418,15 +442,15 @@ function runTestFive() {
442 });
443 });
444 });
421 - scheduleCallback(cbA);
445 + scheduleCallback(NormalPriority, cbA);
446 console.log('scheduled cbA');
423 - scheduleCallback(cbB); // will throw error
447 + scheduleCallback(NormalPriority, cbB); // will throw error
448 console.log('scheduled cbB');
425 - scheduleCallback(cbC);
449 + scheduleCallback(NormalPriority, cbC);
450 console.log('scheduled cbC');
427 - scheduleCallback(cbD); // will throw error
451 + scheduleCallback(NormalPriority, cbD); // will throw error
452 console.log('scheduled cbD');
429 - scheduleCallback(cbE);
453 + scheduleCallback(NormalPriority, cbE);
454 console.log('scheduled cbE');
455 };
456 }
@@ -496,15 +520,15 @@ function runTestSix() {
520 });
521 });
522 });
499 - scheduleCallback(cbA);
523 + scheduleCallback(NormalPriority, cbA);
524 console.log('scheduled cbA');
501 - scheduleCallback(cbB); // will throw error
525 + scheduleCallback(NormalPriority, cbB); // will throw error
526 console.log('scheduled cbB');
503 - scheduleCallback(cbC, {timeout: 1});
527 + scheduleCallback(NormalPriority, cbC, {timeout: 1});
528 console.log('scheduled cbC');
505 - scheduleCallback(cbD, {timeout: 1}); // will throw error
529 + scheduleCallback(NormalPriority, cbD, {timeout: 1}); // will throw error
530 console.log('scheduled cbD');
507 - scheduleCallback(cbE, {timeout: 1});
531 + scheduleCallback(NormalPriority, cbE, {timeout: 1});
532 console.log('scheduled cbE');
533 };
534 }
@@ -520,9 +544,9 @@ function runTestSeven() {
544 counter++;
545 counterNode.innerHTML = counter;
546 waitForTimeToPass(100);
523 - scheduleCallback(incrementCounterAndScheduleNextCallback);
547 + scheduleCallback(NormalPriority, incrementCounterAndScheduleNextCallback);
548 }
525 - scheduleCallback(incrementCounterAndScheduleNextCallback);
549 + scheduleCallback(NormalPriority, incrementCounterAndScheduleNextCallback);
550 }
551
552 function runTestEight() {
@@ -542,18 +566,18 @@ function runTestEight() {
566 return count;
567 }
568
545 - scheduleCallback(() => {
569 + scheduleCallback(NormalPriority, () => {
570
571 // size should be 0
572 updateTestResult(8, `Queue size: ${countNodesInStack(getFirstCallbackNode())}.`);
573 updateTestResult(8, 'Pausing... press continue to resume.');
574 pauseExecution();
575
552 - scheduleCallback(function () {
576 + scheduleCallback(NormalPriority, function () {
577 updateTestResult(8, 'Finishing...');
578 displayTestResult(8);
579 })
556 - scheduleCallback(function () {
580 + scheduleCallback(NormalPriority, function () {
581 updateTestResult(8, 'Done!');
582 displayTestResult(8);
583 checkTestResult(8);
@@ -569,6 +593,50 @@ function continueTestEight() {
593 continueExecution();
594 }
595
596 +function runTestNine() {
597 + clearTestResult(9);
598 + // We have this to make sure that the thing that goes right after it can get a full frame
599 + var forceFrameFinish = () => {
600 + while (!shouldYield()) {
601 + waitForTimeToPass(1);
602 + }
603 + waitForTimeToPass(100);
604 + }
605 + scheduleCallback(NormalPriority, forceFrameFinish);
606 + scheduleCallback(NormalPriority, () => {
607 + var startTime = now();
608 + while (!shouldYield()) {}
609 + var initialFrameTime = now() - startTime;
610 + var newFrameTime = (initialFrameTime * 2) > 60 ? (initialFrameTime * 2) : 60;
611 + var newFrameRate = Math.floor(1000/newFrameTime);
612 + updateTestResult(9, `Forcing new frame times...`);
613 + displayTestResult(9);
614 + forceFrameRate(newFrameRate);
615 + var toSchedule = (again) => {
616 + var startTime = now();
617 + while (!shouldYield()) {}
618 + var frameTime = now() - startTime;
619 + if (frameTime >= (newFrameTime-8)) {
620 + updateTestResult(9, `Using new frame time!`);
621 + } else {
622 + updateTestResult(9, `Failed to use new frame time. (off by ${newFrameTime - frameTime}ms)`);
623 + }
624 + displayTestResult(9);
625 + if (again) {
626 + scheduleCallback(NormalPriority, forceFrameFinish);
627 + scheduleCallback(NormalPriority, () => {toSchedule(false);});
628 + } else {
629 + updateTestResult(9, `Finished!`);
630 + forceFrameRate(0);
631 + displayTestResult(9);
632 + checkTestResult(9);
633 + }
634 + }
635 + scheduleCallback(NormalPriority, forceFrameFinish);
636 + scheduleCallback(NormalPriority, () => {toSchedule(true);});
637 + });
638 +}
639 +
640 </script type="text/babel">
641 </body>
574 -</html>
\ No newline at end of file
642 +</html>
packages/react-events/drag.js deleted
-14
@@ -1,14 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - *
7 - * @flow
8 - */
9 -
10 -'use strict';
11 -
12 -const Drag = require('./src/Drag');
13 -
14 -module.exports = Drag.default || Drag;
packages/react-events/focus.js deleted
-14
@@ -1,14 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - *
7 - * @flow
8 - */
9 -
10 -'use strict';
11 -
12 -const Focus = require('./src/Focus');
13 -
14 -module.exports = Focus.default || Focus;
packages/react-events/hover.js deleted
-14
@@ -1,14 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - *
7 - * @flow
8 - */
9 -
10 -'use strict';
11 -
12 -const Hover = require('./src/Hover');
13 -
14 -module.exports = Hover.default || Hover;
packages/react-events/npm/drag.js deleted
-7
@@ -1,7 +0,0 @@
1 -'use strict';
2 -
3 -if (process.env.NODE_ENV === 'production') {
4 - module.exports = require('./cjs/react-events-drag.production.min.js');
5 -} else {
6 - module.exports = require('./cjs/react-events-drag.development.js');
7 -}
packages/react-events/npm/focus.js deleted
-7
@@ -1,7 +0,0 @@
1 -'use strict';
2 -
3 -if (process.env.NODE_ENV === 'production') {
4 - module.exports = require('./cjs/react-events-focus.production.min.js');
5 -} else {
6 - module.exports = require('./cjs/react-events-focus.development.js');
7 -}
packages/react-events/npm/hover.js deleted
-7
@@ -1,7 +0,0 @@
1 -'use strict';
2 -
3 -if (process.env.NODE_ENV === 'production') {
4 - module.exports = require('./cjs/react-events-hover.production.min.js');
5 -} else {
6 - module.exports = require('./cjs/react-events-hover.development.js');
7 -}
packages/react-events/npm/press.js deleted
-7
@@ -1,7 +0,0 @@
1 -'use strict';
2 -
3 -if (process.env.NODE_ENV === 'production') {
4 - module.exports = require('./cjs/react-events-press.production.min.js');
5 -} else {
6 - module.exports = require('./cjs/react-events-press.development.js');
7 -}
packages/react-events/npm/swipe.js deleted
-7
@@ -1,7 +0,0 @@
1 -'use strict';
2 -
3 -if (process.env.NODE_ENV === 'production') {
4 - module.exports = require('./cjs/react-events-swipe.production.min.js');
5 -} else {
6 - module.exports = require('./cjs/react-events-swipe.development.js');
7 -}
packages/react-events/press.js deleted
-14
@@ -1,14 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - *
7 - * @flow
8 - */
9 -
10 -'use strict';
11 -
12 -const Press = require('./src/Press');
13 -
14 -module.exports = Press.default || Press;
packages/react-events/swipe.js deleted
-14
@@ -1,14 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - *
7 - * @flow
8 - */
9 -
10 -'use strict';
11 -
12 -const Swipe = require('./src/Swipe');
13 -
14 -module.exports = Swipe.default || Swipe;
packages/scheduler/npm/umd/scheduler.development.js
+8
@@ -96,6 +96,13 @@
96 );
97 }
98
99 + function unstable_forceFrameRate() {
100 + return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_forceFrameRate.apply(
101 + this,
102 + arguments
103 + );
104 + }
105 +
106 return Object.freeze({
107 unstable_now: unstable_now,
108 unstable_scheduleCallback: unstable_scheduleCallback,
@@ -108,6 +115,7 @@
115 unstable_continueExecution: unstable_continueExecution,
116 unstable_pauseExecution: unstable_pauseExecution,
117 unstable_getFirstCallbackNode: unstable_getFirstCallbackNode,
118 + unstable_forceFrameRate: unstable_forceFrameRate,
119 get unstable_IdlePriority() {
120 return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
121 .Scheduler.unstable_IdlePriority;
packages/scheduler/npm/umd/scheduler.production.min.js
+8
@@ -90,6 +90,13 @@
90 return undefined;
91 }
92
93 + function unstable_forceFrameRate() {
94 + return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_forceFrameRate.apply(
95 + this,
96 + arguments
97 + );
98 + }
99 +
100 return Object.freeze({
101 unstable_now: unstable_now,
102 unstable_scheduleCallback: unstable_scheduleCallback,
@@ -102,6 +109,7 @@
109 unstable_continueExecution: unstable_continueExecution,
110 unstable_pauseExecution: unstable_pauseExecution,
111 unstable_getFirstCallbackNode: unstable_getFirstCallbackNode,
112 + unstable_forceFrameRate: unstable_forceFrameRate,
113 get unstable_IdlePriority() {
114 return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
115 .Scheduler.unstable_IdlePriority;
packages/scheduler/npm/umd/scheduler.profiling.min.js
+8
@@ -90,6 +90,13 @@
90 return undefined;
91 }
92
93 + function unstable_forceFrameRate() {
94 + return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_forceFrameRate.apply(
95 + this,
96 + arguments
97 + );
98 + }
99 +
100 return Object.freeze({
101 unstable_now: unstable_now,
102 unstable_scheduleCallback: unstable_scheduleCallback,
@@ -102,6 +109,7 @@
109 unstable_continueExecution: unstable_continueExecution,
110 unstable_pauseExecution: unstable_pauseExecution,
111 unstable_getFirstCallbackNode: unstable_getFirstCallbackNode,
112 + unstable_forceFrameRate: unstable_forceFrameRate,
113 get unstable_IdlePriority() {
114 return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
115 .Scheduler.unstable_IdlePriority;
packages/scheduler/src/Scheduler.js
+2
@@ -14,6 +14,7 @@ import {
14 cancelHostCallback,
15 shouldYieldToHost,
16 getCurrentTime,
17 + forceFrameRate,
18 } from './SchedulerHostConfig';
19
20 // TODO: Use symbols?
@@ -448,4 +449,5 @@ export {
449 unstable_pauseExecution,
450 unstable_getFirstCallbackNode,
451 getCurrentTime as unstable_now,
452 + forceFrameRate as unstable_forceFrameRate,
453 };
packages/scheduler/src/forks/SchedulerHostConfig.default.js
+23 -1
@@ -17,6 +17,7 @@ export let requestHostCallback;
17 export let cancelHostCallback;
18 export let shouldYieldToHost;
19 export let getCurrentTime;
20 +export let forceFrameRate;
21
22 const hasNativePerformanceNow =
23 typeof performance === 'object' && typeof performance.now === 'function';
@@ -111,6 +112,7 @@ if (
112 shouldYieldToHost = function() {
113 return false;
114 };
115 + forceFrameRate = function() {};
116 } else {
117 if (typeof console !== 'undefined') {
118 // TODO: Remove fb.me link
@@ -144,11 +146,30 @@ if (
146 // frames.
147 let previousFrameTime = 33;
148 let activeFrameTime = 33;
149 + let fpsLocked = false;
150
151 shouldYieldToHost = function() {
152 return frameDeadline <= getCurrentTime();
153 };
154
155 + forceFrameRate = function(fps) {
156 + if (fps < 0 || fps > 125) {
157 + console.error(
158 + 'forceFrameRate takes a positive int between 0 and 125, ' +
159 + 'forcing framerates higher than 125 fps is not unsupported',
160 + );
161 + return;
162 + }
163 + if (fps > 0) {
164 + activeFrameTime = Math.floor(1000 / fps);
165 + fpsLocked = true;
166 + } else {
167 + // reset the framerate
168 + activeFrameTime = 33;
169 + fpsLocked = false;
170 + }
171 + };
172 +
173 // We use the postMessage trick to defer idle work until after the repaint.
174 const channel = new MessageChannel();
175 const port = channel.port2;
@@ -214,7 +235,8 @@ if (
235 let nextFrameTime = rafTime - frameDeadline + activeFrameTime;
236 if (
237 nextFrameTime < activeFrameTime &&
217 - previousFrameTime < activeFrameTime
238 + previousFrameTime < activeFrameTime &&
239 + !fpsLocked
240 ) {
241 if (nextFrameTime < 8) {
242 // Defensive coding. We don't support higher frame rates than 120hz.
packages/scheduler/src/forks/SchedulerHostConfig.mock.js
+4
@@ -47,6 +47,10 @@ export function getCurrentTime(): number {
47 return currentTime;
48 }
49
50 +export function forceFrameRate() {
51 + // No-op
52 +}
53 +
54 export function reset() {
55 if (isFlushing) {
56 throw new Error('Cannot reset while already flushing work.');
packages/shared/forks/Scheduler.umd.js
+2
@@ -27,6 +27,7 @@ const {
27 unstable_NormalPriority,
28 unstable_LowPriority,
29 unstable_IdlePriority,
30 + unstable_forceFrameRate,
31 } = ReactInternals.Scheduler;
32
33 export {
@@ -45,4 +46,5 @@ export {
46 unstable_NormalPriority,
47 unstable_LowPriority,
48 unstable_IdlePriority,
49 + unstable_forceFrameRate,
50 };