Skip to content

Commit

Permalink
optionally do not cache subelements
Browse files Browse the repository at this point in the history
  • Loading branch information
watt committed Sep 20, 2023
1 parent 4043283 commit 0ba0e4d
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 3 deletions.
21 changes: 20 additions & 1 deletion BlueprintUI/Sources/Element/LayoutStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,26 @@ extension LayoutStorage: LegacyContentStorage {
extension LayoutStorage: CaffeinatedContentStorage {

private func subelements(from node: LayoutTreeNode, environment: Environment) -> LayoutSubelements {
node.layoutSubelements {
if node.sizeCache.options.assumeStableSubelements {
// Current behavior
return node.layoutSubelements {
var identifierFactory = ElementIdentifier.Factory(elementCount: children.count)
return children.map { child in
let identifier = identifierFactory.nextIdentifier(
for: type(of: child.element),
key: child.key
)
return LayoutSubelement(
identifier: identifier,
content: child.content,
environment: environment,
node: node.subnode(key: identifier),
traits: child.traits
)
}
}
} else {
// Proposed new behavior
var identifierFactory = ElementIdentifier.Factory(elementCount: children.count)
return children.map { child in
let identifier = identifierFactory.nextIdentifier(
Expand Down
12 changes: 10 additions & 2 deletions BlueprintUI/Sources/Layout/LayoutOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ public struct LayoutOptions: Equatable {
/// The default configuration.
public static let `default` = LayoutOptions(
hintRangeBoundaries: true,
searchUnconstrainedKeys: true
searchUnconstrainedKeys: true,
assumeStableSubelements: true
)

/// Enables aggressive cache hinting along the boundaries of the range between constraints and
Expand All @@ -22,8 +23,15 @@ public struct LayoutOptions: Equatable {
/// Layout contract for correct behavior.
public var searchUnconstrainedKeys: Bool

public init(hintRangeBoundaries: Bool, searchUnconstrainedKeys: Bool) {
public var assumeStableSubelements: Bool

public init(
hintRangeBoundaries: Bool,
searchUnconstrainedKeys: Bool,
assumeStableSubelements: Bool = true
) {
self.hintRangeBoundaries = hintRangeBoundaries
self.searchUnconstrainedKeys = searchUnconstrainedKeys
self.assumeStableSubelements = assumeStableSubelements
}
}
64 changes: 64 additions & 0 deletions BlueprintUI/Tests/GeometryReaderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,70 @@ final class GeometryReaderTests: XCTestCase {

wait(for: [layoutExpectation], timeout: 5)
}

func test_dynamicSubelements() {
let threshold: CGFloat = 100
let size = CGSize(width: 120, height: 120)

let element: Element = Row { outerRow in
outerRow.horizontalUnderflow = .growUniformly
outerRow.horizontalOverflow = .condenseUniformly
outerRow.verticalAlignment = .fill

outerRow.addFlexible(
child: GeometryReader { geometry in

return Row { innerRow in
innerRow.horizontalUnderflow = .growUniformly
innerRow.horizontalOverflow = .condenseUniformly
innerRow.verticalAlignment = .fill

if let width = geometry.constraint.width.constrainedValue, width < threshold {
// If constrained < 100, 2 children
innerRow.addFixed(child: Spacer(1))
innerRow.addFixed(child: Spacer(1))
} else {
// else 1 child
innerRow.addFixed(child: Spacer(threshold))
}
}
}
)

outerRow.addFlexible(child: Spacer(threshold / 2))
}

// during layout:
// 1. Outer row measures GR with full width
// 2. GR body evaluates as a row with 1 child
// 3. Outer row measures again with reduced width
// 4. GR body evaluates as a row with 2 children
// 5. Subelement count has changed, as well as content of child 1

LayoutMode.caffeinated(options: .notAssumingSubelementsStable).performAsDefault {
let frames = element
.layout(frame: CGRect(origin: .zero, size: size))
.queryLayout(for: Spacer.self)
.map { $0.layoutAttributes.frame }

XCTAssertEqual(
frames,
[
CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: 1, height: 120)),
CGRect(origin: CGPoint(x: 1, y: 0), size: CGSize(width: 1, height: 120)),
CGRect(origin: CGPoint(x: 85, y: 0), size: CGSize(width: 35, height: 120)),
]
)
}
}
}

extension LayoutOptions {
static let notAssumingSubelementsStable = LayoutOptions(
hintRangeBoundaries: true,
searchUnconstrainedKeys: true,
assumeStableSubelements: false
)
}

extension SizeConstraint.Axis {
Expand Down
14 changes: 14 additions & 0 deletions BlueprintUI/Tests/LayoutResultNode+Testing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,18 @@ extension LayoutResultNode {
}
return nil
}

func queryLayout(for elementType: Element.Type) -> [LayoutResultNode] {
var results: [LayoutResultNode] = []

if type(of: element) == elementType {
results.append(self)
}

for child in children {
results.append(contentsOf: child.node.queryLayout(for: elementType))
}

return results
}
}

0 comments on commit 0ba0e4d

Please sign in to comment.