From 43c4e5f348eb5704464986e2dc3221e347041b82 Mon Sep 17 00:00:00 2001 From: Nathan Schloss Date: Thu, 31 Jan 2019 12:11:31 -0500 Subject: [PATCH] Add method for forcing a lower framerate --- fixtures/scheduler/index.html | 138 +++++++++++++----- packages/react-events/drag.js | 14 -- packages/react-events/focus.js | 14 -- packages/react-events/hover.js | 14 -- packages/react-events/npm/drag.js | 7 - packages/react-events/npm/focus.js | 7 - packages/react-events/npm/hover.js | 7 - packages/react-events/npm/press.js | 7 - packages/react-events/npm/swipe.js | 7 - packages/react-events/press.js | 14 -- packages/react-events/swipe.js | 14 -- .../npm/umd/scheduler.development.js | 8 + .../npm/umd/scheduler.production.min.js | 8 + .../npm/umd/scheduler.profiling.min.js | 8 + packages/scheduler/src/Scheduler.js | 2 + .../src/forks/SchedulerHostConfig.default.js | 24 ++- .../src/forks/SchedulerHostConfig.mock.js | 4 + packages/shared/forks/Scheduler.umd.js | 2 + 18 files changed, 158 insertions(+), 141 deletions(-) delete mode 100644 packages/react-events/drag.js delete mode 100644 packages/react-events/focus.js delete mode 100644 packages/react-events/hover.js delete mode 100644 packages/react-events/npm/drag.js delete mode 100644 packages/react-events/npm/focus.js delete mode 100644 packages/react-events/npm/hover.js delete mode 100644 packages/react-events/npm/press.js delete mode 100644 packages/react-events/npm/swipe.js delete mode 100644 packages/react-events/press.js delete mode 100644 packages/react-events/swipe.js diff --git a/fixtures/scheduler/index.html b/fixtures/scheduler/index.html index 553d79970d894..4c425b95ebed7 100644 --- a/fixtures/scheduler/index.html +++ b/fixtures/scheduler/index.html @@ -105,6 +105,20 @@

Tests:

Actual:
+
  • +

    Can force a specific framerate

    +

    IMPORTANT: 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

    + +
    Expected:
    +
    +
    +
    -------------------------------------------------
    +
    If you see the same above and below it's correct. +
    -------------------------------------------------
    +
    Actual:
    +
    +
    +
  • @@ -117,6 +131,9 @@

    Tests:

    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 @@

    Tests:

    [ '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 @@

    Tests:

    '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 @@

    Tests:

    '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 @@

    Tests:

    '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 @@

    Tests:

    '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 @@

    Tests:

    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 @@

    Tests:

    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 @@

    Tests:

    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 @@

    Tests:

    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 @@

    Tests:

    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 @@

    Tests:

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

    Tests:

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

    Tests:

    counter++; counterNode.innerHTML = counter; waitForTimeToPass(100); - scheduleCallback(incrementCounterAndScheduleNextCallback); + scheduleCallback(NormalPriority, incrementCounterAndScheduleNextCallback); } - scheduleCallback(incrementCounterAndScheduleNextCallback); + scheduleCallback(NormalPriority, incrementCounterAndScheduleNextCallback); } function runTestEight() { @@ -542,18 +566,18 @@

    Tests:

    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 @@

    Tests:

    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);}); + }); +} + - \ No newline at end of file + diff --git a/packages/react-events/drag.js b/packages/react-events/drag.js deleted file mode 100644 index f8148f1273cbf..0000000000000 --- a/packages/react-events/drag.js +++ /dev/null @@ -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; diff --git a/packages/react-events/focus.js b/packages/react-events/focus.js deleted file mode 100644 index 0b9288a790fef..0000000000000 --- a/packages/react-events/focus.js +++ /dev/null @@ -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; diff --git a/packages/react-events/hover.js b/packages/react-events/hover.js deleted file mode 100644 index a53675ca5c1ab..0000000000000 --- a/packages/react-events/hover.js +++ /dev/null @@ -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; diff --git a/packages/react-events/npm/drag.js b/packages/react-events/npm/drag.js deleted file mode 100644 index 4b8838b9658c8..0000000000000 --- a/packages/react-events/npm/drag.js +++ /dev/null @@ -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'); -} diff --git a/packages/react-events/npm/focus.js b/packages/react-events/npm/focus.js deleted file mode 100644 index 06b656a761ed3..0000000000000 --- a/packages/react-events/npm/focus.js +++ /dev/null @@ -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'); -} diff --git a/packages/react-events/npm/hover.js b/packages/react-events/npm/hover.js deleted file mode 100644 index 1000d87449067..0000000000000 --- a/packages/react-events/npm/hover.js +++ /dev/null @@ -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'); -} diff --git a/packages/react-events/npm/press.js b/packages/react-events/npm/press.js deleted file mode 100644 index deaba326bba07..0000000000000 --- a/packages/react-events/npm/press.js +++ /dev/null @@ -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'); -} diff --git a/packages/react-events/npm/swipe.js b/packages/react-events/npm/swipe.js deleted file mode 100644 index aa2b1f2fe13f9..0000000000000 --- a/packages/react-events/npm/swipe.js +++ /dev/null @@ -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'); -} diff --git a/packages/react-events/press.js b/packages/react-events/press.js deleted file mode 100644 index 2add5ba8ed9d5..0000000000000 --- a/packages/react-events/press.js +++ /dev/null @@ -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; diff --git a/packages/react-events/swipe.js b/packages/react-events/swipe.js deleted file mode 100644 index 3c2ad195ef4b2..0000000000000 --- a/packages/react-events/swipe.js +++ /dev/null @@ -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; diff --git a/packages/scheduler/npm/umd/scheduler.development.js b/packages/scheduler/npm/umd/scheduler.development.js index 206897dcfb97f..48845964b2664 100644 --- a/packages/scheduler/npm/umd/scheduler.development.js +++ b/packages/scheduler/npm/umd/scheduler.development.js @@ -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; diff --git a/packages/scheduler/npm/umd/scheduler.production.min.js b/packages/scheduler/npm/umd/scheduler.production.min.js index 90ee11f122d92..3d020960b2bd0 100644 --- a/packages/scheduler/npm/umd/scheduler.production.min.js +++ b/packages/scheduler/npm/umd/scheduler.production.min.js @@ -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; diff --git a/packages/scheduler/npm/umd/scheduler.profiling.min.js b/packages/scheduler/npm/umd/scheduler.profiling.min.js index 90ee11f122d92..3d020960b2bd0 100644 --- a/packages/scheduler/npm/umd/scheduler.profiling.min.js +++ b/packages/scheduler/npm/umd/scheduler.profiling.min.js @@ -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; diff --git a/packages/scheduler/src/Scheduler.js b/packages/scheduler/src/Scheduler.js index c3f65a6a03b33..e1300149272f9 100644 --- a/packages/scheduler/src/Scheduler.js +++ b/packages/scheduler/src/Scheduler.js @@ -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, }; diff --git a/packages/scheduler/src/forks/SchedulerHostConfig.default.js b/packages/scheduler/src/forks/SchedulerHostConfig.default.js index fd6bdea74377e..ec084f1dd1589 100644 --- a/packages/scheduler/src/forks/SchedulerHostConfig.default.js +++ b/packages/scheduler/src/forks/SchedulerHostConfig.default.js @@ -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. diff --git a/packages/scheduler/src/forks/SchedulerHostConfig.mock.js b/packages/scheduler/src/forks/SchedulerHostConfig.mock.js index 1fda4d2ca7a37..0e2641c2c3be2 100644 --- a/packages/scheduler/src/forks/SchedulerHostConfig.mock.js +++ b/packages/scheduler/src/forks/SchedulerHostConfig.mock.js @@ -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.'); diff --git a/packages/shared/forks/Scheduler.umd.js b/packages/shared/forks/Scheduler.umd.js index 6c9c918dc8779..b1a77ce9232df 100644 --- a/packages/shared/forks/Scheduler.umd.js +++ b/packages/shared/forks/Scheduler.umd.js @@ -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, };