From e0160d50c5a492a925db6ab3f8478e118336c722 Mon Sep 17 00:00:00 2001 From: Luna Ruan Date: Fri, 8 Apr 2022 19:48:30 -0500 Subject: [PATCH] add transition tracing transitions stack (#24321) Added a transitions stack for to keep track of which transitions are still happening for the current boundary. * On the root, we will get all transitions that have been initiated for the corresponding lanes. * Whenever we encounter a suspended boundary, we will add all transitions on the stack onto the boundary * Whenever we encounter a boundary that just unsuspended, we will add all transitions on the boundary onto the stack A transition will be considered complete when there are no boundaries that have the associated transition --- .../src/ReactFiberBeginWork.new.js | 17 ++-- .../src/ReactFiberBeginWork.old.js | 17 ++-- .../src/ReactFiberCompleteWork.new.js | 8 +- .../src/ReactFiberCompleteWork.old.js | 8 +- .../src/ReactFiberTransition.new.js | 77 +++++++++++++++---- .../src/ReactFiberTransition.old.js | 77 +++++++++++++++---- .../src/ReactFiberUnwindWork.new.js | 23 ++---- .../src/ReactFiberUnwindWork.old.js | 23 ++---- 8 files changed, 162 insertions(+), 88 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.new.js b/packages/react-reconciler/src/ReactFiberBeginWork.new.js index b9a35475e693e..3df345b6380bd 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.new.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.new.js @@ -661,7 +661,7 @@ function updateOffscreenComponent( // push the cache pool even though we're going to bail out // because otherwise there'd be a context mismatch if (current !== null) { - pushTransition(workInProgress, null); + pushTransition(workInProgress, null, null); } } pushRenderLanes(workInProgress, renderLanes); @@ -695,7 +695,7 @@ function updateOffscreenComponent( // push the cache pool even though we're going to bail out // because otherwise there'd be a context mismatch if (current !== null) { - pushTransition(workInProgress, null); + pushTransition(workInProgress, null, null); } } @@ -733,7 +733,9 @@ function updateOffscreenComponent( // using the same cache. Unless the parent changed, since that means // there was a refresh. const prevCachePool = prevState !== null ? prevState.cachePool : null; - pushTransition(workInProgress, prevCachePool); + // TODO: Consider if and how Offscreen pre-rendering should + // be attributed to the transition that spawned it + pushTransition(workInProgress, prevCachePool, null); } pushRenderLanes(workInProgress, subtreeRenderLanes); @@ -751,7 +753,7 @@ function updateOffscreenComponent( // using the same cache. Unless the parent changed, since that means // there was a refresh. const prevCachePool = prevState.cachePool; - pushTransition(workInProgress, prevCachePool); + pushTransition(workInProgress, prevCachePool, null); } // Since we're not hidden anymore, reset the state @@ -767,7 +769,7 @@ function updateOffscreenComponent( // using the same cache. Unless the parent changed, since that means // there was a refresh. if (current !== null) { - pushTransition(workInProgress, null); + pushTransition(workInProgress, null, null); } } } @@ -1330,10 +1332,10 @@ function updateHostRoot(current, workInProgress, renderLanes) { const nextState: RootState = workInProgress.memoizedState; const root: FiberRoot = workInProgress.stateNode; + pushRootTransition(workInProgress, root, renderLanes); if (enableCache) { const nextCache: Cache = nextState.cache; - pushRootTransition(root); pushCacheProvider(workInProgress, nextCache); if (nextCache !== prevState.cache) { // The root cache refreshed. @@ -3572,10 +3574,11 @@ function attemptEarlyBailoutIfNoScheduledUpdate( case HostRoot: pushHostRootContext(workInProgress); const root: FiberRoot = workInProgress.stateNode; + pushRootTransition(workInProgress, root, renderLanes); + if (enableCache) { const cache: Cache = current.memoizedState.cache; pushCacheProvider(workInProgress, cache); - pushRootTransition(root); } if (enableTransitionTracing) { workInProgress.memoizedState.transitions = getWorkInProgressTransitions(); diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.old.js b/packages/react-reconciler/src/ReactFiberBeginWork.old.js index 42641c85dd060..66324c8dc3336 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.old.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.old.js @@ -661,7 +661,7 @@ function updateOffscreenComponent( // push the cache pool even though we're going to bail out // because otherwise there'd be a context mismatch if (current !== null) { - pushTransition(workInProgress, null); + pushTransition(workInProgress, null, null); } } pushRenderLanes(workInProgress, renderLanes); @@ -695,7 +695,7 @@ function updateOffscreenComponent( // push the cache pool even though we're going to bail out // because otherwise there'd be a context mismatch if (current !== null) { - pushTransition(workInProgress, null); + pushTransition(workInProgress, null, null); } } @@ -733,7 +733,9 @@ function updateOffscreenComponent( // using the same cache. Unless the parent changed, since that means // there was a refresh. const prevCachePool = prevState !== null ? prevState.cachePool : null; - pushTransition(workInProgress, prevCachePool); + // TODO: Consider if and how Offscreen pre-rendering should + // be attributed to the transition that spawned it + pushTransition(workInProgress, prevCachePool, null); } pushRenderLanes(workInProgress, subtreeRenderLanes); @@ -751,7 +753,7 @@ function updateOffscreenComponent( // using the same cache. Unless the parent changed, since that means // there was a refresh. const prevCachePool = prevState.cachePool; - pushTransition(workInProgress, prevCachePool); + pushTransition(workInProgress, prevCachePool, null); } // Since we're not hidden anymore, reset the state @@ -767,7 +769,7 @@ function updateOffscreenComponent( // using the same cache. Unless the parent changed, since that means // there was a refresh. if (current !== null) { - pushTransition(workInProgress, null); + pushTransition(workInProgress, null, null); } } } @@ -1330,10 +1332,10 @@ function updateHostRoot(current, workInProgress, renderLanes) { const nextState: RootState = workInProgress.memoizedState; const root: FiberRoot = workInProgress.stateNode; + pushRootTransition(workInProgress, root, renderLanes); if (enableCache) { const nextCache: Cache = nextState.cache; - pushRootTransition(root); pushCacheProvider(workInProgress, nextCache); if (nextCache !== prevState.cache) { // The root cache refreshed. @@ -3572,10 +3574,11 @@ function attemptEarlyBailoutIfNoScheduledUpdate( case HostRoot: pushHostRootContext(workInProgress); const root: FiberRoot = workInProgress.stateNode; + pushRootTransition(workInProgress, root, renderLanes); + if (enableCache) { const cache: Cache = current.memoizedState.cache; pushCacheProvider(workInProgress, cache); - pushRootTransition(root); } if (enableTransitionTracing) { workInProgress.memoizedState.transitions = getWorkInProgressTransitions(); diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js index 979f76b51415d..e85af7d9f5dba 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js @@ -875,8 +875,6 @@ function completeWork( } if (enableCache) { - popRootTransition(fiberRoot, renderLanes); - let previousCache: Cache | null = null; if (current !== null) { previousCache = current.memoizedState.cache; @@ -888,6 +886,7 @@ function completeWork( } popCacheProvider(workInProgress, cache); } + popRootTransition(workInProgress, fiberRoot, renderLanes); popHostContainer(workInProgress); popTopLevelLegacyContextObject(workInProgress); resetMutableSourceWorkInProgressVersions(); @@ -1593,11 +1592,10 @@ function completeWork( // Run passive effects to retain/release the cache. workInProgress.flags |= Passive; } - if (current !== null) { - popTransition(workInProgress); - } } + popTransition(workInProgress, current); + return null; } case CacheComponent: { diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.old.js b/packages/react-reconciler/src/ReactFiberCompleteWork.old.js index 4b8fa1083cfaf..dea814c9113ad 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.old.js @@ -875,8 +875,6 @@ function completeWork( } if (enableCache) { - popRootTransition(fiberRoot, renderLanes); - let previousCache: Cache | null = null; if (current !== null) { previousCache = current.memoizedState.cache; @@ -888,6 +886,7 @@ function completeWork( } popCacheProvider(workInProgress, cache); } + popRootTransition(workInProgress, fiberRoot, renderLanes); popHostContainer(workInProgress); popTopLevelLegacyContextObject(workInProgress); resetMutableSourceWorkInProgressVersions(); @@ -1593,11 +1592,10 @@ function completeWork( // Run passive effects to retain/release the cache. workInProgress.flags |= Passive; } - if (current !== null) { - popTransition(workInProgress); - } } + popTransition(workInProgress, current); + return null; } case CacheComponent: { diff --git a/packages/react-reconciler/src/ReactFiberTransition.new.js b/packages/react-reconciler/src/ReactFiberTransition.new.js index dc3c36a918a0a..c855cb3953126 100644 --- a/packages/react-reconciler/src/ReactFiberTransition.new.js +++ b/packages/react-reconciler/src/ReactFiberTransition.new.js @@ -10,11 +10,15 @@ import type {FiberRoot} from './ReactInternalTypes'; import type {Lanes} from './ReactFiberLane.new'; import type {StackCursor} from './ReactFiberStack.new'; import type {Cache, SpawnedCachePool} from './ReactFiberCacheComponent.new'; +import type {Transition} from './ReactFiberTracingMarkerComponent.new'; -import {enableCache} from 'shared/ReactFeatureFlags'; +import {enableCache, enableTransitionTracing} from 'shared/ReactFeatureFlags'; import {isPrimaryRenderer} from './ReactFiberHostConfig'; import {createCursor, push, pop} from './ReactFiberStack.new'; -import {getWorkInProgressRoot} from './ReactFiberWorkLoop.new'; +import { + getWorkInProgressRoot, + getWorkInProgressTransitions, +} from './ReactFiberWorkLoop.new'; import { createCache, retainCache, @@ -25,6 +29,15 @@ import { // used during the previous render by placing it here, on the stack. const resumedCache: StackCursor = createCursor(null); +// During the render/synchronous commit phase, we don't actually process the +// transitions. Therefore, we want to lazily combine transitions. Instead of +// comparing the arrays of transitions when we combine them and storing them +// and filtering out the duplicates, we will instead store the unprocessed transitions +// in an array and actually filter them in the passive phase. +const transitionStack: StackCursor | null> = createCursor( + null, +); + function peekCacheFromPool(): Cache | null { if (!enableCache) { return (null: any); @@ -75,25 +88,31 @@ export function requestCacheFromPool(renderLanes: Lanes): Cache { return freshCache; } -export function pushRootTransition(root: FiberRoot) { - if (enableCache) { - return; +export function pushRootTransition( + workInProgress: Fiber, + root: FiberRoot, + renderLanes: Lanes, +) { + if (enableTransitionTracing) { + const rootTransitions = getWorkInProgressTransitions(); + push(transitionStack, rootTransitions, workInProgress); } - // Note: This function currently does nothing but I'll leave it here for - // code organization purposes in case that changes. } -export function popRootTransition(root: FiberRoot, renderLanes: Lanes) { - if (enableCache) { - return; +export function popRootTransition( + workInProgress: Fiber, + root: FiberRoot, + renderLanes: Lanes, +) { + if (enableTransitionTracing) { + pop(transitionStack, workInProgress); } - // Note: This function currently does nothing but I'll leave it here for - // code organization purposes in case that changes. } export function pushTransition( offscreenWorkInProgress: Fiber, prevCachePool: SpawnedCachePool | null, + newTransitions: Array | null, ): void { if (enableCache) { if (prevCachePool === null) { @@ -102,12 +121,40 @@ export function pushTransition( push(resumedCache, prevCachePool.pool, offscreenWorkInProgress); } } + + if (enableTransitionTracing) { + if (transitionStack.current === null) { + push(transitionStack, newTransitions, offscreenWorkInProgress); + } else if (newTransitions === null) { + push(transitionStack, transitionStack.current, offscreenWorkInProgress); + } else { + push( + transitionStack, + transitionStack.current.concat(newTransitions), + offscreenWorkInProgress, + ); + } + } } -export function popTransition(workInProgress: Fiber) { - if (enableCache) { - pop(resumedCache, workInProgress); +export function popTransition(workInProgress: Fiber, current: Fiber | null) { + if (current !== null) { + if (enableCache) { + pop(resumedCache, workInProgress); + } + + if (enableTransitionTracing) { + pop(transitionStack, workInProgress); + } + } +} + +export function getSuspendedTransitions(): Array | null { + if (!enableTransitionTracing) { + return null; } + + return transitionStack.current; } export function getSuspendedCache(): SpawnedCachePool | null { diff --git a/packages/react-reconciler/src/ReactFiberTransition.old.js b/packages/react-reconciler/src/ReactFiberTransition.old.js index 666e41d5184cc..ed73d4716622b 100644 --- a/packages/react-reconciler/src/ReactFiberTransition.old.js +++ b/packages/react-reconciler/src/ReactFiberTransition.old.js @@ -10,11 +10,15 @@ import type {FiberRoot} from './ReactInternalTypes'; import type {Lanes} from './ReactFiberLane.old'; import type {StackCursor} from './ReactFiberStack.old'; import type {Cache, SpawnedCachePool} from './ReactFiberCacheComponent.old'; +import type {Transition} from './ReactFiberTracingMarkerComponent.old'; -import {enableCache} from 'shared/ReactFeatureFlags'; +import {enableCache, enableTransitionTracing} from 'shared/ReactFeatureFlags'; import {isPrimaryRenderer} from './ReactFiberHostConfig'; import {createCursor, push, pop} from './ReactFiberStack.old'; -import {getWorkInProgressRoot} from './ReactFiberWorkLoop.old'; +import { + getWorkInProgressRoot, + getWorkInProgressTransitions, +} from './ReactFiberWorkLoop.old'; import { createCache, retainCache, @@ -25,6 +29,15 @@ import { // used during the previous render by placing it here, on the stack. const resumedCache: StackCursor = createCursor(null); +// During the render/synchronous commit phase, we don't actually process the +// transitions. Therefore, we want to lazily combine transitions. Instead of +// comparing the arrays of transitions when we combine them and storing them +// and filtering out the duplicates, we will instead store the unprocessed transitions +// in an array and actually filter them in the passive phase. +const transitionStack: StackCursor | null> = createCursor( + null, +); + function peekCacheFromPool(): Cache | null { if (!enableCache) { return (null: any); @@ -75,25 +88,31 @@ export function requestCacheFromPool(renderLanes: Lanes): Cache { return freshCache; } -export function pushRootTransition(root: FiberRoot) { - if (enableCache) { - return; +export function pushRootTransition( + workInProgress: Fiber, + root: FiberRoot, + renderLanes: Lanes, +) { + if (enableTransitionTracing) { + const rootTransitions = getWorkInProgressTransitions(); + push(transitionStack, rootTransitions, workInProgress); } - // Note: This function currently does nothing but I'll leave it here for - // code organization purposes in case that changes. } -export function popRootTransition(root: FiberRoot, renderLanes: Lanes) { - if (enableCache) { - return; +export function popRootTransition( + workInProgress: Fiber, + root: FiberRoot, + renderLanes: Lanes, +) { + if (enableTransitionTracing) { + pop(transitionStack, workInProgress); } - // Note: This function currently does nothing but I'll leave it here for - // code organization purposes in case that changes. } export function pushTransition( offscreenWorkInProgress: Fiber, prevCachePool: SpawnedCachePool | null, + newTransitions: Array | null, ): void { if (enableCache) { if (prevCachePool === null) { @@ -102,12 +121,40 @@ export function pushTransition( push(resumedCache, prevCachePool.pool, offscreenWorkInProgress); } } + + if (enableTransitionTracing) { + if (transitionStack.current === null) { + push(transitionStack, newTransitions, offscreenWorkInProgress); + } else if (newTransitions === null) { + push(transitionStack, transitionStack.current, offscreenWorkInProgress); + } else { + push( + transitionStack, + transitionStack.current.concat(newTransitions), + offscreenWorkInProgress, + ); + } + } } -export function popTransition(workInProgress: Fiber) { - if (enableCache) { - pop(resumedCache, workInProgress); +export function popTransition(workInProgress: Fiber, current: Fiber | null) { + if (current !== null) { + if (enableCache) { + pop(resumedCache, workInProgress); + } + + if (enableTransitionTracing) { + pop(transitionStack, workInProgress); + } + } +} + +export function getSuspendedTransitions(): Array | null { + if (!enableTransitionTracing) { + return null; } + + return transitionStack.current; } export function getSuspendedCache(): SpawnedCachePool | null { diff --git a/packages/react-reconciler/src/ReactFiberUnwindWork.new.js b/packages/react-reconciler/src/ReactFiberUnwindWork.new.js index faac8f38f05fb..eb2ea2286a275 100644 --- a/packages/react-reconciler/src/ReactFiberUnwindWork.new.js +++ b/packages/react-reconciler/src/ReactFiberUnwindWork.new.js @@ -79,13 +79,12 @@ function unwindWork( return null; } case HostRoot: { + const root: FiberRoot = workInProgress.stateNode; if (enableCache) { - const root: FiberRoot = workInProgress.stateNode; - popRootTransition(root, renderLanes); - const cache: Cache = workInProgress.memoizedState.cache; popCacheProvider(workInProgress, cache); } + popRootTransition(workInProgress, root, renderLanes); popHostContainer(workInProgress); popTopLevelLegacyContextObject(workInProgress); resetMutableSourceWorkInProgressVersions(); @@ -153,11 +152,7 @@ function unwindWork( case OffscreenComponent: case LegacyHiddenComponent: popRenderLanes(workInProgress); - if (enableCache) { - if (current !== null) { - popTransition(workInProgress); - } - } + popTransition(workInProgress, current); return null; case CacheComponent: if (enableCache) { @@ -189,13 +184,12 @@ function unwindInterruptedWork( break; } case HostRoot: { + const root: FiberRoot = interruptedWork.stateNode; if (enableCache) { - const root: FiberRoot = interruptedWork.stateNode; - popRootTransition(root, renderLanes); - const cache: Cache = interruptedWork.memoizedState.cache; popCacheProvider(interruptedWork, cache); } + popRootTransition(interruptedWork, root, renderLanes); popHostContainer(interruptedWork); popTopLevelLegacyContextObject(interruptedWork); resetMutableSourceWorkInProgressVersions(); @@ -221,12 +215,7 @@ function unwindInterruptedWork( case OffscreenComponent: case LegacyHiddenComponent: popRenderLanes(interruptedWork); - if (enableCache) { - if (current !== null) { - popTransition(interruptedWork); - } - } - + popTransition(interruptedWork, current); break; case CacheComponent: if (enableCache) { diff --git a/packages/react-reconciler/src/ReactFiberUnwindWork.old.js b/packages/react-reconciler/src/ReactFiberUnwindWork.old.js index 4578134d58d05..1eb4d72d98c98 100644 --- a/packages/react-reconciler/src/ReactFiberUnwindWork.old.js +++ b/packages/react-reconciler/src/ReactFiberUnwindWork.old.js @@ -79,13 +79,12 @@ function unwindWork( return null; } case HostRoot: { + const root: FiberRoot = workInProgress.stateNode; if (enableCache) { - const root: FiberRoot = workInProgress.stateNode; - popRootTransition(root, renderLanes); - const cache: Cache = workInProgress.memoizedState.cache; popCacheProvider(workInProgress, cache); } + popRootTransition(workInProgress, root, renderLanes); popHostContainer(workInProgress); popTopLevelLegacyContextObject(workInProgress); resetMutableSourceWorkInProgressVersions(); @@ -153,11 +152,7 @@ function unwindWork( case OffscreenComponent: case LegacyHiddenComponent: popRenderLanes(workInProgress); - if (enableCache) { - if (current !== null) { - popTransition(workInProgress); - } - } + popTransition(workInProgress, current); return null; case CacheComponent: if (enableCache) { @@ -189,13 +184,12 @@ function unwindInterruptedWork( break; } case HostRoot: { + const root: FiberRoot = interruptedWork.stateNode; if (enableCache) { - const root: FiberRoot = interruptedWork.stateNode; - popRootTransition(root, renderLanes); - const cache: Cache = interruptedWork.memoizedState.cache; popCacheProvider(interruptedWork, cache); } + popRootTransition(interruptedWork, root, renderLanes); popHostContainer(interruptedWork); popTopLevelLegacyContextObject(interruptedWork); resetMutableSourceWorkInProgressVersions(); @@ -221,12 +215,7 @@ function unwindInterruptedWork( case OffscreenComponent: case LegacyHiddenComponent: popRenderLanes(interruptedWork); - if (enableCache) { - if (current !== null) { - popTransition(interruptedWork); - } - } - + popTransition(interruptedWork, current); break; case CacheComponent: if (enableCache) {