Add method for forcing a lower framerate

This commit is contained in:
Nathan Schloss
2019-04-29 15:51:58 -07:00
committed by Andrew Clark
parent 1b752f1914
commit 43c4e5f348
18 changed files with 158 additions and 141 deletions
+103 -35
View File
@@ -105,6 +105,20 @@
<div><b>Actual:</b></div>
<div id="test-8"></div>
</li>
<li>
<p>Can force a specific framerate</p>
<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>
<button onClick="runTestNine()">Run Test 9</button>
<div><b>Expected:</b></div>
<div id="test-9-expected">
</div>
<div> -------------------------------------------------</div>
<div> If you see the same above and below it's correct.
<div> -------------------------------------------------</div>
<div><b>Actual:</b></div>
<div id="test-9"></div>
</div>
</li>
</ol>
<script src="../../build/node_modules/react/umd/react.development.js"></script>
<script src="../../build/node_modules/scheduler/umd/scheduler.development.js"></script>
@@ -117,6 +131,9 @@ const {
unstable_getFirstCallbackNode: getFirstCallbackNode,
unstable_pauseExecution: pauseExecution,
unstable_continueExecution: continueExecution,
unstable_forceFrameRate: forceFrameRate,
unstable_shouldYield: shouldYield,
unstable_NormalPriority: NormalPriority,
} = Scheduler;
function displayTestResult(testNumber) {
const expectationNode = document.getElementById('test-' + testNumber + '-expected');
@@ -188,7 +205,7 @@ const expectedResults = [
[
'scheduled Cb1',
'frame 1 started',
'cb1 called with argument of {"didTimeout":false}',
'cb1 called with argument of false',
'frame 2 started',
'frame 3 started... we stop counting now.',
],
@@ -197,8 +214,8 @@ const expectedResults = [
'scheduled CbA',
'scheduled CbB',
'frame 1 started',
'cbA called with argument of {"didTimeout":false}',
'cbB called with argument of {"didTimeout":false}',
'cbA called with argument of false',
'cbB called with argument of false',
'frame 2 started',
'frame 3 started... we stop counting now.',
],
@@ -208,9 +225,9 @@ const expectedResults = [
'scheduled CbB',
'frame 1 started',
'scheduled CbA again',
'cbA0 called with argument of {"didTimeout":false}',
'cbB called with argument of {"didTimeout":false}',
'cbA1 called with argument of {"didTimeout":false}',
'cbA0 called with argument of false',
'cbB called with argument of false',
'cbA1 called with argument of false',
'frame 2 started',
'frame 3 started... we stop counting now.',
],
@@ -222,11 +239,11 @@ const expectedResults = [
'scheduled cbD',
'frame 1 started',
'cbC called with argument of {"didTimeout":true}',
'cbA called with argument of {"didTimeout":false}',
'cbA called with argument of false',
'cbA running and taking some time',
'frame 2 started',
'cbB called with argument of {"didTimeout":false}',
'cbD called with argument of {"didTimeout":false}',
'cbB called with argument of false',
'cbD called with argument of false',
'frame 3 started... we stop counting now.',
],
// test 5
@@ -243,6 +260,13 @@ const expectedResults = [
'Finishing...',
'Done!',
],
// test 9
[
'Forcing new frame times...',
'Using new frame time!',
'Using new frame time!',
'Finished!',
],
];
function runTestOne() {
// Test 1
@@ -253,7 +277,7 @@ function runTestOne() {
const cb1 = (x) => {
updateTestResult(1, 'cb1 called with argument of ' + JSON.stringify(x));
}
scheduleCallback(cb1);
scheduleCallback(NormalPriority, cb1);
updateTestResult(1, 'scheduled Cb1');
logWhenFramesStart(1, () => {
displayTestResult(1);
@@ -271,9 +295,9 @@ function runTestTwo() {
const cbB = (x) => {
updateTestResult(2, 'cbB called with argument of ' + JSON.stringify(x));
}
scheduleCallback(cbA);
scheduleCallback(NormalPriority, cbA);
updateTestResult(2, 'scheduled CbA');
scheduleCallback(cbB);
scheduleCallback(NormalPriority, cbB);
updateTestResult(2, 'scheduled CbB');
logWhenFramesStart(2, () => {
displayTestResult(2);
@@ -288,7 +312,7 @@ function runTestThree() {
let callbackAIterations = 0;
const cbA = (x) => {
if (callbackAIterations < 1) {
scheduleCallback(cbA);
scheduleCallback(NormalPriority, cbA);
updateTestResult(3, 'scheduled CbA again');
}
updateTestResult(3, 'cbA' + callbackAIterations + ' called with argument of ' + JSON.stringify(x));
@@ -297,9 +321,9 @@ function runTestThree() {
const cbB = (x) => {
updateTestResult(3, 'cbB called with argument of ' + JSON.stringify(x));
}
scheduleCallback(cbA);
scheduleCallback(NormalPriority, cbA);
updateTestResult(3, 'scheduled CbA');
scheduleCallback(cbB);
scheduleCallback(NormalPriority, cbB);
updateTestResult(3, 'scheduled CbB');
logWhenFramesStart(3, () => {
displayTestResult(3);
@@ -333,13 +357,13 @@ function runTestFour() {
const cbD = (x) => {
updateTestResult(4, 'cbD called with argument of ' + JSON.stringify(x));
}
scheduleCallback(cbA); // won't time out
scheduleCallback(NormalPriority, cbA); // won't time out
updateTestResult(4, 'scheduled cbA');
scheduleCallback(cbB, {timeout: 100}); // times out later
scheduleCallback(NormalPriority, cbB, {timeout: 100}); // times out later
updateTestResult(4, 'scheduled cbB');
scheduleCallback(cbC, {timeout: 1}); // will time out fast
scheduleCallback(NormalPriority, cbC, {timeout: 1}); // will time out fast
updateTestResult(4, 'scheduled cbC');
scheduleCallback(cbD); // won't time out
scheduleCallback(NormalPriority, cbD); // won't time out
updateTestResult(4, 'scheduled cbD');
// should have run in order of C, A, B, D
@@ -418,15 +442,15 @@ function runTestFive() {
});
});
});
scheduleCallback(cbA);
scheduleCallback(NormalPriority, cbA);
console.log('scheduled cbA');
scheduleCallback(cbB); // will throw error
scheduleCallback(NormalPriority, cbB); // will throw error
console.log('scheduled cbB');
scheduleCallback(cbC);
scheduleCallback(NormalPriority, cbC);
console.log('scheduled cbC');
scheduleCallback(cbD); // will throw error
scheduleCallback(NormalPriority, cbD); // will throw error
console.log('scheduled cbD');
scheduleCallback(cbE);
scheduleCallback(NormalPriority, cbE);
console.log('scheduled cbE');
};
}
@@ -496,15 +520,15 @@ function runTestSix() {
});
});
});
scheduleCallback(cbA);
scheduleCallback(NormalPriority, cbA);
console.log('scheduled cbA');
scheduleCallback(cbB); // will throw error
scheduleCallback(NormalPriority, cbB); // will throw error
console.log('scheduled cbB');
scheduleCallback(cbC, {timeout: 1});
scheduleCallback(NormalPriority, cbC, {timeout: 1});
console.log('scheduled cbC');
scheduleCallback(cbD, {timeout: 1}); // will throw error
scheduleCallback(NormalPriority, cbD, {timeout: 1}); // will throw error
console.log('scheduled cbD');
scheduleCallback(cbE, {timeout: 1});
scheduleCallback(NormalPriority, cbE, {timeout: 1});
console.log('scheduled cbE');
};
}
@@ -520,9 +544,9 @@ function runTestSeven() {
counter++;
counterNode.innerHTML = counter;
waitForTimeToPass(100);
scheduleCallback(incrementCounterAndScheduleNextCallback);
scheduleCallback(NormalPriority, incrementCounterAndScheduleNextCallback);
}
scheduleCallback(incrementCounterAndScheduleNextCallback);
scheduleCallback(NormalPriority, incrementCounterAndScheduleNextCallback);
}
function runTestEight() {
@@ -542,18 +566,18 @@ function runTestEight() {
return count;
}
scheduleCallback(() => {
scheduleCallback(NormalPriority, () => {
// size should be 0
updateTestResult(8, `Queue size: ${countNodesInStack(getFirstCallbackNode())}.`);
updateTestResult(8, 'Pausing... press continue to resume.');
pauseExecution();
scheduleCallback(function () {
scheduleCallback(NormalPriority, function () {
updateTestResult(8, 'Finishing...');
displayTestResult(8);
})
scheduleCallback(function () {
scheduleCallback(NormalPriority, function () {
updateTestResult(8, 'Done!');
displayTestResult(8);
checkTestResult(8);
@@ -569,6 +593,50 @@ function continueTestEight() {
continueExecution();
}
function runTestNine() {
clearTestResult(9);
// We have this to make sure that the thing that goes right after it can get a full frame
var forceFrameFinish = () => {
while (!shouldYield()) {
waitForTimeToPass(1);
}
waitForTimeToPass(100);
}
scheduleCallback(NormalPriority, forceFrameFinish);
scheduleCallback(NormalPriority, () => {
var startTime = now();
while (!shouldYield()) {}
var initialFrameTime = now() - startTime;
var newFrameTime = (initialFrameTime * 2) > 60 ? (initialFrameTime * 2) : 60;
var newFrameRate = Math.floor(1000/newFrameTime);
updateTestResult(9, `Forcing new frame times...`);
displayTestResult(9);
forceFrameRate(newFrameRate);
var toSchedule = (again) => {
var startTime = now();
while (!shouldYield()) {}
var frameTime = now() - startTime;
if (frameTime >= (newFrameTime-8)) {
updateTestResult(9, `Using new frame time!`);
} else {
updateTestResult(9, `Failed to use new frame time. (off by ${newFrameTime - frameTime}ms)`);
}
displayTestResult(9);
if (again) {
scheduleCallback(NormalPriority, forceFrameFinish);
scheduleCallback(NormalPriority, () => {toSchedule(false);});
} else {
updateTestResult(9, `Finished!`);
forceFrameRate(0);
displayTestResult(9);
checkTestResult(9);
}
}
scheduleCallback(NormalPriority, forceFrameFinish);
scheduleCallback(NormalPriority, () => {toSchedule(true);});
});
}
</script type="text/babel">
</body>
</html>
</html>
-14
View File
@@ -1,14 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
'use strict';
const Drag = require('./src/Drag');
module.exports = Drag.default || Drag;
-14
View File
@@ -1,14 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
'use strict';
const Focus = require('./src/Focus');
module.exports = Focus.default || Focus;
-14
View File
@@ -1,14 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
'use strict';
const Hover = require('./src/Hover');
module.exports = Hover.default || Hover;
-7
View File
@@ -1,7 +0,0 @@
'use strict';
if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/react-events-drag.production.min.js');
} else {
module.exports = require('./cjs/react-events-drag.development.js');
}
-7
View File
@@ -1,7 +0,0 @@
'use strict';
if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/react-events-focus.production.min.js');
} else {
module.exports = require('./cjs/react-events-focus.development.js');
}
-7
View File
@@ -1,7 +0,0 @@
'use strict';
if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/react-events-hover.production.min.js');
} else {
module.exports = require('./cjs/react-events-hover.development.js');
}
-7
View File
@@ -1,7 +0,0 @@
'use strict';
if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/react-events-press.production.min.js');
} else {
module.exports = require('./cjs/react-events-press.development.js');
}
-7
View File
@@ -1,7 +0,0 @@
'use strict';
if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/react-events-swipe.production.min.js');
} else {
module.exports = require('./cjs/react-events-swipe.development.js');
}
-14
View File
@@ -1,14 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
'use strict';
const Press = require('./src/Press');
module.exports = Press.default || Press;
-14
View File
@@ -1,14 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
'use strict';
const Swipe = require('./src/Swipe');
module.exports = Swipe.default || Swipe;
@@ -96,6 +96,13 @@
);
}
function unstable_forceFrameRate() {
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_forceFrameRate.apply(
this,
arguments
);
}
return Object.freeze({
unstable_now: unstable_now,
unstable_scheduleCallback: unstable_scheduleCallback,
@@ -108,6 +115,7 @@
unstable_continueExecution: unstable_continueExecution,
unstable_pauseExecution: unstable_pauseExecution,
unstable_getFirstCallbackNode: unstable_getFirstCallbackNode,
unstable_forceFrameRate: unstable_forceFrameRate,
get unstable_IdlePriority() {
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
.Scheduler.unstable_IdlePriority;
@@ -90,6 +90,13 @@
return undefined;
}
function unstable_forceFrameRate() {
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_forceFrameRate.apply(
this,
arguments
);
}
return Object.freeze({
unstable_now: unstable_now,
unstable_scheduleCallback: unstable_scheduleCallback,
@@ -102,6 +109,7 @@
unstable_continueExecution: unstable_continueExecution,
unstable_pauseExecution: unstable_pauseExecution,
unstable_getFirstCallbackNode: unstable_getFirstCallbackNode,
unstable_forceFrameRate: unstable_forceFrameRate,
get unstable_IdlePriority() {
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
.Scheduler.unstable_IdlePriority;
+8
View File
@@ -90,6 +90,13 @@
return undefined;
}
function unstable_forceFrameRate() {
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_forceFrameRate.apply(
this,
arguments
);
}
return Object.freeze({
unstable_now: unstable_now,
unstable_scheduleCallback: unstable_scheduleCallback,
@@ -102,6 +109,7 @@
unstable_continueExecution: unstable_continueExecution,
unstable_pauseExecution: unstable_pauseExecution,
unstable_getFirstCallbackNode: unstable_getFirstCallbackNode,
unstable_forceFrameRate: unstable_forceFrameRate,
get unstable_IdlePriority() {
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
.Scheduler.unstable_IdlePriority;
+2
View File
@@ -14,6 +14,7 @@ import {
cancelHostCallback,
shouldYieldToHost,
getCurrentTime,
forceFrameRate,
} from './SchedulerHostConfig';
// TODO: Use symbols?
@@ -448,4 +449,5 @@ export {
unstable_pauseExecution,
unstable_getFirstCallbackNode,
getCurrentTime as unstable_now,
forceFrameRate as unstable_forceFrameRate,
};
@@ -17,6 +17,7 @@ export let requestHostCallback;
export let cancelHostCallback;
export let shouldYieldToHost;
export let getCurrentTime;
export let forceFrameRate;
const hasNativePerformanceNow =
typeof performance === 'object' && typeof performance.now === 'function';
@@ -111,6 +112,7 @@ if (
shouldYieldToHost = function() {
return false;
};
forceFrameRate = function() {};
} else {
if (typeof console !== 'undefined') {
// TODO: Remove fb.me link
@@ -144,11 +146,30 @@ if (
// frames.
let previousFrameTime = 33;
let activeFrameTime = 33;
let fpsLocked = false;
shouldYieldToHost = function() {
return frameDeadline <= getCurrentTime();
};
forceFrameRate = function(fps) {
if (fps < 0 || fps > 125) {
console.error(
'forceFrameRate takes a positive int between 0 and 125, ' +
'forcing framerates higher than 125 fps is not unsupported',
);
return;
}
if (fps > 0) {
activeFrameTime = Math.floor(1000 / fps);
fpsLocked = true;
} else {
// reset the framerate
activeFrameTime = 33;
fpsLocked = false;
}
};
// We use the postMessage trick to defer idle work until after the repaint.
const channel = new MessageChannel();
const port = channel.port2;
@@ -214,7 +235,8 @@ if (
let nextFrameTime = rafTime - frameDeadline + activeFrameTime;
if (
nextFrameTime < activeFrameTime &&
previousFrameTime < activeFrameTime
previousFrameTime < activeFrameTime &&
!fpsLocked
) {
if (nextFrameTime < 8) {
// Defensive coding. We don't support higher frame rates than 120hz.
@@ -47,6 +47,10 @@ export function getCurrentTime(): number {
return currentTime;
}
export function forceFrameRate() {
// No-op
}
export function reset() {
if (isFlushing) {
throw new Error('Cannot reset while already flushing work.');
+2
View File
@@ -27,6 +27,7 @@ const {
unstable_NormalPriority,
unstable_LowPriority,
unstable_IdlePriority,
unstable_forceFrameRate,
} = ReactInternals.Scheduler;
export {
@@ -45,4 +46,5 @@ export {
unstable_NormalPriority,
unstable_LowPriority,
unstable_IdlePriority,
unstable_forceFrameRate,
};