Skip to content

Add State implementation #75

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/wasm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ jobs:
swift_version: ["5.10-SNAPSHOT-2024-03-30-a"] # "5.10-RELEASE" is not release for WASM, tracked via https://github.com/swiftwasm/swift/issues/5570
os: [ubuntu-22.04]
runs-on: ${{ matrix.os }}
env:
OPENSWIFTUI_WERROR: 1
OPENSWIFTUI_SWIFT_TESTING: 0
OPENGRAPH_ATTRIBUTEGRAPH: 0
OPENSWIFTUI_COMPATIBILITY_TEST: 0
OPENSWIFTUI_SWIFT_LOG: 1
steps:
- uses: actions/checkout@v4
- uses: swiftwasm/setup-swiftwasm@v1
Expand Down
2 changes: 1 addition & 1 deletion Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Sources/OpenSwiftUI/Core/Data/Location/AnyLocation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class AnyLocation<Value>: AnyLocationBase {
}
func get() -> Value { fatalError() }
func set(_ value: Value, transaction: Transaction) { fatalError() }
func projecting<P>(_ p: P) -> AnyLocation<P.Projected> where P: Projection, P.Base == Value {
func projecting<P: Projection>(_ p: P) -> AnyLocation<P.Projected> where Value == P.Base {
fatalError()
}
func update() -> (Value, Bool) { fatalError() }
Expand Down
4 changes: 2 additions & 2 deletions Sources/OpenSwiftUI/Core/Data/Location/LocationBox.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ class LocationBox<L: Location>: AnyLocation<L.Value> {
location.set(value, transaction: transaction)
}

override func projecting<P>(_ p: P) -> AnyLocation<P.Projected> where L.Value == P.Base, P : Projection {
cache.reference(for: p, on: location)
override func projecting<P: Projection>(_ projection: P) -> AnyLocation<P.Projected> where L.Value == P.Base {
cache.reference(for: projection, on: location)
}

override func update() -> (L.Value, Bool) {
Expand Down

This file was deleted.

18 changes: 17 additions & 1 deletion Sources/OpenSwiftUI/Core/Data/Property/PropertyList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
// Status: Blocked by merge
// ID: 2B32D570B0B3D2A55DA9D4BFC1584D20

internal import COpenSwiftUI
internal import OpenGraphShims

// MARK: - PropertyList
Expand All @@ -18,6 +19,11 @@ struct PropertyList: CustomStringConvertible {

@inlinable
init() { elements = nil }

@inline(__always)
init(elements: Element?) {
self.elements = elements
}

@usableFromInline
var description: String {
Expand Down Expand Up @@ -86,13 +92,23 @@ struct PropertyList: CustomStringConvertible {
fatalError("TODO")
}

mutating private func override(with plist: PropertyList) {
mutating func override(with plist: PropertyList) {
if let element = elements {
elements = element.byPrepending(plist.elements)
} else {
elements = plist.elements
}
}

@inline(__always)
static var current: PropertyList {
if let data = _threadTransactionData() {
// FIXME: swift_dynamicCastClassUnconditional
PropertyList(elements: data.assumingMemoryBound(to: Element.self).pointee)
} else {
PropertyList()
}
}
}

// MARK: - PropertyList Help functions
Expand Down
22 changes: 22 additions & 0 deletions Sources/OpenSwiftUI/Core/Util/ThreadUtils.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// ThreadUtils.swift
//
//
// Created by Kyle on 2024/4/21.
//

import Foundation

@inline(__always)
func performOnMainThread(_ block: @escaping () -> Void) {
#if os(WASI)
// See #76: Thread and RunLoopMode.common is not available on WASI currently
block()
#else
if Thread.isMainThread {
block()
} else {
RunLoop.main.perform(inModes: [.common], block: block)
}
#endif
}
7 changes: 6 additions & 1 deletion Sources/OpenSwiftUI/Data/Model/Binding/Binding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,12 @@ extension Binding: DynamicProperty {
}
}

public static func _makeProperty<V>(in buffer: inout _DynamicPropertyBuffer, container: _GraphValue<V>, fieldOffset: Int, inputs: inout _GraphInputs) {
public static func _makeProperty<V>(
in buffer: inout _DynamicPropertyBuffer,
container _: _GraphValue<V>,
fieldOffset: Int,
inputs _: inout _GraphInputs
) {
buffer.append(Box(), fieldOffset: fieldOffset)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
protocol DynamicPropertyBox<Property>: DynamicProperty {
associatedtype Property: DynamicProperty
func destroy()
func reset()
mutating func reset()
mutating func update(property: inout Property, phase: _GraphInputs.Phase) -> Bool
func getState<Value>(type: Value.Type) -> Binding<Value>?
}
Expand Down
55 changes: 49 additions & 6 deletions Sources/OpenSwiftUI/Data/Model/State/State.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
// OpenSwiftUI
//
// Audited for RELEASE_2021
// Status: Blocked by DynamicProperty
// Status: Complete
// ID: 08168374F4710A99DCB15B5E8768D632

internal import OpenGraphShims

/// A property wrapper type that can read and write a value managed by OpenSwiftUI.
///
/// Use state as the single source of truth for a given value type that you
Expand Down Expand Up @@ -206,11 +208,6 @@ public struct State<Value> {
}
}

extension State: DynamicProperty {
// TODO:
public static func _makeProperty(in _: inout _DynamicPropertyBuffer, container _: _GraphValue<some Any>, fieldOffset _: Swift.Int, inputs _: inout _GraphInputs) {}
}

extension State where Value: ExpressibleByNilLiteral {
/// Creates a state property without an initial value.
///
Expand All @@ -237,3 +234,49 @@ extension State {
}
}
}

extension State: DynamicProperty {
public static func _makeProperty<V>(
in buffer: inout _DynamicPropertyBuffer,
container _: _GraphValue<V>,
fieldOffset: Int,
inputs _: inout _GraphInputs
) {
let attribute = Attribute(value: ())
let box = StatePropertyBox<Value>(signal: WeakAttribute(attribute))
buffer.append(box, fieldOffset: fieldOffset)
}
}

private struct StatePropertyBox<Value>: DynamicPropertyBox {
let signal: WeakAttribute<Void>
var location: StoredLocation<Value>?

typealias Property = State<Value>
func destroy() {}
mutating func reset() { location = nil }
mutating func update(property: inout State<Value>, phase: _GraphInputs.Phase) -> Bool {
let locationChanged = location == nil
if location == nil {
location = property._location as? StoredLocation ?? StoredLocation(
initialValue: property._value,
host: .currentHost,
signal: signal
)
}
let signalChanged = signal.changedValue()?.changed ?? false
property._value = location!.updateValue
property._location = location!
return (signalChanged ? location!.wasRead : false) || locationChanged
}
func getState<V>(type _: V.Type) -> Binding<V>? {
guard Value.self == V.self,
let location
else {
return nil
}
let value = location.get()
let binding = Binding(value: value, location: location)
return binding as? Binding<V>
}
}
176 changes: 176 additions & 0 deletions Sources/OpenSwiftUI/Data/Model/State/StoredLocation.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
//
// StoredLocation.swift
// OpenSwiftUI
//
// Audited for RELEASE_2021
// Status: WIP
// ID: EBDC911C9EE054BAE3D86F947C24B7C3

internal import OpenGraphShims
internal import COpenSwiftUI

class StoredLocationBase<Value>: AnyLocation<Value>, Location {
private struct LockedData {
var currentValue: Value
var savedValue: [Value]
var cache: LocationProjectionCache

init(currentValue: Value, savedValue: [Value], cache: LocationProjectionCache = LocationProjectionCache()) {
self.currentValue = currentValue
self.savedValue = savedValue
self.cache = cache
}
}

fileprivate struct BeginUpdate: GraphMutation {
weak var box: StoredLocationBase<Value>?

func apply() {
box?.beginUpdate()
}

func combine<Mutation: GraphMutation>(with mutation: Mutation) -> Bool {
guard let otherBeginUpdate = mutation as? BeginUpdate,
let box,
let otherBox = otherBeginUpdate.box,
box === otherBox
else {
return false
}

box.$data.withMutableData { data in
_ = data.savedValue.removeFirst()
}
return true
}
}

@UnsafeLockedPointer
private var data: LockedData

var _wasRead: Bool

init(initialValue value: Value) {
_wasRead = false
_data = UnsafeLockedPointer(wrappedValue: LockedData(currentValue: value, savedValue: []))
super.init()
}

fileprivate var isValid: Bool { true }

// MARK: - abstract method

fileprivate var isUpdating: Bool {
fatalError("abstract")
}

fileprivate func commit(transaction: Transaction, mutation: BeginUpdate) {
fatalError("abstract")
}

fileprivate func notifyObservers() {
fatalError("abstract")
}

// MARK: - AnyLocation

override var wasRead: Bool {
get { _wasRead }
set { _wasRead = newValue }
}

override func get() -> Value {
data.currentValue
}

override func set(_ value: Value, transaction: Transaction) {
guard !isUpdating else {
Log.runtimeIssues("Modifying state during view update, this will cause undefined behavior.")
return
}
guard isValid else {
$data.withMutableData { data in
data.savedValue.removeAll()
}
return
}
let shouldCommit = $data.withMutableData { data in
guard !compareValues(data.currentValue, value) else {
return false
}
data.savedValue.append(data.currentValue)
data.currentValue = value
return true
}
guard shouldCommit else {
return
}
var newTransaction = transaction
newTransaction.override(.current)
performOnMainThread { [weak self] in
guard let self else {
return
}
let update = BeginUpdate(box: self)
commit(transaction: newTransaction, mutation: update)
}
}

override func projecting<P: Projection>(_ projection: P) -> AnyLocation<P.Projected> where Value == P.Base {
data.cache.reference(for: projection, on: self)
}

override func update() -> (Value, Bool) {
_wasRead = true
return (updateValue, true)
}

// MARK: - final properties and methods

deinit {
$data.destroy()
}

final var updateValue: Value {
$data.withMutableData { data in
data.savedValue.first ?? data.currentValue
}
}

private final func beginUpdate() {
data.savedValue.removeFirst()
notifyObservers()
}
}

final class StoredLocation<Value>: StoredLocationBase<Value> {
weak var host: GraphHost?
@WeakAttribute var signal: Void?

init(initialValue value: Value, host: GraphHost?, signal: WeakAttribute<Void>) {
self.host = host
_signal = signal
super.init(initialValue: value)
}

override fileprivate var isValid: Bool {
host?.isValid ?? false
}

override fileprivate var isUpdating: Bool {
host?.isUpdating ?? false
}

override fileprivate func commit(transaction: Transaction, mutation: StoredLocationBase<Value>.BeginUpdate) {
host?.asyncTransaction(
transaction,
mutation: mutation,
style: ._1,
mayDeferUpdate: true
)
}

override fileprivate func notifyObservers() {
$signal?.invalidateValue()
}
}
Loading