Skip to content

Commit 8a7f600

Browse files
committed
Initial draft of observation
1 parent e059be1 commit 8a7f600

32 files changed

+1398
-0
lines changed

CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,10 @@ option(SWIFT_ENABLE_EXPERIMENTAL_REFLECTION
571571
"Enable build of the Swift reflection module"
572572
FALSE)
573573

574+
option(SWIFT_ENABLE_EXPERIMENTAL_OBSERVATION
575+
"Enable build of the Swift observation module"
576+
FALSE)
577+
574578
option(SWIFT_ENABLE_DISPATCH
575579
"Enable use of libdispatch"
576580
TRUE)
@@ -1117,6 +1121,7 @@ if(SWIFT_BUILD_STDLIB OR SWIFT_BUILD_SDK_OVERLAY)
11171121
message(STATUS "String Processing Support: ${SWIFT_ENABLE_EXPERIMENTAL_STRING_PROCESSING}")
11181122
message(STATUS "Unicode Support: ${SWIFT_STDLIB_ENABLE_UNICODE_DATA}")
11191123
message(STATUS "Reflection Support: ${SWIFT_ENABLE_EXPERIMENTAL_REFLECTION}")
1124+
message(STATUS "Observation Support: ${SWIFT_ENABLE_EXPERIMENTAL_OBSERVATION}")
11201125
message(STATUS "")
11211126
else()
11221127
message(STATUS "Not building Swift standard library, SDK overlays, and runtime")

stdlib/cmake/modules/SwiftSource.cmake

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,10 @@ function(_add_target_variant_swift_compile_flags
298298
list(APPEND result "-D" "SWIFT_ENABLE_EXPERIMENTAL_REFLECTION")
299299
endif()
300300

301+
if(SWIFT_ENABLE_EXPERIMENTAL_OBSERVATION)
302+
list(APPEND result "-D" "SWIFT_ENABLE_EXPERIMENTAL_OBSERVATION")
303+
endif()
304+
301305
if(SWIFT_STDLIB_OS_VERSIONING)
302306
list(APPEND result "-D" "SWIFT_RUNTIME_OS_VERSIONING")
303307
endif()

stdlib/public/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,10 @@ if(SWIFT_BUILD_STDLIB)
151151
if(SWIFT_ENABLE_EXPERIMENTAL_REFLECTION)
152152
add_subdirectory(Reflection)
153153
endif()
154+
155+
if(SWIFT_ENABLE_EXPERIMENTAL_OBSERVATION)
156+
add_subdirectory(Observation)
157+
endif()
154158
endif()
155159

156160
if(SWIFT_BUILD_STDLIB OR SWIFT_BUILD_REMOTE_MIRROR)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#===--- CMakeLists.txt - Reflection support library ---------------------===#
2+
#
3+
# This source file is part of the Swift.org open source project
4+
#
5+
# Copyright (c) 2023 Apple Inc. and the Swift project authors
6+
# Licensed under Apache License v2.0 with Runtime Library Exception
7+
#
8+
# See https://swift.org/LICENSE.txt for license information
9+
# See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
#
11+
#===----------------------------------------------------------------------===#
12+
13+
add_subdirectory(Sources/_ObservationRuntime)
14+
add_subdirectory(Sources/Observation)
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#===--- CMakeLists.txt - Observation support library ---------------------===#
2+
#
3+
# This source file is part of the Swift.org open source project
4+
#
5+
# Copyright (c) 2023 Apple Inc. and the Swift project authors
6+
# Licensed under Apache License v2.0 with Runtime Library Exception
7+
#
8+
# See https://swift.org/LICENSE.txt for license information
9+
# See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
#
11+
#===----------------------------------------------------------------------===#
12+
13+
set(SWIFT_OBSERVATION_SWIFT_FLAGS)
14+
15+
list(APPEND SWIFT_OBSERVATION_SWIFT_FLAGS
16+
"-parse-stdlib")
17+
18+
add_swift_target_library(swiftObservation ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} IS_STDLIB
19+
ImmediateTransactionModel.swift
20+
Locking.swift
21+
MemberKeyPaths.swift
22+
Observable.swift
23+
ObservationRegistrar.swift
24+
ObservationToken.swift
25+
ObservationTracking.swift
26+
ObservationTransaction.swift
27+
ObservationTransactionModel.swift
28+
ObservedChanges.swift
29+
Observer.swift
30+
ThreadLocal.swift
31+
32+
SWIFT_COMPILE_FLAGS
33+
${SWIFT_STANDARD_LIBRARY_SWIFT_FLAGS}
34+
${SWIFT_OBSERVATION_SWIFT_FLAGS}
35+
SWIFT_MODULE_DEPENDS _ObservationRuntime
36+
INSTALL_IN_COMPONENT stdlib)
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2022 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
import Swift
13+
14+
@available(SwiftStdlib 5.9, *)
15+
struct ImmediateObservationTransactionModel: ObservationTransactionModel {
16+
func register<O: Observer, Member>(
17+
transaction: inout ObservationTransaction<O.Subject>,
18+
observer: O,
19+
observable: O.Subject,
20+
willSet keyPath: KeyPath<O.Subject, Member>,
21+
to newValue: Member
22+
) -> Bool {
23+
return true
24+
}
25+
26+
func register<O: Observer, Member>(
27+
transaction: inout ObservationTransaction<O.Subject>,
28+
observer: O,
29+
observable: O.Subject,
30+
didSet keyPath: KeyPath<O.Subject, Member>
31+
) -> Bool {
32+
transaction.addObserver(observer)
33+
return true
34+
}
35+
36+
func commit<Subject>(
37+
transaction: inout ObservationTransaction<Subject>,
38+
observable: Subject
39+
) -> Bool where Subject : Observable {
40+
return true
41+
}
42+
43+
func invalidate() {
44+
45+
}
46+
}
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2022 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
import Swift
13+
14+
@available(SwiftStdlib 5.9, *)
15+
@frozen
16+
public struct MemberKeyPaths<Root>: SetAlgebra, Hashable {
17+
public typealias Element = PartialKeyPath<Root>
18+
public typealias ArrayLiteralElement = PartialKeyPath<Root>
19+
20+
@usableFromInline
21+
internal var raw: Set<UnsafeRawPointer>
22+
23+
@inlinable
24+
internal init(raw keyPaths: Set<UnsafeRawPointer>) {
25+
self.raw = keyPaths
26+
}
27+
28+
@inlinable
29+
public init(_ sequence: __owned some Sequence<PartialKeyPath<Root>>) {
30+
self.raw = Set(sequence.map {
31+
UnsafeRawPointer(Unmanaged.passUnretained($0).toOpaque())
32+
})
33+
}
34+
35+
@inlinable
36+
public init() {
37+
self.raw = Set()
38+
}
39+
40+
@inlinable
41+
public init(arrayLiteral elements: PartialKeyPath<Root>...) {
42+
self.init(elements)
43+
}
44+
45+
@inlinable
46+
public func union(
47+
_ other: __owned MemberKeyPaths<Root>
48+
) -> MemberKeyPaths<Root> {
49+
MemberKeyPaths(raw: raw.union(other.raw))
50+
}
51+
52+
@inlinable
53+
public func intersection(
54+
_ other: MemberKeyPaths<Root>
55+
) -> MemberKeyPaths<Root> {
56+
MemberKeyPaths(raw: raw.intersection(other.raw))
57+
}
58+
59+
@inlinable
60+
public func symmetricDifference(
61+
_ other: __owned MemberKeyPaths<Root>
62+
) -> MemberKeyPaths<Root> {
63+
MemberKeyPaths(raw: raw.symmetricDifference(other.raw))
64+
}
65+
66+
@inlinable
67+
public mutating func formUnion(_ other: __owned MemberKeyPaths<Root>) {
68+
raw.formUnion(other.raw)
69+
}
70+
71+
@inlinable
72+
public mutating func formIntersection(_ other: MemberKeyPaths<Root>) {
73+
raw.formIntersection(other.raw)
74+
}
75+
76+
@inlinable
77+
public mutating func formSymmetricDifference(
78+
_ other: __owned MemberKeyPaths<Root>
79+
) {
80+
raw.formSymmetricDifference(other.raw)
81+
}
82+
83+
@inlinable
84+
public func contains(_ member: PartialKeyPath<Root>) -> Bool {
85+
raw.contains(UnsafeRawPointer(Unmanaged.passUnretained(member).toOpaque()))
86+
}
87+
88+
@inlinable
89+
public var isEmpty: Bool {
90+
raw.isEmpty
91+
}
92+
93+
@inlinable
94+
@discardableResult
95+
public mutating func insert(
96+
_ newMember: __owned PartialKeyPath<Root>
97+
) -> (inserted: Bool, memberAfterInsert: PartialKeyPath<Root>) {
98+
let (inserted, memberAfterInsert) = raw.insert(
99+
UnsafeRawPointer(Unmanaged.passUnretained(newMember).toOpaque()))
100+
return (
101+
inserted,
102+
Unmanaged<PartialKeyPath<Root>>.fromOpaque(memberAfterInsert)
103+
.takeUnretainedValue()
104+
)
105+
}
106+
107+
@inlinable
108+
internal mutating func _insert(_ raw: UnsafeRawPointer) {
109+
self.raw.insert(raw)
110+
}
111+
112+
@inlinable
113+
@discardableResult
114+
public mutating func remove(
115+
_ member: PartialKeyPath<Root>
116+
) -> PartialKeyPath<Root>? {
117+
raw.remove(
118+
UnsafeRawPointer(Unmanaged.passUnretained(member).toOpaque()))
119+
.map { Unmanaged.fromOpaque($0).takeUnretainedValue() }
120+
}
121+
122+
@inlinable
123+
@discardableResult
124+
public mutating func update(
125+
with newMember: __owned PartialKeyPath<Root>
126+
) -> PartialKeyPath<Root>? {
127+
raw.update(with:
128+
UnsafeRawPointer(Unmanaged.passUnretained(newMember).toOpaque()))
129+
.map { Unmanaged.fromOpaque($0).takeUnretainedValue() }
130+
}
131+
132+
@inlinable
133+
public func hash(into hasher: inout Hasher) {
134+
hasher.combine(raw)
135+
}
136+
137+
@inlinable
138+
public static func == (
139+
_ lhs: MemberKeyPaths<Root>,
140+
_ rhs: MemberKeyPaths<Root>
141+
) -> Bool {
142+
return lhs.raw == rhs.raw
143+
}
144+
}
145+
146+
@available(SwiftStdlib 5.9, *)
147+
extension MemberKeyPaths where Root: Observable {
148+
@inlinable
149+
public init(members: MemberKeyPaths<Root>, root: Root) {
150+
self.init()
151+
for raw in members.raw {
152+
self.formUnion(
153+
root.memberKeyPaths(for:
154+
Unmanaged<PartialKeyPath<Root>>.fromOpaque(raw)
155+
.takeUnretainedValue()))
156+
}
157+
}
158+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2022 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
import Swift
13+
14+
@available(SwiftStdlib 5.9, *)
15+
public protocol Observable: Identifiable {
16+
associatedtype Token
17+
18+
func addObserver(
19+
_ observer: some Observer<Self>,
20+
for members: MemberKeyPaths<Self>,
21+
using transactionModel: some ObservationTransactionModel
22+
) -> Token
23+
func removeObserver(_ observation: Token)
24+
func memberKeyPaths(for keyPath: PartialKeyPath<Self>) -> MemberKeyPaths<Self>
25+
}
26+
27+
@available(SwiftStdlib 5.9, *)
28+
extension Observable {
29+
public func addObserver<O: Observer>(
30+
_ observer: O,
31+
for fields: MemberKeyPaths<Self>
32+
) -> Token where O.Subject == Self {
33+
addObserver(observer,
34+
for: fields,
35+
using: ImmediateObservationTransactionModel())
36+
}
37+
38+
public func addObserver<Member>(
39+
_ observer: some Observer<Self>,
40+
for keyPath: KeyPath<Self, Member>,
41+
using transactionModel: some ObservationTransactionModel
42+
) -> Token {
43+
addObserver(observer, for: [keyPath], using: transactionModel)
44+
}
45+
46+
public func addObserver<O: Observer, Member>(
47+
_ observer: O,
48+
for keyPath: KeyPath<Self, Member>
49+
) -> Token where O.Subject == Self {
50+
addObserver(observer,
51+
for: keyPath,
52+
using: ImmediateObservationTransactionModel())
53+
}
54+
55+
public func memberKeyPaths(
56+
for keyPath: PartialKeyPath<Self>
57+
) -> MemberKeyPaths<Self> {
58+
return [keyPath]
59+
}
60+
}
61+
62+
63+
64+
65+

0 commit comments

Comments
 (0)