-
Notifications
You must be signed in to change notification settings - Fork 47.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[schedule] Refactor Schedule, remove React-isms #13582
Conversation
@@ -309,10 +309,10 @@ describe('ScheduleDOM', () => { | |||
const callbackC = jest.fn(() => callbackLog.push('C')); | |||
const callbackD = jest.fn(() => callbackLog.push('D')); | |||
|
|||
scheduleWork(callbackA); // won't time out | |||
scheduleWork(callbackA, {timeout: 100}); // won't time out |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needed to make a small tweak to an existing test: Callbacks are now executed in timeout-order. This is the only semantic change.
Do you expect to fix the timeout issue I encountered as part of this, or is it completely orthogonal? |
React: size: 🔺+7.2%, gzip: 🔺+5.8% Details of bundled changes.Comparing: f6fb03e...3f059be react
schedule
Generated by 🚫 dangerJS |
@gaearon In a follow-up. Though I might have accidentally fixed it. This PR is just shuffling things around in preparation for bigger changes. |
Oh wow. I don't know what exactly changed but the time slicing fixture is now super smooth and doesn't lock up every few seconds. It's actually worrying because I don't like that the experience can change so much without tests also changing. Interestingly I still don't get into the |
Yeah we definitely need more tests. I added some in this PR, planning to write many more. |
I think that we should drop all bundling (rollup), all minification, all build scripts for this package. It should just be a single file in its final location. |
Ok I'll move everything back into one file and just put a big line in between the two halves, like we do with ReactFiberScheduler :D |
We should probably drop types and put them in separate libdefs. If we need ES module exports maybe we just have two copies of the file and keep them in sync. |
I read the tests in this and the continuations pull request briefly through. It seems that most of the tests consume the frame budget exactly, which is nice from the edge case standpoint, but the most common case might be where the last task goes a bit over the frame budget, and it would be good to document this and its variations and implications in the tests. |
What's the point of not having a build step? Simple babel+commonjs plugin in |
Hmm. Not sure what's going on here, but if you put <html>
<body>
<script src="../../../build/dist/react.development.js"></script>
<script src="../../../build/dist/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.js"></script>
<div id="container"></div>
<script type="text/babel">
ReactDOM.unstable_createRoot(
document.getElementById('container')
).render(<h1>Hello World!</h1>);
</script>
</body>
</html> into |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See the case above — is this a bug?
packages/schedule/src/Schedule.js
Outdated
let isHostCallbackScheduled = false; | ||
|
||
let timeRemaining; | ||
if (hasNativePerformanceNow) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is used before declaration. Hence the bugs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2c9a100
to
3f059be
Compare
Can also confirm time slicing fixture still works as intended (incl. the fix to its behavior on timeout) 👍 |
Once the API stabilizes, we will move Schedule this into a separate repo. To promote adoption, especially by projects outside the React ecosystem, we'll remove all React-isms from the source and keep it as simple as possible: - No build step. - No static types. - Everything is in a single file. If we end up needing to support multiple targets, like CommonJS and ESM, we can still avoid a build step by maintaining two copies of the same file, but with different exports. This commit also refactors the implementation to split out the DOM- specific parts (essentially a requestIdleCallback polyfill). Aside from the architectural benefits, this also makes it possible to write host- agnostic tests. If/when we publish a version of Schedule that targets other environments, like React Native, we can run these same tests across all implementations.
3f059be
to
7c4ff13
Compare
packages/schedule/src/Schedule.js
Outdated
const timeoutId = timeoutIds.get(callback); | ||
timeoutIds.delete(callbackId); | ||
localClearTimeout(timeoutId); | ||
cancelCallback = () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why arrow functions? I figured this would just become an ES file eventually?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Erm I don't get it. Why does that matter?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean ES5 file so that it can be published to npm directly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh, when you use 'ES file', seems we all think it refer to 'ES6+ file'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should I get rid of let/const as well then?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, let's make it as plain as possible.
packages/schedule/src/Schedule.js
Outdated
flushCallback(firstCallbackNode); | ||
} while ( | ||
firstCallbackNode !== null && | ||
firstCallbackNode.timesOutAt <= getCurrentTime() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since performance.now()
calls have a cost, I wonder if we could avoid recalculating it while we have expired work (since if 100 things expired checking the time is unnecessary — they can't "unexpire"). For example by having a nested loop. You read the time in the outer loop, and compare to it in the inner loop. Once you exit the inner loop, you read the time again. If there's nothing new that expired then we exit.
packages/schedule/src/Schedule.js
Outdated
// Keep flushing callbacks until we run out of time in the frame. | ||
while ( | ||
firstCallbackNode !== null && | ||
getTimeRemaining() - getCurrentTime() > 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if this could leave a small buffer as a heuristic. So we don't go over by a few ms from last iteration every time.
packages/schedule/src/Schedule.js
Outdated
let node = firstCallbackNode; | ||
do { | ||
if (node.timesOutAt > timesOutAt) { | ||
// This callback is lower priority than the new one. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to avoid using the word "priority" in this file if we can since mixing that with timeouts changes the meaning of "higher" and "lower".
e.g.
// The new callback times out before this one
That would also map nicely to the sorted order
packages/schedule/src/Schedule.js
Outdated
} while (node !== firstCallbackNode); | ||
|
||
if (next === null) { | ||
// No lower priority callback was found, which means the new callback is |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same re:wording
packages/schedule/src/Schedule.js
Outdated
}; | ||
getTimeRemaining = () => 0; | ||
} else if (window._sched) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe window.__scheduleMock
or something more obscure?
97ccdc9
to
5f74f04
Compare
isPerformingWork = false; | ||
if (firstCallbackNode !== null) { | ||
// There's still work remaining. Request another callback. | ||
ensureHostCallbackIsScheduled(firstCallbackNode); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@acdlite Seems ensureHostCallbackIsScheduled
doesn't use any argument in the function body, can I ask that why we pass the firstCallbackNode
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For Flow I think
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm wait this doesn't make sense. I dunno.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should I remove this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think in the initial commit (7c4ff13) ensureHostCallbackIsScheduled
expected a param, but the second commit (5f74f04) changed it to just read from the package-level firstCallbackNode
variable since that's always the value that was passed in anyway. Seems like the call sites were just overlooked as part of this change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the lint should warn on this but don't know why not
// to a naive implementation. | ||
var timeoutID = -1; | ||
requestCallback = function(callback, absoluteTimeout) { | ||
timeoutID = setTimeout(callback, 0, true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@acdlite The absoluteTimeout
argument doesn't been used here, I'm not sure why eslint doesn't warn about this. Should I remove it?
* Refactor Schedule, remove React-isms Once the API stabilizes, we will move Schedule this into a separate repo. To promote adoption, especially by projects outside the React ecosystem, we'll remove all React-isms from the source and keep it as simple as possible: - No build step. - No static types. - Everything is in a single file. If we end up needing to support multiple targets, like CommonJS and ESM, we can still avoid a build step by maintaining two copies of the same file, but with different exports. This commit also refactors the implementation to split out the DOM- specific parts (essentially a requestIdleCallback polyfill). Aside from the architectural benefits, this also makes it possible to write host- agnostic tests. If/when we publish a version of Schedule that targets other environments, like React Native, we can run these same tests across all implementations. * Edits in response to Dan's PR feedback
* Refactor Schedule, remove React-isms Once the API stabilizes, we will move Schedule this into a separate repo. To promote adoption, especially by projects outside the React ecosystem, we'll remove all React-isms from the source and keep it as simple as possible: - No build step. - No static types. - Everything is in a single file. If we end up needing to support multiple targets, like CommonJS and ESM, we can still avoid a build step by maintaining two copies of the same file, but with different exports. This commit also refactors the implementation to split out the DOM- specific parts (essentially a requestIdleCallback polyfill). Aside from the architectural benefits, this also makes it possible to write host- agnostic tests. If/when we publish a version of Schedule that targets other environments, like React Native, we can run these same tests across all implementations. * Edits in response to Dan's PR feedback
Once the API stabilizes, we will move Schedule into a separate repo. To promote adoption, especially by projects outside the React ecosystem, we'll remove all React-isms from the source and keep it as simple as possible:
If we end up needing to support multiple targets, like CommonJS and ESM, we can still avoid a build step by maintaining two copies of the same file, but with different exports.
This commit also refactors the implementation to split out the DOM- specific parts (essentially a requestIdleCallback polyfill). Aside from the architectural benefits, this also makes it possible to write host-agnostic tests. If/when we publish a version of Schedule that targets other environments, like React Native, we can run these same tests across all implementations.