Skip to content

Commit

Permalink
Track the key path difference between right before the first array (#…
Browse files Browse the repository at this point in the history
…27360)

There's a subtle difference if you suspend before the first array or
after. In Fiber, we don't deal with this because we just suspend the
parent and replay it if lazy() or Usable are used in its child slots. In
Fizz we try to optimize this a bit more and enable resuming inside the
component.

Semantically, it's different if you suspend/postpone before the first
child array or inside that child array. Because when you resume the
inner result might be another array and either that's part of the parent
path or part of the inner slot.

There might be more clever way of structuring this but I just use -1 to
indicate that we're not yet inside the array and is in the root child
position. If that renders an element, then that's just the same as the 0
slot.

We need to also encode this in the resuming. I called that resuming the
element or resuming the slot.

DiffTrain build for [7a3cb8f](7a3cb8f)
  • Loading branch information
sebmarkbage committed Sep 12, 2023
1 parent 998c193 commit f527fd1
Show file tree
Hide file tree
Showing 8 changed files with 236 additions and 374 deletions.
2 changes: 1 addition & 1 deletion compiled/facebook-www/REVISION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
bbc8530ed7a67859583a7c990ac51cd39c7746e5
7a3cb8f9cf43591afc74722ece9e3216ccc98128
83 changes: 27 additions & 56 deletions compiled/facebook-www/ReactDOMServer-dev.classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ if (__DEV__) {
var React = require("react");
var ReactDOM = require("react-dom");

var ReactVersion = "18.3.0-www-classic-51adb8d6";
var ReactVersion = "18.3.0-www-classic-d167ae1b";

// This refers to a WWW module.
var warningWWW = require("warning");
Expand Down Expand Up @@ -9990,7 +9990,7 @@ function renderSuspenseBoundary(request, task, props) {

try {
// We use the safe form because we don't handle suspending here. Only error handling.
renderNode(request, task, content, 0);
renderNode(request, task, content, -1);
pushSegmentFinale(
contentRootSegment.chunks,
request.renderState,
Expand Down Expand Up @@ -10077,7 +10077,7 @@ function renderHostElement(request, task, type, props) {
task.formatContext = getChildFormatContext(prevContext, type, props); // We use the non-destructive form because if something suspends, we still
// need to pop back up and finish this subtree of HTML.

renderNode(request, task, children, 0); // We expect that errors will fatal the whole task and that we don't need
renderNode(request, task, children, -1); // We expect that errors will fatal the whole task and that we don't need
// the correct context. Therefore this is not in a finally.

task.formatContext = prevContext;
Expand Down Expand Up @@ -10139,13 +10139,13 @@ function finishClassComponent(request, task, instance, Component, props) {
childContextTypes
);
task.legacyContext = mergedContext;
renderNodeDestructive(request, task, null, nextChildren, 0);
renderNodeDestructive(request, task, null, nextChildren, -1);
task.legacyContext = previousContext;
return;
}
}

renderNodeDestructive(request, task, null, nextChildren, 0);
renderNodeDestructive(request, task, null, nextChildren, -1);
}

function renderClassComponent(request, task, Component, props) {
Expand Down Expand Up @@ -10302,20 +10302,20 @@ function finishFunctionComponent(
// suspends or errors, we'll use the non-destructive render path.

task.treeContext = pushTreeContext(prevTreeContext, totalChildren, index);
renderNode(request, task, children, 0); // Like the other contexts, this does not need to be in a finally block
renderNode(request, task, children, -1); // Like the other contexts, this does not need to be in a finally block
// because renderNode takes care of unwinding the stack.

task.treeContext = prevTreeContext;
} else if (didEmitFormStateMarkers) {
// If there were formState hooks, we must use the non-destructive path
// because this component is not a pure indirection; we emitted markers
// to the stream.
renderNode(request, task, children, 0);
renderNode(request, task, children, -1);
} else {
// We're now successfully past this task, and we haven't modified the
// context stack. We don't have to pop back to the previous task every
// again, so we can use the destructive recursive form.
renderNodeDestructive(request, task, null, children, 0);
renderNodeDestructive(request, task, null, children, -1);
}
}

Expand Down Expand Up @@ -10473,7 +10473,7 @@ function renderContextConsumer(request, task, context, props) {

var newValue = readContext$1(context);
var newChildren = render(newValue);
renderNodeDestructive(request, task, null, newChildren, 0);
renderNodeDestructive(request, task, null, newChildren, -1);
}

function renderContextProvider(request, task, type, props) {
Expand All @@ -10487,7 +10487,7 @@ function renderContextProvider(request, task, type, props) {
}

task.context = pushProvider(context, value);
renderNodeDestructive(request, task, null, children, 0);
renderNodeDestructive(request, task, null, children, -1);
task.context = popProvider(context);

{
Expand Down Expand Up @@ -10530,7 +10530,7 @@ function renderOffscreen(request, task, props) {
else {
// A visible Offscreen boundary is treated exactly like a fragment: a
// pure indirection.
renderNodeDestructive(request, task, null, props.children, 0);
renderNodeDestructive(request, task, null, props.children, -1);
}
}

Expand Down Expand Up @@ -10571,7 +10571,7 @@ function renderElement(request, task, prevThenableState, type, props, ref) {
case REACT_STRICT_MODE_TYPE:
case REACT_PROFILER_TYPE:
case REACT_FRAGMENT_TYPE: {
renderNodeDestructive(request, task, null, props.children, 0);
renderNodeDestructive(request, task, null, props.children, -1);
return;
}

Expand All @@ -10583,14 +10583,14 @@ function renderElement(request, task, prevThenableState, type, props, ref) {
case REACT_SUSPENSE_LIST_TYPE: {
pushBuiltInComponentStackInDEV(task, "SuspenseList"); // TODO: SuspenseList should control the boundaries.

renderNodeDestructive(request, task, null, props.children, 0);
renderNodeDestructive(request, task, null, props.children, -1);
popComponentStackInDEV(task);
return;
}

case REACT_SCOPE_TYPE: {
{
renderNodeDestructive(request, task, null, props.children, 0);
renderNodeDestructive(request, task, null, props.children, -1);
return;
}
}
Expand Down Expand Up @@ -10748,7 +10748,11 @@ function renderNodeDestructiveImpl(
var ref = element.ref;
var name = getComponentNameFromType(type);
var prevKeyPath = task.keyPath;
task.keyPath = [task.keyPath, name, key == null ? childIndex : key];
task.keyPath = [
task.keyPath,
name,
key == null ? (childIndex === -1 ? 0 : childIndex) : key
];
renderElement(request, task, prevThenableState, type, props, ref);
task.keyPath = prevKeyPath;
return;
Expand Down Expand Up @@ -10910,59 +10914,26 @@ function renderNodeDestructiveImpl(
}

function renderChildrenArray(request, task, children, childIndex) {
var prevKeyPath = task.keyPath;

if (childIndex !== -1) {
task.keyPath = [task.keyPath, "", childIndex];
}

var prevTreeContext = task.treeContext;
var totalChildren = children.length;

for (var i = 0; i < totalChildren; i++) {
var node = children[i];
task.treeContext = pushTreeContext(prevTreeContext, totalChildren, i); // Nested arrays behave like a "fragment node" which is keyed.
// Therefore we need to add the current index as a parent key.
// We first check if the nested nodes are arrays or iterables.

if (isArray(node)) {
var prevKeyPath = task.keyPath;
task.keyPath = [task.keyPath, "", childIndex];
renderChildrenArray(request, task, node, i);
task.keyPath = prevKeyPath;
continue;
}

var iteratorFn = getIteratorFn(node);

if (iteratorFn) {
{
validateIterable(node, iteratorFn);
}

var iterator = iteratorFn.call(node);

if (iterator) {
var step = iterator.next();

if (!step.done) {
var _prevKeyPath = task.keyPath;
task.keyPath = [task.keyPath, "", childIndex];
var nestedChildren = [];

do {
nestedChildren.push(step.value);
step = iterator.next();
} while (!step.done);

renderChildrenArray(request, task, nestedChildren, i);
task.keyPath = _prevKeyPath;
}

continue;
}
} // We need to use the non-destructive form so that we can safely pop back
task.treeContext = pushTreeContext(prevTreeContext, totalChildren, i); // We need to use the non-destructive form so that we can safely pop back
// up and render the sibling if something suspends.

renderNode(request, task, node, i);
} // Because this context is always set right before rendering every child, we
// only need to reset it to the previous value at the very end.

task.treeContext = prevTreeContext;
task.keyPath = prevKeyPath;
}

function spawnNewSuspendedTask(request, task, thenableState, x) {
Expand Down
81 changes: 26 additions & 55 deletions compiled/facebook-www/ReactDOMServer-dev.modern.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ if (__DEV__) {
var React = require("react");
var ReactDOM = require("react-dom");

var ReactVersion = "18.3.0-www-modern-78c6f394";
var ReactVersion = "18.3.0-www-modern-2efbee1a";

// This refers to a WWW module.
var warningWWW = require("warning");
Expand Down Expand Up @@ -9749,7 +9749,7 @@ function renderSuspenseBoundary(request, task, props) {

try {
// We use the safe form because we don't handle suspending here. Only error handling.
renderNode(request, task, content, 0);
renderNode(request, task, content, -1);
pushSegmentFinale(
contentRootSegment.chunks,
request.renderState,
Expand Down Expand Up @@ -9836,7 +9836,7 @@ function renderHostElement(request, task, type, props) {
task.formatContext = getChildFormatContext(prevContext, type, props); // We use the non-destructive form because if something suspends, we still
// need to pop back up and finish this subtree of HTML.

renderNode(request, task, children, 0); // We expect that errors will fatal the whole task and that we don't need
renderNode(request, task, children, -1); // We expect that errors will fatal the whole task and that we don't need
// the correct context. Therefore this is not in a finally.

task.formatContext = prevContext;
Expand Down Expand Up @@ -9886,7 +9886,7 @@ function finishClassComponent(request, task, instance, Component, props) {
}
}

renderNodeDestructive(request, task, null, nextChildren, 0);
renderNodeDestructive(request, task, null, nextChildren, -1);
}

function renderClassComponent(request, task, Component, props) {
Expand Down Expand Up @@ -10050,20 +10050,20 @@ function finishFunctionComponent(
// suspends or errors, we'll use the non-destructive render path.

task.treeContext = pushTreeContext(prevTreeContext, totalChildren, index);
renderNode(request, task, children, 0); // Like the other contexts, this does not need to be in a finally block
renderNode(request, task, children, -1); // Like the other contexts, this does not need to be in a finally block
// because renderNode takes care of unwinding the stack.

task.treeContext = prevTreeContext;
} else if (didEmitFormStateMarkers) {
// If there were formState hooks, we must use the non-destructive path
// because this component is not a pure indirection; we emitted markers
// to the stream.
renderNode(request, task, children, 0);
renderNode(request, task, children, -1);
} else {
// We're now successfully past this task, and we haven't modified the
// context stack. We don't have to pop back to the previous task every
// again, so we can use the destructive recursive form.
renderNodeDestructive(request, task, null, children, 0);
renderNodeDestructive(request, task, null, children, -1);
}
}

Expand Down Expand Up @@ -10221,7 +10221,7 @@ function renderContextConsumer(request, task, context, props) {

var newValue = readContext$1(context);
var newChildren = render(newValue);
renderNodeDestructive(request, task, null, newChildren, 0);
renderNodeDestructive(request, task, null, newChildren, -1);
}

function renderContextProvider(request, task, type, props) {
Expand All @@ -10235,7 +10235,7 @@ function renderContextProvider(request, task, type, props) {
}

task.context = pushProvider(context, value);
renderNodeDestructive(request, task, null, children, 0);
renderNodeDestructive(request, task, null, children, -1);
task.context = popProvider(context);

{
Expand Down Expand Up @@ -10278,7 +10278,7 @@ function renderOffscreen(request, task, props) {
else {
// A visible Offscreen boundary is treated exactly like a fragment: a
// pure indirection.
renderNodeDestructive(request, task, null, props.children, 0);
renderNodeDestructive(request, task, null, props.children, -1);
}
}

Expand Down Expand Up @@ -10319,7 +10319,7 @@ function renderElement(request, task, prevThenableState, type, props, ref) {
case REACT_STRICT_MODE_TYPE:
case REACT_PROFILER_TYPE:
case REACT_FRAGMENT_TYPE: {
renderNodeDestructive(request, task, null, props.children, 0);
renderNodeDestructive(request, task, null, props.children, -1);
return;
}

Expand All @@ -10331,14 +10331,14 @@ function renderElement(request, task, prevThenableState, type, props, ref) {
case REACT_SUSPENSE_LIST_TYPE: {
pushBuiltInComponentStackInDEV(task, "SuspenseList"); // TODO: SuspenseList should control the boundaries.

renderNodeDestructive(request, task, null, props.children, 0);
renderNodeDestructive(request, task, null, props.children, -1);
popComponentStackInDEV(task);
return;
}

case REACT_SCOPE_TYPE: {
{
renderNodeDestructive(request, task, null, props.children, 0);
renderNodeDestructive(request, task, null, props.children, -1);
return;
}
}
Expand Down Expand Up @@ -10496,7 +10496,11 @@ function renderNodeDestructiveImpl(
var ref = element.ref;
var name = getComponentNameFromType(type);
var prevKeyPath = task.keyPath;
task.keyPath = [task.keyPath, name, key == null ? childIndex : key];
task.keyPath = [
task.keyPath,
name,
key == null ? (childIndex === -1 ? 0 : childIndex) : key
];
renderElement(request, task, prevThenableState, type, props, ref);
task.keyPath = prevKeyPath;
return;
Expand Down Expand Up @@ -10658,59 +10662,26 @@ function renderNodeDestructiveImpl(
}

function renderChildrenArray(request, task, children, childIndex) {
var prevKeyPath = task.keyPath;

if (childIndex !== -1) {
task.keyPath = [task.keyPath, "", childIndex];
}

var prevTreeContext = task.treeContext;
var totalChildren = children.length;

for (var i = 0; i < totalChildren; i++) {
var node = children[i];
task.treeContext = pushTreeContext(prevTreeContext, totalChildren, i); // Nested arrays behave like a "fragment node" which is keyed.
// Therefore we need to add the current index as a parent key.
// We first check if the nested nodes are arrays or iterables.

if (isArray(node)) {
var prevKeyPath = task.keyPath;
task.keyPath = [task.keyPath, "", childIndex];
renderChildrenArray(request, task, node, i);
task.keyPath = prevKeyPath;
continue;
}

var iteratorFn = getIteratorFn(node);

if (iteratorFn) {
{
validateIterable(node, iteratorFn);
}

var iterator = iteratorFn.call(node);

if (iterator) {
var step = iterator.next();

if (!step.done) {
var _prevKeyPath = task.keyPath;
task.keyPath = [task.keyPath, "", childIndex];
var nestedChildren = [];

do {
nestedChildren.push(step.value);
step = iterator.next();
} while (!step.done);

renderChildrenArray(request, task, nestedChildren, i);
task.keyPath = _prevKeyPath;
}

continue;
}
} // We need to use the non-destructive form so that we can safely pop back
task.treeContext = pushTreeContext(prevTreeContext, totalChildren, i); // We need to use the non-destructive form so that we can safely pop back
// up and render the sibling if something suspends.

renderNode(request, task, node, i);
} // Because this context is always set right before rendering every child, we
// only need to reset it to the previous value at the very end.

task.treeContext = prevTreeContext;
task.keyPath = prevKeyPath;
}

function spawnNewSuspendedTask(request, task, thenableState, x) {
Expand Down
Loading

0 comments on commit f527fd1

Please sign in to comment.