Skip to content

Commit 5306f33

Browse files
committed
Add ViewLayoutEngine
1 parent 3423c71 commit 5306f33

File tree

3 files changed

+216
-9
lines changed

3 files changed

+216
-9
lines changed

Sources/OpenSwiftUICore/Layout/Layout.swift

Lines changed: 197 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -706,7 +706,11 @@ extension Layout {
706706
layoutContext ctx: SizeAndSpacingContext,
707707
children: LayoutProxyCollection
708708
) where R: StatefulRule, R.Value == LayoutComputer {
709-
preconditionFailure("TODO")
709+
rule.update { (engine: inout ViewLayoutEngine<Self>) in
710+
engine.update(layout: self, context: ctx, children: children)
711+
} create: {
712+
ViewLayoutEngine(layout: self, context: ctx, children: children)
713+
}
710714
}
711715

712716
public static var layoutProperties: LayoutProperties {
@@ -1118,6 +1122,165 @@ extension ViewSpacing: CustomStringConvertible {
11181122
}
11191123
}
11201124

1125+
// MARK: - DefaultAlignmentFunction [6.4.41]
1126+
1127+
private protocol DefaultAlignmentFunction {
1128+
static func defaultAlignment(
1129+
_ key: AlignmentKey,
1130+
size: ViewSize,
1131+
data: UnsafeMutableRawPointer
1132+
) -> CGFloat?
1133+
}
1134+
1135+
// MARK: - ViewLayoutEngine [6.4.41] [WIP]
1136+
1137+
struct ViewLayoutEngine<L>: DefaultAlignmentFunction, LayoutEngine where L: Layout {
1138+
var layout: L
1139+
1140+
var cache: L.Cache
1141+
1142+
var proxies: LayoutProxyCollection
1143+
1144+
var layoutDirection: LayoutDirection
1145+
1146+
var sizeCache: ViewSizeCache = .init()
1147+
1148+
var cachedAlignmentSize: ViewSize = .zero
1149+
1150+
var cachedAlignmentGeometry: [ViewGeometry] = []
1151+
1152+
var cachedAlignment: Cache3<ObjectIdentifier, CGFloat?> = .init()
1153+
1154+
var preferredSpacing: ViewSpacing?
1155+
1156+
init(
1157+
layout: L,
1158+
context ctx: SizeAndSpacingContext,
1159+
children: LayoutProxyCollection
1160+
) {
1161+
self.proxies = children
1162+
self.layoutDirection = ctx.layoutDirection
1163+
self.layout = layout
1164+
self.cache = layout.makeCache(
1165+
subviews: LayoutSubviews(
1166+
context: children.context,
1167+
storage: .direct(children.attributes),
1168+
layoutDirection: layoutDirection
1169+
)
1170+
)
1171+
}
1172+
1173+
mutating func update(
1174+
layout: L,
1175+
context ctx: SizeAndSpacingContext,
1176+
children: LayoutProxyCollection
1177+
) {
1178+
self.proxies = children
1179+
self.layoutDirection = ctx.layoutDirection
1180+
self.layout = layout
1181+
self.sizeCache = .init()
1182+
self.cachedAlignmentGeometry = []
1183+
self.cachedAlignment = .init()
1184+
self.preferredSpacing = nil
1185+
layout.updateCache(
1186+
&cache,
1187+
subviews: LayoutSubviews(
1188+
context: proxies.context,
1189+
storage: .direct(proxies.attributes),
1190+
layoutDirection: layoutDirection
1191+
)
1192+
)
1193+
}
1194+
1195+
func layoutPriority() -> Double {
1196+
proxies.attributes.isEmpty ? -Double.infinity : Double.zero
1197+
}
1198+
1199+
mutating func spacing() -> Spacing {
1200+
guard let preferredSpacing else {
1201+
let subviews = LayoutSubviews(
1202+
context: proxies.context,
1203+
storage: .direct(proxies.attributes),
1204+
layoutDirection: layoutDirection
1205+
)
1206+
let spacing = layout.spacing(subviews: subviews, cache: &cache)
1207+
preferredSpacing = spacing
1208+
return spacing.spacing
1209+
}
1210+
return preferredSpacing.spacing
1211+
}
1212+
1213+
mutating func sizeThatFits(_ proposedSize: _ProposedSize) -> CGSize {
1214+
sizeCache.get(proposedSize) {
1215+
layout.sizeThatFits(
1216+
proposal: ProposedViewSize(proposedSize),
1217+
subviews: LayoutSubviews(
1218+
context: proxies.context,
1219+
storage: .direct(proxies.attributes),
1220+
layoutDirection: layoutDirection
1221+
),
1222+
cache: &cache
1223+
)
1224+
}
1225+
}
1226+
1227+
mutating func childGeometries(at parentSize: ViewSize, origin: CGPoint) -> [ViewGeometry] {
1228+
preconditionFailure("TODO")
1229+
}
1230+
1231+
mutating func explicitAlignment(_ k: AlignmentKey, at viewSize: ViewSize) -> CGFloat? {
1232+
if cachedAlignmentSize != viewSize {
1233+
cachedAlignmentSize = viewSize
1234+
cachedAlignmentGeometry = []
1235+
cachedAlignment = .init()
1236+
}
1237+
let key = ObjectIdentifier(k.id)
1238+
guard let value = cachedAlignment.find(key) else {
1239+
let value = withUnsafePointer(to: self) { ptr in
1240+
Self.defaultAlignment(k, size: viewSize, data: UnsafeMutableRawPointer(mutating: ptr))
1241+
}
1242+
cachedAlignment.put(key, value: value)
1243+
return value
1244+
}
1245+
return value
1246+
}
1247+
1248+
static func defaultAlignment(_ key: AlignmentKey, size: ViewSize, data: UnsafeMutableRawPointer) -> CGFloat? {
1249+
guard size.value.isFinite else {
1250+
return nil
1251+
}
1252+
let enginePtr = data.assumingMemoryBound(to: Self.self)
1253+
1254+
let proxies = enginePtr.pointee.proxies
1255+
let oldAlignmentGeometry = enginePtr.pointee.cachedAlignmentGeometry
1256+
let alignmentGeometry: [ViewGeometry]
1257+
if oldAlignmentGeometry.count == proxies.count {
1258+
alignmentGeometry = oldAlignmentGeometry
1259+
} else {
1260+
alignmentGeometry = enginePtr.pointee.childGeometries(at: size, origin: .zero)
1261+
enginePtr.pointee.cachedAlignmentGeometry = alignmentGeometry
1262+
}
1263+
var result: CGFloat? = nil
1264+
guard !alignmentGeometry.isEmpty else {
1265+
return nil
1266+
}
1267+
var n = 0
1268+
for (index, proxy) in proxies.enumerated() {
1269+
let geometry = alignmentGeometry[index]
1270+
guard let value = proxy.layoutComputer.explicitAlignment(
1271+
key,
1272+
at: geometry.dimensions.size
1273+
) else {
1274+
continue
1275+
}
1276+
let origin = key.axis == .horizontal ? geometry.origin.x : geometry.origin.y
1277+
key.id._combineExplicit(childValue: origin + value, n, into: &result)
1278+
n += 1
1279+
}
1280+
return result
1281+
}
1282+
}
1283+
11211284
// MARK: - LayoutSubviews
11221285

11231286
/// A collection of proxy values that represent the subviews of a layout view.
@@ -1152,7 +1315,7 @@ public struct LayoutSubviews: Equatable, RandomAccessCollection, Sendable {
11521315

11531316
var context: AnyRuleContext
11541317

1155-
private enum Storage: Equatable {
1318+
fileprivate enum Storage: Equatable {
11561319
case direct([LayoutProxyAttributes])
11571320
case indirect([IndexedAttributes])
11581321

@@ -1170,7 +1333,7 @@ public struct LayoutSubviews: Equatable, RandomAccessCollection, Sendable {
11701333
}
11711334
}
11721335

1173-
private var storage: Storage
1336+
fileprivate var storage: Storage
11741337

11751338
/// The layout direction inherited by the container view.
11761339
///
@@ -1450,9 +1613,27 @@ public struct LayoutSubview: Equatable {
14501613
@available(*, unavailable)
14511614
extension LayoutSubview: Sendable {}
14521615

1453-
// MARK: - PlacementData [WIP]
1616+
// MARK: - PlacementData [6.4.41] [WIP]
1617+
1618+
private struct PlacementData {
1619+
var geometrys: [ViewGeometry]
1620+
1621+
var invalidCount: Int
14541622

1455-
private struct PlacementData {}
1623+
var frame: CGRect
1624+
1625+
var layoutDirection: LayoutDirection
1626+
1627+
mutating func setGeometry(_ geometry: ViewGeometry, at index: Int, layoutDirection: LayoutDirection) {
1628+
if geometrys[index].isInvalid {
1629+
invalidCount += 1
1630+
}
1631+
geometrys[index] = geometry
1632+
if layoutDirection != self.layoutDirection {
1633+
geometrys[index].origin.x = frame.maxX - (geometry.frame.maxX - frame.x)
1634+
}
1635+
}
1636+
}
14561637

14571638
// MARK: - LayoutValueKey
14581639

@@ -1692,7 +1873,7 @@ package struct AnyLayoutProperties: Rule, AsyncAttribute {
16921873
}
16931874
}
16941875

1695-
// MARK: - ViewSizeCache
1876+
// MARK: - ViewSizeCache [6.4.41]
16961877

16971878
/// A cache for storing and retrieving view sizes based on proposed size values.
16981879
///
@@ -1719,7 +1900,16 @@ package struct ViewSizeCache {
17191900
/// - Returns: The cached or newly computed size.
17201901
@inline(__always)
17211902
package mutating func get(_ k: _ProposedSize, makeValue: () -> CGSize) -> CGSize {
1722-
cache.get(ProposedViewSize(k), makeValue: makeValue)
1903+
let key = ProposedViewSize(k)
1904+
if let value = cache.find(key) {
1905+
LayoutTrace.traceCacheLookup(k, true)
1906+
return value
1907+
} else {
1908+
LayoutTrace.traceCacheLookup(k, false)
1909+
let value = makeValue()
1910+
cache.put(key, value: value)
1911+
return value
1912+
}
17231913
}
17241914
}
17251915

Sources/OpenSwiftUICore/Layout/UnaryLayout.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
// Status: WIP
77

88
package import Foundation
9+
package import OpenGraphShims
910

1011
package protocol UnaryLayout: Animatable, MultiViewModifier, PrimitiveViewModifier {
1112
associatedtype PlacementContextType = PlacementContext
@@ -48,3 +49,13 @@ extension UnaryLayout {
4849
child.spacing()
4950
}
5051
}
52+
53+
extension StatefulRule where Value == LayoutComputer {
54+
package mutating func updateLayoutComputer<L>(
55+
layout: L,
56+
environment: Attribute<EnvironmentValues>,
57+
attributes: [LayoutProxyAttributes]
58+
) where L: Layout {
59+
preconditionFailure("TODO")
60+
}
61+
}

Sources/OpenSwiftUICore/Tracing/LayoutTrace.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,17 @@ package struct LayoutTrace {
5252
}
5353

5454
func traceCacheLookup(_ proposal: _ProposedSize, _ hit: Bool) {
55-
preconditionFailure("TODO")
55+
guard let recorder else {
56+
return
57+
}
58+
recorder.cacheLookup = (proposal, hit)
5659
}
5760

5861
func traceCacheLookup(_ proposal: CGSize, _ hit: Bool) {
59-
preconditionFailure("TODO")
62+
guard let recorder else {
63+
return
64+
}
65+
recorder.cacheLookup = (.init(proposal), hit)
6066
}
6167

6268
func traceChildGeometries(_ attribute: AnyAttribute?, at parentSize: ViewSize, origin: CGPoint, _ block: () -> [ViewGeometry]) -> [ViewGeometry] {

0 commit comments

Comments
 (0)