-
Notifications
You must be signed in to change notification settings - Fork 47k
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
Cloned flag to avoid extra clones in persistent renderer #27647
Conversation
// then we only have to check the `completedWork.subtreeFlags`. | ||
let child = completedWork.child; | ||
while (child !== null) { | ||
if ( | ||
(child.flags & MutationMask) !== NoFlags || | ||
(child.subtreeFlags & MutationMask) !== NoFlags | ||
(child.flags & (Cloned | Visibility | Placement)) !== NoFlags || |
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 the function where I would put the roll out flag
/* Skipped value: 0b0000000000000000000000001000; */ | ||
export const Update = /* */ 0b00000000000000000000000000100; | ||
export const Cloned = /* */ 0b10000000000000000000000000000; | ||
/* Skipped value: 0b00000000000000000000000001000; */ |
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.
Could we just use the skipped value? Can't think of any reason why not. I'm guessing this is the old Deletion flag that was replaced by ChildDeletion and then removed
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.
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.
react-devtools-shared/src/backend/ReactFiberFlags.js
was removed in https://github.com/facebook/react/pull/26542/files, so we could mirror the whole function implementation, instead of just mirroring flags.
We should update these flags for DevTools accordingly, like here, for example: https://github.com/facebook/react/blame/main/packages/react-devtools-shared/src/backend/renderer.js#L1515.
I think it should be safe to use skipped value here, because DevTools were not depending on this flag at any time, AFAIK. I can help with testing these changes if you end up with using skipped value.
aec7f84
to
8ef9bb2
Compare
Comparing: 56dbd58...966936b Critical size changesIncludes critical production bundles, as well as any change greater than 2%:
Significant size changesIncludes any change greater than 0.2%: Expand to show
|
830f58f
to
6c2c437
Compare
6c2c437
to
6a9a322
Compare
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
6a9a322
to
10f326d
Compare
10f326d
to
1d14580
Compare
1d14580
to
c73400f
Compare
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.
Definitely out of my comfort zone so I can't approve but left some comments. I think there is a bug that might be causing cloned tags to be assigned more than they should (but maybe I misunderstand the mechanic)
@@ -473,6 +489,8 @@ function updateHostComponent( | |||
// Note that this might release a previous clone. | |||
workInProgress.stateNode = currentInstance; | |||
return; | |||
} else { | |||
markCloned(workInProgress); |
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.
is this right? seems like we should only mark clone if requiresClone
is 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.
cloneInstance
above returned a new instance since newInstance !== currentInstance
in this branch, so the cloned tree needs to bubble up (I think)
if (!enablePersistedModeClonedFlag) { | ||
// We'll have to mark it as having an effect, even though we won't use the effect for anything. | ||
// This lets the parents know that at least one of their children has changed. | ||
markUpdate(workInProgress); | ||
} |
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 may be right but it's a little confusing that markCloned and markUpdate are not apparently mutually exclusive (when they actually are given the implementation of markCloned)
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 actually the core of the PR. We can have an Update
for example due to effect dependencies changing, but do not want to trigger a clone up the tree indicated by the Cloned
flag.
Previously they were both grouped together which means we'd do a full subtree clone if there was an effect dependency change in the subtree.
Even with the feature flag an effect change will still set the Update
flag, but Update
(behind flag) no longer causes a clone.
if (parentFiber.subtreeFlags & MutationMask) { | ||
if ( | ||
parentFiber.subtreeFlags & | ||
(enablePersistedModeClonedFlag ? MutationMask | Cloned : MutationMask) |
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 Cloned just be part of the MutationMask?
@@ -199,9 +211,12 @@ function doesRequireClone(current: null | Fiber, completedWork: Fiber) { | |||
// then we only have to check the `completedWork.subtreeFlags`. | |||
let child = completedWork.child; | |||
while (child !== null) { | |||
const checkedFlags = enablePersistedModeClonedFlag | |||
? Cloned | Visibility | Placement |
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 guess this answers my question about why Cloned isn't just part of MutationMask. you specifically want to track it separately from general mutations like Update. You could still make it part of the Mask and just not use MutationMask here (as you are doing now). I guess the question is does Cloned generally count as a Mutation you need to visit during commit (yes) and if so then it semantically probably does belong in the mask.
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 have similar questions to what @gnoff pointed out, but overall this makes sense. Have you tried syncing internally and running RN tests?
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'll run internal tests too now.
@@ -473,6 +489,8 @@ function updateHostComponent( | |||
// Note that this might release a previous clone. | |||
workInProgress.stateNode = currentInstance; | |||
return; | |||
} else { | |||
markCloned(workInProgress); |
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.
cloneInstance
above returned a new instance since newInstance !== currentInstance
in this branch, so the cloned tree needs to bubble up (I think)
if (!enablePersistedModeClonedFlag) { | ||
// We'll have to mark it as having an effect, even though we won't use the effect for anything. | ||
// This lets the parents know that at least one of their children has changed. | ||
markUpdate(workInProgress); | ||
} |
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 actually the core of the PR. We can have an Update
for example due to effect dependencies changing, but do not want to trigger a clone up the tree indicated by the Cloned
flag.
Previously they were both grouped together which means we'd do a full subtree clone if there was an effect dependency change in the subtree.
Even with the feature flag an effect change will still set the Update
flag, but Update
(behind flag) no longer causes a clone.
c73400f
to
b18abbb
Compare
263b12f
to
ae601e1
Compare
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 makes sense. Thanks for the thorough testing, let's move forward and iterate if necessary.
Persistent renderers used the `Update` effect flag to check if a subtree needs to be cloned. In some cases, that causes extra renders, such as when a layout effect is triggered which only has an effect on the JS side, but doesn't update the host components. It's been a bit tricky to find the right places where this needs to be set and I'm not 100% sure I got all the cases even though the tests passed. DiffTrain build for commit 5fb67fa.
) Persistent renderers used the `Update` effect flag to check if a subtree needs to be cloned. In some cases, that causes extra renders, such as when a layout effect is triggered which only has an effect on the JS side, but doesn't update the host components. It's been a bit tricky to find the right places where this needs to be set and I'm not 100% sure I got all the cases even though the tests passed. DiffTrain build for commit facebook@5fb67fa.
Persistent renderers used the
Update
effect flag to check if a subtree needs to be cloned. In some cases, that causes extra renders, such as when a layout effect is triggered which only has an effect on the JS side, but doesn't update the host components.It's been a bit tricky to find the right places where this needs to be set and I'm not 100% sure I got all the cases even though the tests passed.