Skip to content

Commit d9a02bd

Browse files
authored
Update GraphReuse (#269)
1 parent 486b073 commit d9a02bd

File tree

5 files changed

+138
-45
lines changed

5 files changed

+138
-45
lines changed

Sources/OpenSwiftUICore/Graph/GraphInputs.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,13 +114,14 @@ public struct _GraphInputs {
114114

115115
private mutating func recordReusableInput<T>(_ input: T.Type) where T: GraphInput, T.Value: GraphReusable {
116116
let filter = BloomFilter(type: input)
117-
let reusableInputs = customInputs[ReusableInputs.self]
118-
if reusableInputs.stack.top == T.self {
117+
let inputs = customInputs[ReusableInputs.self]
118+
let stack = inputs.stack
119+
guard stack.top != T.self else {
119120
return
120121
}
121122
customInputs[ReusableInputs.self] = ReusableInputStorage(
122-
filter: reusableInputs.filter.union(filter),
123-
stack: .node(value: T.self, next: reusableInputs.stack)
123+
filter: inputs.filter.union(filter),
124+
stack: .node(value: T.self, next: stack)
124125
)
125126
}
126127

Sources/OpenSwiftUICore/Graph/GraphReuse.swift

Lines changed: 122 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,37 @@
33
// OpenSwiftUICore
44
//
55
// Audited for iOS 18.0
6-
// Status: Blocked by _GraphInputs
6+
// Status: Complete
77
// ID: 3E2D3733C4CBF57EC1EA761D02CE8317 (SwiftUICore)
88

9+
import Foundation
910
package import OpenGraphShims
11+
#if OPENSWIFTUI_SWIFT_LOG
12+
import Logging
13+
#else
14+
import os.log
15+
#endif
16+
17+
// MARK: - IndirectAttributeMap
1018

11-
public final class IndirectAttributeMap {
19+
package final class IndirectAttributeMap {
1220
package final let subgraph: Subgraph
21+
1322
package final var map: [AnyAttribute: AnyAttribute]
23+
1424
package init(subgraph: Subgraph) {
1525
self.subgraph = subgraph
1626
self.map = [:]
1727
}
1828
}
1929

30+
// MARK: - GraphReusable
31+
2032
package protocol GraphReusable {
2133
static var isTriviallyReusable: Bool { get }
34+
2235
mutating func makeReusable(indirectMap: IndirectAttributeMap)
36+
2337
func tryToReuse(by other: Self, indirectMap: IndirectAttributeMap, testOnly: Bool) -> Bool
2438
}
2539

@@ -28,6 +42,8 @@ extension GraphReusable {
2842
package static var isTriviallyReusable: Bool { false }
2943
}
3044

45+
// MARK: - _GraphValue + GraphReusable
46+
3147
extension _GraphValue: GraphReusable {
3248
package mutating func makeReusable(indirectMap: IndirectAttributeMap) {
3349
value.makeReusable(indirectMap: indirectMap)
@@ -41,65 +57,137 @@ extension _GraphValue where Value: GraphReusable {
4157
package static var isTriviallyReusable: Bool { Value.isTriviallyReusable }
4258
}
4359

44-
//extension _GraphInputs : GraphReusable {
45-
// package mutating func makeReusable(indirectMap: IndirectAttributeMap)
46-
// package func tryToReuse(by other: _GraphInputs, indirectMap: IndirectAttributeMap, testOnly: Bool) -> Bool
47-
//}
60+
// MARK: - _GraphInputs + GraphReusable [WIP]
61+
62+
extension _GraphInputs: GraphReusable {
63+
package mutating func makeReusable(indirectMap: IndirectAttributeMap) {
64+
time.makeReusable(indirectMap: indirectMap)
65+
phase.makeReusable(indirectMap: indirectMap)
66+
changedDebugProperties.insert(.phase)
67+
environment.makeReusable(indirectMap: indirectMap)
68+
detachEnvironmentInputs()
69+
changedDebugProperties.insert(.environment)
70+
transaction.makeReusable(indirectMap: indirectMap)
71+
func project<Input>(_ type: Input.Type) where Input: GraphInput {
72+
guard !Input.isTriviallyReusable else {
73+
return
74+
}
75+
var value = self[Input.self]
76+
Input.makeReusable(indirectMap: indirectMap, value: &value)
77+
self[Input.self] = value
78+
}
79+
let stack = customInputs[ReusableInputs.self].stack
80+
for value in stack {
81+
project(value)
82+
}
83+
}
84+
85+
package func tryToReuse(by other: _GraphInputs, indirectMap: IndirectAttributeMap, testOnly: Bool) -> Bool {
86+
guard time.tryToReuse(by: other.time, indirectMap: indirectMap, testOnly: testOnly),
87+
phase.tryToReuse(by: other.phase, indirectMap: indirectMap, testOnly: testOnly),
88+
environment.tryToReuse(by: other.environment, indirectMap: indirectMap, testOnly: testOnly),
89+
transaction.tryToReuse(by: other.transaction, indirectMap: indirectMap, testOnly: testOnly)
90+
else {
91+
Log.graphReuse("Reuse failed: standard inputs")
92+
return false
93+
}
94+
return reuseCustomInputs(by: other, indirectMap: indirectMap, testOnly: testOnly)
95+
}
96+
97+
private func reuseCustomInputs(by other: _GraphInputs, indirectMap: IndirectAttributeMap, testOnly: Bool) -> Bool {
98+
let reusableInputs = customInputs[ReusableInputs.self]
99+
let otherReusableInputs = other.customInputs[ReusableInputs.self]
100+
guard reusableInputs.filter == otherReusableInputs.filter else {
101+
return false
102+
}
103+
var reusableInputsArray: [ObjectIdentifier] = []
104+
for value in reusableInputs.stack {
105+
reusableInputsArray.append(ObjectIdentifier(value))
106+
}
107+
var otherReusableInputsArray: [ObjectIdentifier] = []
108+
for value in otherReusableInputs.stack {
109+
otherReusableInputsArray.append(ObjectIdentifier(value))
110+
}
111+
guard reusableInputsArray == otherReusableInputsArray else {
112+
Log.graphReuse("Reuse failed: custom inputs type mismatch")
113+
return false
114+
}
115+
var ignoredTypes = reusableInputsArray + [ObjectIdentifier(ReusableInputs.self)]
116+
guard !customInputs.mayNotBeEqual(to: other.customInputs, ignoredTypes: &ignoredTypes) else {
117+
Log.graphReuse("Reuse failed: custom inputs plist equality")
118+
return false
119+
}
120+
func project<Input>(_ type: Input.Type) -> Bool where Input: GraphInput {
121+
guard let index = ignoredTypes.firstIndex(of: ObjectIdentifier(type)) else {
122+
return true
123+
}
124+
let lastIndex = ignoredTypes.count - 1
125+
ignoredTypes.swapAt(index, lastIndex)
126+
guard !Input.isTriviallyReusable else {
127+
return true
128+
}
129+
guard Input.tryToReuse(self[type], by: other[type], indirectMap: indirectMap, testOnly: testOnly) else {
130+
Log.graphReuse("Reuse failed: custom input \(Input.self)")
131+
return false
132+
}
133+
return true
134+
}
135+
let stack = reusableInputs.stack
136+
for value in stack {
137+
guard project(value) else {
138+
return false
139+
}
140+
continue
141+
}
142+
return true
143+
}
144+
}
48145

49-
//extension _GraphInputs {
50-
// private func reuseCustomInputs(by other: _GraphInputs, indirectMap: IndirectAttributeMap, testOnly: Bool) -> Bool {
51-
// Log.graphReuse("Reuse failed: custom input \(Self.self)")
52-
// }
53-
//}
146+
// MARK: - Attribute + GraphReusable
54147

55148
extension Attribute: GraphReusable {
56149
package mutating func makeReusable(indirectMap: IndirectAttributeMap) {
150+
let indirect: AnyAttribute
57151
if let result = indirectMap.map[identifier] {
58-
identifier = result
152+
indirect = result
59153
} else {
60-
let indirect = indirectMap.subgraph.apply {
61-
IndirectAttribute(source: self)
154+
indirect = indirectMap.subgraph.apply {
155+
IndirectAttribute(source: self).identifier
62156
}
63-
indirectMap.map[identifier] = indirect.identifier
157+
indirectMap.map[identifier] = indirect
64158
}
159+
identifier = indirect
65160
}
66-
161+
67162
package func tryToReuse(by other: Attribute<Value>, indirectMap: IndirectAttributeMap, testOnly: Bool) -> Bool {
68-
if let result = indirectMap.map[identifier] {
69-
if testOnly {
70-
return true
71-
} else {
72-
result.source = other.identifier
73-
return true
74-
}
75-
} else {
163+
guard let result = indirectMap.map[identifier] else {
76164
Log.graphReuse("Reuse failed: missing indirection for \(Value.self)")
77165
return false
78166
}
167+
if !testOnly {
168+
result.source = other.identifier
169+
}
170+
return true
79171
}
80172
}
81173

82-
import Foundation
83-
#if canImport(Darwin)
84-
import os.log
85-
#endif
86-
87174
private struct EnableGraphReuseLogging: UserDefaultKeyedFeature {
88175
static var key: String { "org.OpenSwiftUIProject.OpenSwiftUI.GraphReuseLogging" }
176+
89177
static var cachedValue: Bool?
90178
}
91179

92180
extension Log {
93-
#if canImport(Darwin)
94181
private static let graphReuseLog: Logger = Logger(subsystem: Log.subsystem, category: "GraphReuse")
95-
#endif
96-
182+
97183
static func graphReuse(_ message: @autoclosure () -> String) {
98-
#if canImport(Darwin)
99184
if EnableGraphReuseLogging.isEnabled {
100185
let message = message()
186+
#if OPENSWIFTUI_SWIFT_LOG
187+
graphReuseLog.log(level: .info, "\(message)")
188+
#else
101189
graphReuseLog.log("\(message)")
190+
#endif
102191
}
103-
#endif
104192
}
105193
}

Sources/OpenSwiftUICore/Tracing/ReuseTrace.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ package struct ReuseTrace {
5151
traceReuseFailure("reuse_bodyMismatched")
5252
}
5353

54+
@inline(__always)
55+
package static func traceNeverMadeReusableFailure(_ valueType: (any Any.Type)?) {
56+
// TODO
57+
}
58+
5459
// TODO
5560

5661
final package class Recorder {

Sources/OpenSwiftUICore/View/Input/ViewList.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1051,12 +1051,12 @@ extension ViewList.ID.Views: Sendable {}
10511051

10521052
// MARK: - UnaryViewGenerator
10531053

1054-
public protocol UnaryViewGenerator {
1054+
private protocol UnaryViewGenerator {
10551055
func makeView(inputs: _ViewInputs, indirectMap: IndirectAttributeMap?) -> _ViewOutputs
10561056
func tryToReuse(by other: Self, indirectMap: IndirectAttributeMap, testOnly: Bool) -> Bool
10571057
}
10581058

1059-
public struct BodyUnaryViewGenerator<V>: UnaryViewGenerator {
1059+
private struct BodyUnaryViewGenerator<V>: UnaryViewGenerator {
10601060
let body: ViewList.Elements.MakeElement
10611061

10621062
public func makeView(inputs: _ViewInputs, indirectMap: IndirectAttributeMap?) -> _ViewOutputs {
@@ -1073,10 +1073,10 @@ public struct BodyUnaryViewGenerator<V>: UnaryViewGenerator {
10731073
}
10741074
}
10751075

1076-
public struct TypedUnaryViewGenerator<V>: UnaryViewGenerator where V: View {
1076+
private struct TypedUnaryViewGenerator<V>: UnaryViewGenerator where V: View {
10771077
let view: WeakAttribute<V>
10781078

1079-
public func makeView(inputs: _ViewInputs, indirectMap: IndirectAttributeMap?) -> _ViewOutputs {
1079+
func makeView(inputs: _ViewInputs, indirectMap: IndirectAttributeMap?) -> _ViewOutputs {
10801080
guard var view = view.attribute else {
10811081
return .init()
10821082
}
@@ -1086,7 +1086,7 @@ public struct TypedUnaryViewGenerator<V>: UnaryViewGenerator where V: View {
10861086
return V.makeDebuggableView(view: _GraphValue(view), inputs: inputs)
10871087
}
10881088

1089-
public func tryToReuse(by other: TypedUnaryViewGenerator<V>, indirectMap: IndirectAttributeMap, testOnly: Bool) -> Bool {
1089+
func tryToReuse(by other: TypedUnaryViewGenerator<V>, indirectMap: IndirectAttributeMap, testOnly: Bool) -> Bool {
10901090
guard let view = view.attribute, let otherView = other.view.attribute else {
10911091
Log.graphReuse("Reuse failed: missing attribute for \(V.self)")
10921092
return false

Tests/OpenSwiftUICoreTests/Graph/GraphHostTests.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
//
22
// GraphHostTests.swift
3-
// OpenSwiftUITests
3+
// OpenSwiftUICoreTests
44

5-
import OpenSwiftUICore
65
@_spi(ForOpenSwiftUIOnly) import OpenSwiftUICore
76
import Testing
87

0 commit comments

Comments
 (0)