Skip to content

Commit a16e48d

Browse files
authored
Add LazyState (#906)
1 parent f37859d commit a16e48d

2 files changed

Lines changed: 189 additions & 30 deletions

File tree

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
//
2+
// LazyState.swift
3+
// OpenSwiftUI
4+
//
5+
// Audited for 6.5.4
6+
// Status: Complete
7+
// ID: D23E5900647091DC07D54FF9E7D1D688 (SwiftUI)
8+
9+
import OpenAttributeGraphShims
10+
@_spi(ForOpenSwiftUIOnly) import OpenSwiftUICore
11+
12+
// MARK: - LazyState
13+
14+
@_spi(Private)
15+
@available(OpenSwiftUI_v5_0, *)
16+
@propertyWrapper
17+
@frozen
18+
public struct LazyState<Value>: DynamicProperty {
19+
@usableFromInline
20+
enum Storage {
21+
case thunk(() -> Value)
22+
case value(Value)
23+
24+
@usableFromInline
25+
var value: Value {
26+
switch self {
27+
case let .thunk(thunk):
28+
thunk()
29+
case let .value(value):
30+
value
31+
}
32+
}
33+
}
34+
35+
@usableFromInline
36+
var _storage: Storage
37+
38+
@usableFromInline
39+
var _location: AnyLocation<Value>?
40+
41+
public init(wrappedValue thunk: @autoclosure @escaping () -> Value) {
42+
_storage = .thunk(thunk)
43+
_location = nil
44+
}
45+
46+
public var wrappedValue: Value {
47+
get {
48+
getValue(forReading: true)
49+
}
50+
nonmutating set {
51+
guard let _location else {
52+
return
53+
}
54+
_location.set(newValue, transaction: Transaction())
55+
}
56+
}
57+
58+
public var projectedValue: Binding<Value> {
59+
let value = getValue(forReading: false)
60+
guard let _location else {
61+
Log.runtimeIssues("Accessing State's value outside of being installed on a View. This will result in a constant Binding of the initial value and will not update.")
62+
return .constant(value)
63+
}
64+
return Binding(value: value, location: _location)
65+
}
66+
67+
private func getValue(forReading: Bool) -> Value {
68+
guard let _location else {
69+
return _storage.value
70+
}
71+
if GraphHost.isUpdating {
72+
if forReading {
73+
_location.wasRead = true
74+
}
75+
return _storage.value
76+
} else {
77+
return _location.get()
78+
}
79+
}
80+
81+
public static func _makeProperty<V>(
82+
in buffer: inout _DynamicPropertyBuffer,
83+
container: _GraphValue<V>,
84+
fieldOffset: Int,
85+
inputs: inout _GraphInputs
86+
) {
87+
let attribute = Attribute(value: ())
88+
let box = LazyStatePropertyBox<Value>(signal: WeakAttribute(attribute))
89+
buffer.append(box, fieldOffset: fieldOffset)
90+
addTreeValue(
91+
attribute,
92+
as: Value.self,
93+
at: fieldOffset,
94+
in: V.self,
95+
flags: .stateSignal
96+
)
97+
}
98+
}
99+
100+
@_spi(Private)
101+
@available(*, unavailable)
102+
extension LazyState: Sendable {}
103+
104+
@_spi(Private)
105+
@available(OpenSwiftUI_v5_0, *)
106+
extension LazyState where Value: ExpressibleByNilLiteral {
107+
@inlinable
108+
public init() {
109+
self.init(wrappedValue: nil)
110+
}
111+
}
112+
113+
@_spi(Private)
114+
@available(*, unavailable)
115+
extension LazyState.Storage: Sendable {}
116+
117+
// MARK: - LazyStatePropertyBox
118+
119+
private struct LazyStatePropertyBox<Value>: DynamicPropertyBox {
120+
let signal: WeakAttribute<Void>
121+
122+
var location: StoredLocation<Value>?
123+
124+
typealias Property = LazyState<Value>
125+
126+
func destroy() {
127+
location?.invalidate()
128+
}
129+
130+
mutating func reset() {
131+
location?.invalidate()
132+
location = nil
133+
}
134+
135+
mutating func update(property: inout LazyState<Value>, phase: _GraphInputs.Phase) -> Bool {
136+
let oldLocation = location
137+
var changed = oldLocation == nil
138+
let newLocation: StoredLocation<Value>
139+
if let oldLocation {
140+
newLocation = oldLocation
141+
} else {
142+
newLocation = property._location as? StoredLocation ?? StoredLocation(
143+
initialValue: property._storage.value,
144+
host: .currentHost,
145+
signal: signal
146+
)
147+
location = newLocation
148+
}
149+
let signalChanged = signal.changedValue()?.changed ?? false
150+
property._storage = .value(newLocation.updateValue)
151+
property._location = newLocation
152+
if signalChanged {
153+
changed = oldLocation == nil || newLocation.wasRead
154+
}
155+
return changed
156+
}
157+
}

‎Sources/OpenSwiftUICore/Data/State/StoredLocation.swift‎

Lines changed: 32 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
// ID: EBDC911C9EE054BAE3D86F947C24B7C3 (SwiftUI)
88
// ID: 4F21368B1C1680817451AC25B55A8D48 (SwiftUICore)
99

10-
import OpenAttributeGraphShims
10+
package import OpenAttributeGraphShims
1111
import OpenSwiftUI_SPI
1212

1313
// MARK: - StoredLocationBase
@@ -18,14 +18,14 @@ package class StoredLocationBase<Value>: AnyLocation<Value>, Location, @unchecke
1818
var savedValue: [Value]
1919
var cache: LocationProjectionCache
2020
}
21-
21+
2222
fileprivate struct BeginUpdate: GraphMutation {
2323
weak var box: StoredLocationBase<Value>?
24-
24+
2525
func apply() {
2626
box?.beginUpdate()
2727
}
28-
28+
2929
func combine<Mutation: GraphMutation>(with mutation: Mutation) -> Bool {
3030
guard let otherBeginUpdate = mutation as? BeginUpdate,
3131
let box,
@@ -40,12 +40,12 @@ package class StoredLocationBase<Value>: AnyLocation<Value>, Location, @unchecke
4040
return true
4141
}
4242
}
43-
43+
4444
@AtomicBox
4545
private var data: Data
4646

4747
var _wasRead: Bool
48-
48+
4949
package init(initialValue value: Value) {
5050
_wasRead = false
5151
_data = AtomicBox(wrappedValue: Data(currentValue: value, savedValue: [], cache: .init()))
@@ -54,7 +54,7 @@ package class StoredLocationBase<Value>: AnyLocation<Value>, Location, @unchecke
5454

5555
// MARK: - final properties and methods
5656

57-
final var updateValue: Value {
57+
final package var updateValue: Value {
5858
$data.access { data in
5959
data.savedValue.first ?? data.currentValue
6060
}
@@ -64,57 +64,59 @@ package class StoredLocationBase<Value>: AnyLocation<Value>, Location, @unchecke
6464
Binding(value: updateValue, location: self)
6565
}
6666

67-
private final func beginUpdate() {
67+
final private func beginUpdate() {
6868
$data.access { data in
6969
_ = data.savedValue.removeFirst()
7070
}
7171
notifyObservers()
7272
}
7373

74-
final func invalidate() {
74+
final package func invalidate() {
7575
$data.access { data in
7676
data.cache.reset()
7777
}
7878
}
7979

8080
// MARK: - non-final methods
8181

82-
fileprivate var isValid: Bool { true }
83-
82+
fileprivate var isValid: Bool {
83+
true
84+
}
85+
8486
// MARK: - abstract method
85-
87+
8688
fileprivate var isUpdating: Bool {
8789
_openSwiftUIBaseClassAbstractMethod()
8890
}
8991

9092
fileprivate func commit(transaction: Transaction, mutation: BeginUpdate) {
9193
_openSwiftUIBaseClassAbstractMethod()
9294
}
93-
95+
9496
fileprivate func notifyObservers() {
9597
_openSwiftUIBaseClassAbstractMethod()
9698
}
97-
99+
98100
// MARK: - AnyLocation
99-
100-
override package final var wasRead: Bool {
101+
102+
override final package var wasRead: Bool {
101103
get { _wasRead }
102104
set { _wasRead = newValue }
103105
}
104-
105-
override package final func get() -> Value {
106+
107+
override final package func get() -> Value {
106108
data.currentValue
107109
}
108110

109-
override package final func projecting<P: Projection>(
111+
override final package func projecting<P: Projection>(
110112
_ projection: P
111-
)-> AnyLocation<P.Projected> where Value == P.Base {
113+
) -> AnyLocation<P.Projected> where Value == P.Base {
112114
$data.access { data in
113115
data.cache.reference(for: projection, on: self)
114116
}
115117
}
116118

117-
override package final func set(_ value: Value, transaction: Transaction) {
119+
override final package func set(_ value: Value, transaction: Transaction) {
118120
guard !isUpdating else {
119121
Log.runtimeIssues("Modifying state during view update, this will cause undefined behavior.")
120122
return
@@ -145,7 +147,7 @@ package class StoredLocationBase<Value>: AnyLocation<Value>, Location, @unchecke
145147
commit(transaction: transaction, mutation: update)
146148
}
147149
}
148-
150+
149151
override package func update() -> (Value, Bool) {
150152
_wasRead = true
151153
return (updateValue, true)
@@ -154,31 +156,31 @@ package class StoredLocationBase<Value>: AnyLocation<Value>, Location, @unchecke
154156

155157
// MARK: - StoredLocation
156158

157-
package final class StoredLocation<Value>: StoredLocationBase<Value>, @unchecked Sendable {
159+
final package class StoredLocation<Value>: StoredLocationBase<Value>, @unchecked Sendable {
158160
weak var host: GraphHost?
159161
@WeakAttribute var signal: Void?
160-
161-
init(initialValue value: Value, host: GraphHost?, signal: WeakAttribute<Void>) {
162+
163+
package init(initialValue value: Value, host: GraphHost?, signal: WeakAttribute<Void>) {
162164
self.host = host
163-
self._signal = signal
165+
_signal = signal
164166
super.init(initialValue: value)
165167
}
166-
168+
167169
override fileprivate var isValid: Bool {
168170
host?.isValid ?? false
169171
}
170-
172+
171173
override fileprivate var isUpdating: Bool {
172174
host?.isUpdating ?? false
173175
}
174-
176+
175177
override fileprivate func commit(transaction: Transaction, mutation: StoredLocationBase<Value>.BeginUpdate) {
176178
host?.asyncTransaction(
177179
transaction,
178180
mutation: mutation
179181
)
180182
}
181-
183+
182184
override fileprivate func notifyObservers() {
183185
$signal?.invalidateValue()
184186
}

0 commit comments

Comments
 (0)