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
};