Skip to content
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

Entangle overlapping transitions per queue #20670

Merged
merged 1 commit into from
Jan 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions packages/react-reconciler/src/ReactFiberClassComponent.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {

import {
enqueueUpdate,
entangleTransitions,
processUpdateQueue,
checkHasForceUpdateAfterProcessing,
resetHasForceUpdateBeforeProcessing,
Expand Down Expand Up @@ -214,7 +215,10 @@ const classComponentUpdater = {
}

enqueueUpdate(fiber, update, lane);
scheduleUpdateOnFiber(fiber, lane, eventTime);
const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
if (root !== null) {
entangleTransitions(root, fiber, lane);
}

if (__DEV__) {
if (enableDebugTracing) {
Expand Down Expand Up @@ -246,7 +250,10 @@ const classComponentUpdater = {
}

enqueueUpdate(fiber, update, lane);
scheduleUpdateOnFiber(fiber, lane, eventTime);
const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
if (root !== null) {
entangleTransitions(root, fiber, lane);
}

if (__DEV__) {
if (enableDebugTracing) {
Expand Down Expand Up @@ -277,7 +284,10 @@ const classComponentUpdater = {
}

enqueueUpdate(fiber, update, lane);
scheduleUpdateOnFiber(fiber, lane, eventTime);
const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
if (root !== null) {
entangleTransitions(root, fiber, lane);
}

if (__DEV__) {
if (enableDebugTracing) {
Expand Down
16 changes: 13 additions & 3 deletions packages/react-reconciler/src/ReactFiberClassComponent.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {

import {
enqueueUpdate,
entangleTransitions,
processUpdateQueue,
checkHasForceUpdateAfterProcessing,
resetHasForceUpdateBeforeProcessing,
Expand Down Expand Up @@ -214,7 +215,10 @@ const classComponentUpdater = {
}

enqueueUpdate(fiber, update);
scheduleUpdateOnFiber(fiber, lane, eventTime);
const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
if (root !== null) {
entangleTransitions(root, fiber, lane);
}

if (__DEV__) {
if (enableDebugTracing) {
Expand Down Expand Up @@ -246,7 +250,10 @@ const classComponentUpdater = {
}

enqueueUpdate(fiber, update);
scheduleUpdateOnFiber(fiber, lane, eventTime);
const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
if (root !== null) {
entangleTransitions(root, fiber, lane);
}

if (__DEV__) {
if (enableDebugTracing) {
Expand Down Expand Up @@ -277,7 +284,10 @@ const classComponentUpdater = {
}

enqueueUpdate(fiber, update);
scheduleUpdateOnFiber(fiber, lane, eventTime);
const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
if (root !== null) {
entangleTransitions(root, fiber, lane);
}

if (__DEV__) {
if (enableDebugTracing) {
Expand Down
39 changes: 37 additions & 2 deletions packages/react-reconciler/src/ReactFiberHooks.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ import {
isSubsetOfLanes,
mergeLanes,
removeLanes,
intersectLanes,
isTransitionLane,
markRootEntangled,
markRootMutableRead,
getCurrentUpdateLanePriority,
Expand Down Expand Up @@ -104,7 +106,11 @@ import {getIsRendering} from './ReactCurrentFiber';
import {logStateUpdateScheduled} from './DebugTracing';
import {markStateUpdateScheduled} from './SchedulingProfiler';
import {CacheContext} from './ReactFiberCacheComponent.new';
import {createUpdate, enqueueUpdate} from './ReactUpdateQueue.new';
import {
createUpdate,
enqueueUpdate,
entangleTransitions,
} from './ReactUpdateQueue.new';
import {pushInterleavedQueue} from './ReactFiberInterleavedUpdates.new';

const {ReactCurrentDispatcher, ReactCurrentBatchConfig} = ReactSharedInternals;
Expand All @@ -121,6 +127,7 @@ type Update<S, A> = {|
export type UpdateQueue<S, A> = {|
pending: Update<S, A> | null,
interleaved: Update<S, A> | null,
lanes: Lanes,
dispatch: (A => mixed) | null,
lastRenderedReducer: ((S, A) => S) | null,
lastRenderedState: S | null,
Expand Down Expand Up @@ -654,6 +661,7 @@ function mountReducer<S, I, A>(
const queue = (hook.queue = {
pending: null,
interleaved: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: reducer,
lastRenderedState: (initialState: any),
Expand Down Expand Up @@ -811,6 +819,10 @@ function updateReducer<S, I, A>(
markSkippedUpdateLanes(interleavedLane);
interleaved = ((interleaved: any).next: Update<S, A>);
} while (interleaved !== lastInterleaved);
} else if (baseQueue === null) {
// `queue.lanes` is used for entangling transitions. We can set it back to
// zero once the queue is empty.
queue.lanes = NoLanes;
}

const dispatch: Dispatch<A> = (queue.dispatch: any);
Expand Down Expand Up @@ -1102,6 +1114,7 @@ function useMutableSource<Source, Snapshot>(
const newQueue = {
pending: null,
interleaved: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: snapshot,
Expand Down Expand Up @@ -1158,6 +1171,7 @@ function mountState<S>(
const queue = (hook.queue = {
pending: null,
interleaved: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: (initialState: any),
Expand Down Expand Up @@ -1821,6 +1835,9 @@ function refreshCache<T>(fiber: Fiber, seedKey: ?() => T, seedValue: T) {
const lane = requestUpdateLane(provider);
const eventTime = requestEventTime();
const root = scheduleUpdateOnFiber(provider, lane, eventTime);
if (root !== null) {
entangleTransitions(root, fiber, lane);
}

const seededCache = new Map();
if (seedKey !== null && seedKey !== undefined && root !== null) {
Expand Down Expand Up @@ -1960,7 +1977,25 @@ function dispatchAction<S, A>(
warnIfNotCurrentlyActingUpdatesInDev(fiber);
}
}
scheduleUpdateOnFiber(fiber, lane, eventTime);
const root = scheduleUpdateOnFiber(fiber, lane, eventTime);

if (isTransitionLane(lane) && root !== null) {
let queueLanes = queue.lanes;

// If any entangled lanes are no longer pending on the root, then they
// must have finished. We can remove them from the shared queue, which
// represents a superset of the actually pending lanes. In some cases we
// may entangle more than we need to, but that's OK. In fact it's worse if
// we *don't* entangle when we should.
queueLanes = intersectLanes(queueLanes, root.pendingLanes);

// Entangle the new transition lane with the other transition lanes.
const newQueueLanes = mergeLanes(queueLanes, lane);
if (newQueueLanes !== queueLanes) {
queue.lanes = newQueueLanes;
markRootEntangled(root, newQueueLanes);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to make sure I understand the new logic for calling markRootEntangled.

The only other place we do this now (I think) is in mutable source. The trigger in that case is when we detect reading from a source that has pending mutations scheduled.

I think the added calls to markRootEntangled are there to ensure that every transition scheduled for the same state/reducer queue get entangled.

I'm not sure I understand why we need to explicitly remove already finished entangled lanes (things no longer in pendingLanes). I understand why we shouldn't re-entangle them but I'm not sure I get why they'd still be in queue.lanes.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand why we shouldn't re-entangle them but I'm not sure I get why they'd still be in queue.lanes.

Yeah good question. The lanes field is a superset of all the transition lanes that are still pending on that queue. We don't clear it in the commit phase, we only clear it here and if the hook bails out. Neither of those heuristics will account for all cases, there will be some cases where we don't know for sure if a lane is still pending in the queue, but it's good enough for the common case, which is that there are no pending transitions on the root at all.

It's basically the same trick we use for eagerly bailing out of state updates.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An important detail is that this is the "shared" queue, not the per-fiber queue. When we schedule updates on it, we don't know the current state. It's append-only, you can't read from it. Except with tricks like this.

}

if (__DEV__) {
Expand Down
41 changes: 39 additions & 2 deletions packages/react-reconciler/src/ReactFiberHooks.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ import {
isSubsetOfLanes,
mergeLanes,
removeLanes,
intersectLanes,
isTransitionLane,
markRootEntangled,
markRootMutableRead,
getCurrentUpdateLanePriority,
Expand Down Expand Up @@ -103,7 +105,11 @@ import {getIsRendering} from './ReactCurrentFiber';
import {logStateUpdateScheduled} from './DebugTracing';
import {markStateUpdateScheduled} from './SchedulingProfiler';
import {CacheContext} from './ReactFiberCacheComponent.old';
import {createUpdate, enqueueUpdate} from './ReactUpdateQueue.old';
import {
createUpdate,
enqueueUpdate,
entangleTransitions,
} from './ReactUpdateQueue.old';

const {ReactCurrentDispatcher, ReactCurrentBatchConfig} = ReactSharedInternals;

Expand All @@ -118,6 +124,7 @@ type Update<S, A> = {|

type UpdateQueue<S, A> = {|
pending: Update<S, A> | null,
lanes: Lanes,
dispatch: (A => mixed) | null,
lastRenderedReducer: ((S, A) => S) | null,
lastRenderedState: S | null,
Expand Down Expand Up @@ -650,6 +657,7 @@ function mountReducer<S, I, A>(
hook.memoizedState = hook.baseState = initialState;
const queue = (hook.queue = {
pending: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: reducer,
lastRenderedState: (initialState: any),
Expand Down Expand Up @@ -792,6 +800,12 @@ function updateReducer<S, I, A>(
queue.lastRenderedState = newState;
}

if (baseQueue === null) {
// `queue.lanes` is used for entangling transitions. We can set it back to
// zero once the queue is empty.
queue.lanes = NoLanes;
}

const dispatch: Dispatch<A> = (queue.dispatch: any);
return [hook.memoizedState, dispatch];
}
Expand Down Expand Up @@ -1080,6 +1094,7 @@ function useMutableSource<Source, Snapshot>(
// including any interleaving updates that occur.
const newQueue = {
pending: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: snapshot,
Expand Down Expand Up @@ -1135,6 +1150,7 @@ function mountState<S>(
hook.memoizedState = hook.baseState = initialState;
const queue = (hook.queue = {
pending: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: (initialState: any),
Expand Down Expand Up @@ -1798,6 +1814,9 @@ function refreshCache<T>(fiber: Fiber, seedKey: ?() => T, seedValue: T) {
const lane = requestUpdateLane(provider);
const eventTime = requestEventTime();
const root = scheduleUpdateOnFiber(provider, lane, eventTime);
if (root !== null) {
entangleTransitions(root, fiber, lane);
}

const seededCache = new Map();
if (seedKey !== null && seedKey !== undefined && root !== null) {
Expand Down Expand Up @@ -1914,7 +1933,25 @@ function dispatchAction<S, A>(
warnIfNotCurrentlyActingUpdatesInDev(fiber);
}
}
scheduleUpdateOnFiber(fiber, lane, eventTime);
const root = scheduleUpdateOnFiber(fiber, lane, eventTime);

if (isTransitionLane(lane) && root !== null) {
let queueLanes = queue.lanes;

// If any entangled lanes are no longer pending on the root, then they
// must have finished. We can remove them from the shared queue, which
// represents a superset of the actually pending lanes. In some cases we
// may entangle more than we need to, but that's OK. In fact it's worse if
// we *don't* entangle when we should.
queueLanes = intersectLanes(queueLanes, root.pendingLanes);

// Entangle the new transition lane with the other transition lanes.
const newQueueLanes = mergeLanes(queueLanes, lane);
if (newQueueLanes !== queueLanes) {
queue.lanes = newQueueLanes;
markRootEntangled(root, newQueueLanes);
}
}
}

if (__DEV__) {
Expand Down
13 changes: 10 additions & 3 deletions packages/react-reconciler/src/ReactFiberLane.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -310,9 +310,8 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
}

if (enableTransitionEntanglement) {
// We don't need to include higher priority lanes, because in this
// experiment we always unsuspend all transitions whenever we receive
// an update.
// We don't need to do anything extra here, because we apply per-lane
// transition entanglement in the entanglement loop below.
} else {
// If there are higher priority lanes, we'll include them even if they
// are suspended.
Expand Down Expand Up @@ -492,6 +491,10 @@ export function includesOnlyTransitions(lanes: Lanes) {
return (lanes & TransitionLanes) === lanes;
}

export function isTransitionLane(lane: Lane) {
return (lane & TransitionLanes) !== 0;
}

// To ensure consistency across multiple updates in the same event, this should
// be a pure function, so that it always returns the same lane for given inputs.
export function findUpdateLane(
Expand Down Expand Up @@ -634,6 +637,10 @@ export function removeLanes(set: Lanes, subset: Lanes | Lane): Lanes {
return set & ~subset;
}

export function intersectLanes(a: Lanes | Lane, b: Lanes | Lane): Lanes {
return a & b;
}

// Seems redundant, but it changes the type from a single lane (used for
// updates) to a group of lanes (used for flushing work).
export function laneToLanes(lane: Lane): Lanes {
Expand Down
13 changes: 10 additions & 3 deletions packages/react-reconciler/src/ReactFiberLane.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -310,9 +310,8 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
}

if (enableTransitionEntanglement) {
// We don't need to include higher priority lanes, because in this
// experiment we always unsuspend all transitions whenever we receive
// an update.
// We don't need to do anything extra here, because we apply per-lane
// transition entanglement in the entanglement loop below.
} else {
// If there are higher priority lanes, we'll include them even if they
// are suspended.
Expand Down Expand Up @@ -492,6 +491,10 @@ export function includesOnlyTransitions(lanes: Lanes) {
return (lanes & TransitionLanes) === lanes;
}

export function isTransitionLane(lane: Lane) {
return (lane & TransitionLanes) !== 0;
}

// To ensure consistency across multiple updates in the same event, this should
// be a pure function, so that it always returns the same lane for given inputs.
export function findUpdateLane(
Expand Down Expand Up @@ -634,6 +637,10 @@ export function removeLanes(set: Lanes, subset: Lanes | Lane): Lanes {
return set & ~subset;
}

export function intersectLanes(a: Lanes | Lane, b: Lanes | Lane): Lanes {
return a & b;
}

// Seems redundant, but it changes the type from a single lane (used for
// updates) to a group of lanes (used for flushing work).
export function laneToLanes(lane: Lane): Lanes {
Expand Down
11 changes: 9 additions & 2 deletions packages/react-reconciler/src/ReactFiberReconciler.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,11 @@ import {
IsThisRendererActing,
act,
} from './ReactFiberWorkLoop.new';
import {createUpdate, enqueueUpdate} from './ReactUpdateQueue.new';
import {
createUpdate,
enqueueUpdate,
entangleTransitions,
} from './ReactUpdateQueue.new';
import {
isRendering as ReactCurrentFiberIsRendering,
current as ReactCurrentFiberCurrent,
Expand Down Expand Up @@ -315,7 +319,10 @@ export function updateContainer(
}

enqueueUpdate(current, update, lane);
scheduleUpdateOnFiber(current, lane, eventTime);
const root = scheduleUpdateOnFiber(current, lane, eventTime);
if (root !== null) {
entangleTransitions(root, current, lane);
}

return lane;
}
Expand Down
Loading