Skip to content

Commit e5a35f4

Browse files
authored
Document RenderObject._relayoutBoundary and its invariant; small refactors (flutter#150527)
Fixes part of flutter#150524. Recently I was studying RenderObject and related code to understand how the rendering library tracks which objects need layout, and in particular just how the concept of "relayout boundary" works. The [RenderObject._relayoutBoundary] property itself had no docs, and there were some other areas that I felt could be clearer; so here's a PR where I add those docs. In addition to docs, this makes some small pure refactors. Mostly those are in order to make clear docs easier to write. One rename is to fix a lone straggler from flutter#4425 / flutter#4483. One key thing I learned, which this documents, is an invariant that this code maintains on [_relayoutBoundary]. With that invariant in mind, I found it's possible to simplify that bookkeeping, and to get a measurable performance improvement as a bonus. To keep things simple, though, I'll leave those for follow-up PRs. For ease of review, the PR is broken into several commits each with their own commit message.
1 parent a4ce902 commit e5a35f4

File tree

1 file changed

+55
-23
lines changed

1 file changed

+55
-23
lines changed

packages/flutter/lib/src/rendering/object.dart

Lines changed: 55 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1853,7 +1853,7 @@ abstract class RenderObject with DiagnosticableTreeMixin implements HitTestTarge
18531853
assert(child._parent == this);
18541854
assert(child.attached == attached);
18551855
assert(child.parentData != null);
1856-
child._cleanRelayoutBoundary();
1856+
_cleanChildRelayoutBoundary(child);
18571857
child.parentData!.detach();
18581858
child.parentData = null;
18591859
child._parent = null;
@@ -2185,6 +2185,32 @@ abstract class RenderObject with DiagnosticableTreeMixin implements HitTestTarge
21852185
}
21862186
bool _needsLayout = true;
21872187

2188+
/// The nearest relayout boundary enclosing this render object, if known.
2189+
///
2190+
/// When a render object is marked as needing layout, its parent may
2191+
/// as a result also need to be marked as needing layout.
2192+
/// For details, see [markNeedsLayout].
2193+
/// A render object where relayout does not require relayout of the parent
2194+
/// (because its size cannot change on relayout, or because
2195+
/// its parent does not use the child's size for its own layout)
2196+
/// is a "relayout boundary".
2197+
///
2198+
/// This property is set in [layout], and consulted by [markNeedsLayout] in
2199+
/// deciding whether to recursively mark the parent as also needing layout.
2200+
///
2201+
/// This property is initially null, and becomes null again if this
2202+
/// render object is removed from the tree (with [dropChild]);
2203+
/// it remains null until the first layout of this render object
2204+
/// after it was most recently added to the tree.
2205+
/// This property can also be null while an ancestor in the tree is
2206+
/// currently doing layout, until this render object itself does layout.
2207+
///
2208+
/// When not null, the relayout boundary is either this render object itself
2209+
/// or one of its ancestors, and all the render objects in the ancestry chain
2210+
/// up through that ancestor have the same [_relayoutBoundary].
2211+
/// Equivalently: when not null, the relayout boundary is either this render
2212+
/// object itself or the same as that of its parent. (So [_relayoutBoundary]
2213+
/// is one of `null`, `this`, or `parent!._relayoutBoundary!`.)
21882214
RenderObject? _relayoutBoundary;
21892215

21902216
/// Whether [invokeLayoutCallback] for this render object is currently running.
@@ -2222,7 +2248,7 @@ abstract class RenderObject with DiagnosticableTreeMixin implements HitTestTarge
22222248
/// (where it will always be false).
22232249
static bool debugCheckingIntrinsics = false;
22242250

2225-
bool _debugSubtreeRelayoutRootAlreadyMarkedNeedsLayout() {
2251+
bool _debugRelayoutBoundaryAlreadyMarkedNeedsLayout() {
22262252
if (_relayoutBoundary == null) {
22272253
// We don't know where our relayout boundary is yet.
22282254
return true;
@@ -2281,7 +2307,7 @@ abstract class RenderObject with DiagnosticableTreeMixin implements HitTestTarge
22812307
void markNeedsLayout() {
22822308
assert(_debugCanPerformMutations);
22832309
if (_needsLayout) {
2284-
assert(_debugSubtreeRelayoutRootAlreadyMarkedNeedsLayout());
2310+
assert(_debugRelayoutBoundaryAlreadyMarkedNeedsLayout());
22852311
return;
22862312
}
22872313
if (_relayoutBoundary == null) {
@@ -2346,32 +2372,36 @@ abstract class RenderObject with DiagnosticableTreeMixin implements HitTestTarge
23462372
markParentNeedsLayout();
23472373
}
23482374

2349-
void _cleanRelayoutBoundary() {
2350-
if (_relayoutBoundary != this) {
2351-
_relayoutBoundary = null;
2352-
visitChildren(_cleanChildRelayoutBoundary);
2375+
/// Set [_relayoutBoundary] to null throughout this render object's subtree,
2376+
/// stopping at relayout boundaries.
2377+
// This is a static method to reduce closure allocation with visitChildren.
2378+
static void _cleanChildRelayoutBoundary(RenderObject child) {
2379+
if (child._relayoutBoundary != child) {
2380+
child.visitChildren(_cleanChildRelayoutBoundary);
2381+
child._relayoutBoundary = null;
23532382
}
23542383
}
23552384

2356-
void _propagateRelayoutBoundary() {
2357-
if (_relayoutBoundary == this) {
2385+
// This is a static method to reduce closure allocation with visitChildren.
2386+
static void _propagateRelayoutBoundaryToChild(RenderObject child) {
2387+
if (child._relayoutBoundary == child) {
23582388
return;
23592389
}
2360-
final RenderObject? parentRelayoutBoundary = parent?._relayoutBoundary;
2390+
final RenderObject? parentRelayoutBoundary = child.parent?._relayoutBoundary;
23612391
assert(parentRelayoutBoundary != null);
2362-
if (parentRelayoutBoundary != _relayoutBoundary) {
2363-
_relayoutBoundary = parentRelayoutBoundary;
2364-
visitChildren(_propagateRelayoutBoundaryToChild);
2365-
}
2392+
assert(parentRelayoutBoundary != child._relayoutBoundary);
2393+
child._setRelayoutBoundary(parentRelayoutBoundary!);
23662394
}
23672395

2368-
// Reduces closure allocation for visitChildren use cases.
2369-
static void _cleanChildRelayoutBoundary(RenderObject child) {
2370-
child._cleanRelayoutBoundary();
2371-
}
2372-
2373-
static void _propagateRelayoutBoundaryToChild(RenderObject child) {
2374-
child._propagateRelayoutBoundary();
2396+
/// Set [_relayoutBoundary] to [value] throughout this render object's
2397+
/// subtree, including this render object but stopping at relayout boundaries
2398+
/// thereafter.
2399+
void _setRelayoutBoundary(RenderObject value) {
2400+
assert(value != _relayoutBoundary);
2401+
// This may temporarily break the _relayoutBoundary invariant at children;
2402+
// the visitChildren restores the invariant.
2403+
_relayoutBoundary = value;
2404+
visitChildren(_propagateRelayoutBoundaryToChild);
23752405
}
23762406

23772407
/// Bootstrap the rendering pipeline by scheduling the very first layout.
@@ -2396,6 +2426,7 @@ abstract class RenderObject with DiagnosticableTreeMixin implements HitTestTarge
23962426

23972427
@pragma('vm:notify-debugger-on-exception')
23982428
void _layoutWithoutResize() {
2429+
assert(_needsLayout);
23992430
assert(_relayoutBoundary == this);
24002431
RenderObject? debugPreviousActiveLayout;
24012432
assert(!_debugMutationsLocked);
@@ -2520,8 +2551,7 @@ abstract class RenderObject with DiagnosticableTreeMixin implements HitTestTarge
25202551
}());
25212552

25222553
if (relayoutBoundary != _relayoutBoundary) {
2523-
_relayoutBoundary = relayoutBoundary;
2524-
visitChildren(_propagateRelayoutBoundaryToChild);
2554+
_setRelayoutBoundary(relayoutBoundary);
25252555
}
25262556

25272557
if (!kReleaseMode && debugProfileLayoutsEnabled) {
@@ -2530,13 +2560,15 @@ abstract class RenderObject with DiagnosticableTreeMixin implements HitTestTarge
25302560
return;
25312561
}
25322562
_constraints = constraints;
2563+
25332564
if (_relayoutBoundary != null && relayoutBoundary != _relayoutBoundary) {
25342565
// The local relayout boundary has changed, must notify children in case
25352566
// they also need updating. Otherwise, they will be confused about what
25362567
// their actual relayout boundary is later.
25372568
visitChildren(_cleanChildRelayoutBoundary);
25382569
}
25392570
_relayoutBoundary = relayoutBoundary;
2571+
25402572
assert(!_debugMutationsLocked);
25412573
assert(!_doingThisLayoutWithCallback);
25422574
assert(() {

0 commit comments

Comments
 (0)