Skip to content

Commit b0726e9

Browse files
authored
Support sharing context objects between concurrent renderers (#12779)
* Support concurrent primary and secondary renderers. As a workaround to support multiple concurrent renderers, we categorize some renderers as primary and others as secondary. We only expect there to be two concurrent renderers at most: React Native (primary) and Fabric (secondary); React DOM (primary) and React ART (secondary). Secondary renderers store their context values on separate fields. * Add back concurrent renderer warning Only warn for two concurrent primary or two concurrent secondary renderers. * Change "_secondary" suffix to "2" #EveryBitCounts
1 parent 6565795 commit b0726e9

File tree

13 files changed

+146
-24
lines changed

13 files changed

+146
-24
lines changed

packages/react-art/src/ReactART.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,9 @@ const ARTRenderer = ReactFiberReconciler({
478478

479479
now: ReactScheduler.now,
480480

481+
// The ART renderer is secondary to the React DOM renderer.
482+
isPrimaryRenderer: false,
483+
481484
mutation: {
482485
appendChild(parentInstance, child) {
483486
if (child.parentNode === parentInstance) {

packages/react-art/src/__tests__/ReactART-test.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,60 @@ describe('ReactART', () => {
339339
doClick(instance);
340340
expect(onClick2).toBeCalled();
341341
});
342+
343+
it('can concurrently render with a "primary" renderer while sharing context', () => {
344+
const CurrentRendererContext = React.createContext(null);
345+
346+
function Yield(props) {
347+
testRenderer.unstable_yield(props.value);
348+
return null;
349+
}
350+
351+
let ops = [];
352+
function LogCurrentRenderer() {
353+
return (
354+
<CurrentRendererContext.Consumer>
355+
{currentRenderer => {
356+
ops.push(currentRenderer);
357+
return null;
358+
}}
359+
</CurrentRendererContext.Consumer>
360+
);
361+
}
362+
363+
// Using test renderer instead of the DOM renderer here because async
364+
// testing APIs for the DOM renderer don't exist.
365+
const testRenderer = renderer.create(
366+
<CurrentRendererContext.Provider value="Test">
367+
<Yield value="A" />
368+
<Yield value="B" />
369+
<LogCurrentRenderer />
370+
<Yield value="C" />
371+
</CurrentRendererContext.Provider>,
372+
{
373+
unstable_isAsync: true,
374+
},
375+
);
376+
377+
testRenderer.unstable_flushThrough(['A']);
378+
379+
ReactDOM.render(
380+
<Surface>
381+
<LogCurrentRenderer />
382+
<CurrentRendererContext.Provider value="ART">
383+
<LogCurrentRenderer />
384+
</CurrentRendererContext.Provider>
385+
</Surface>,
386+
container,
387+
);
388+
389+
expect(ops).toEqual([null, 'ART']);
390+
391+
ops = [];
392+
expect(testRenderer.unstable_flushAll()).toEqual(['B', 'C']);
393+
394+
expect(ops).toEqual(['Test']);
395+
});
342396
});
343397

344398
describe('ReactARTComponents', () => {

packages/react-dom/src/client/ReactDOM.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,8 @@ const DOMRenderer = ReactFiberReconciler({
690690

691691
now: ReactScheduler.now,
692692

693+
isPrimaryRenderer: true,
694+
693695
mutation: {
694696
commitMount(
695697
domElement: Instance,

packages/react-native-renderer/src/ReactFabricRenderer.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,9 @@ const ReactFabricRenderer = ReactFiberReconciler({
217217

218218
now: ReactNativeFrameScheduling.now,
219219

220+
// The Fabric renderer is secondary to the existing React Native renderer.
221+
isPrimaryRenderer: false,
222+
220223
prepareForCommit(): void {
221224
// Noop
222225
},

packages/react-native-renderer/src/ReactNativeFiberRenderer.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,8 @@ const NativeRenderer = ReactFiberReconciler({
169169

170170
now: ReactNativeFrameScheduling.now,
171171

172+
isPrimaryRenderer: true,
173+
172174
prepareForCommit(): void {
173175
// Noop
174176
},

packages/react-noop-renderer/src/ReactNoop.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,8 @@ let SharedHostConfig = {
185185
now(): number {
186186
return elapsedTimeInMs;
187187
},
188+
189+
isPrimaryRenderer: true,
188190
};
189191

190192
const NoopRenderer = ReactFiberReconciler({

packages/react-reconciler/src/ReactFiberBeginWork.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,11 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
104104

105105
const {pushHostContext, pushHostContainer} = hostContext;
106106

107-
const {pushProvider} = newContext;
107+
const {
108+
pushProvider,
109+
getContextCurrentValue,
110+
getContextChangedBits,
111+
} = newContext;
108112

109113
const {
110114
markActualRenderTimeStarted,
@@ -1048,8 +1052,8 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
10481052
const newProps = workInProgress.pendingProps;
10491053
const oldProps = workInProgress.memoizedProps;
10501054

1051-
const newValue = context._currentValue;
1052-
const changedBits = context._changedBits;
1055+
const newValue = getContextCurrentValue(context);
1056+
const changedBits = getContextChangedBits(context);
10531057

10541058
if (hasLegacyContextChanged()) {
10551059
// Normally we can bail out on props equality but if context has changed

packages/react-reconciler/src/ReactFiberNewContext.js

Lines changed: 54 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,16 @@ import type {Fiber} from './ReactFiber';
1111
import type {ReactContext} from 'shared/ReactTypes';
1212
import type {StackCursor, Stack} from './ReactFiberStack';
1313

14-
import warning from 'fbjs/lib/warning';
15-
1614
export type NewContext = {
1715
pushProvider(providerFiber: Fiber): void,
1816
popProvider(providerFiber: Fiber): void,
17+
getContextCurrentValue(context: ReactContext<any>): any,
18+
getContextChangedBits(context: ReactContext<any>): number,
1919
};
2020

21-
export default function(stack: Stack) {
21+
import warning from 'fbjs/lib/warning';
22+
23+
export default function(stack: Stack, isPrimaryRenderer: boolean) {
2224
const {createCursor, push, pop} = stack;
2325

2426
const providerCursor: StackCursor<Fiber | null> = createCursor(null);
@@ -34,21 +36,38 @@ export default function(stack: Stack) {
3436
function pushProvider(providerFiber: Fiber): void {
3537
const context: ReactContext<any> = providerFiber.type._context;
3638

37-
push(changedBitsCursor, context._changedBits, providerFiber);
38-
push(valueCursor, context._currentValue, providerFiber);
39-
push(providerCursor, providerFiber, providerFiber);
40-
41-
context._currentValue = providerFiber.pendingProps.value;
42-
context._changedBits = providerFiber.stateNode;
43-
44-
if (__DEV__) {
45-
warning(
46-
context._currentRenderer === null ||
47-
context._currentRenderer === rendererSigil,
48-
'Detected multiple renderers concurrently rendering the ' +
49-
'same context provider. This is currently unsupported.',
50-
);
51-
context._currentRenderer = rendererSigil;
39+
if (isPrimaryRenderer) {
40+
push(changedBitsCursor, context._changedBits, providerFiber);
41+
push(valueCursor, context._currentValue, providerFiber);
42+
push(providerCursor, providerFiber, providerFiber);
43+
44+
context._currentValue = providerFiber.pendingProps.value;
45+
context._changedBits = providerFiber.stateNode;
46+
if (__DEV__) {
47+
warning(
48+
context._currentRenderer === null ||
49+
context._currentRenderer === rendererSigil,
50+
'Detected multiple renderers concurrently rendering the ' +
51+
'same context provider. This is currently unsupported.',
52+
);
53+
context._currentRenderer = rendererSigil;
54+
}
55+
} else {
56+
push(changedBitsCursor, context._changedBits2, providerFiber);
57+
push(valueCursor, context._currentValue2, providerFiber);
58+
push(providerCursor, providerFiber, providerFiber);
59+
60+
context._currentValue2 = providerFiber.pendingProps.value;
61+
context._changedBits2 = providerFiber.stateNode;
62+
if (__DEV__) {
63+
warning(
64+
context._currentRenderer2 === null ||
65+
context._currentRenderer2 === rendererSigil,
66+
'Detected multiple renderers concurrently rendering the ' +
67+
'same context provider. This is currently unsupported.',
68+
);
69+
context._currentRenderer2 = rendererSigil;
70+
}
5271
}
5372
}
5473

@@ -61,12 +80,27 @@ export default function(stack: Stack) {
6180
pop(changedBitsCursor, providerFiber);
6281

6382
const context: ReactContext<any> = providerFiber.type._context;
64-
context._currentValue = currentValue;
65-
context._changedBits = changedBits;
83+
if (isPrimaryRenderer) {
84+
context._currentValue = currentValue;
85+
context._changedBits = changedBits;
86+
} else {
87+
context._currentValue2 = currentValue;
88+
context._changedBits2 = changedBits;
89+
}
90+
}
91+
92+
function getContextCurrentValue(context: ReactContext<any>): any {
93+
return isPrimaryRenderer ? context._currentValue : context._currentValue2;
94+
}
95+
96+
function getContextChangedBits(context: ReactContext<any>): number {
97+
return isPrimaryRenderer ? context._changedBits : context._changedBits2;
6698
}
6799

68100
return {
69101
pushProvider,
70102
popProvider,
103+
getContextCurrentValue,
104+
getContextChangedBits,
71105
};
72106
}

packages/react-reconciler/src/ReactFiberReconciler.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,11 @@ export type HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL> = {
9595

9696
now(): number,
9797

98+
// Temporary workaround for scenario where multiple renderers concurrently
99+
// render using the same context objects. E.g. React DOM and React ART on the
100+
// same page. DOM is the primary renderer; ART is the secondary renderer.
101+
isPrimaryRenderer: boolean,
102+
98103
+hydration?: HydrationHostConfig<T, P, I, TI, HI, C, CX, PL>,
99104

100105
+mutation?: MutableUpdatesHostConfig<T, P, I, TI, C, PL>,

packages/react-reconciler/src/ReactFiberScheduler.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
181181
const stack = ReactFiberStack();
182182
const hostContext = ReactFiberHostContext(config, stack);
183183
const legacyContext = ReactFiberLegacyContext(stack);
184-
const newContext = ReactFiberNewContext(stack);
184+
const newContext = ReactFiberNewContext(stack, config.isPrimaryRenderer);
185185
const profilerTimer = createProfilerTimer(now);
186186
const {popHostContext, popHostContainer} = hostContext;
187187
const {

0 commit comments

Comments
 (0)