Skip to content

Commit 65d8c50

Browse files
authored
Add AtomicBox and ObjectCache implementation (#114)
* Add AtomicBox implementation * Fix OpenSwiftUICore link issue * Add ObjectCache * Add unit test for AtomicBox and ObjectCache
1 parent f7eb6d5 commit 65d8c50

16 files changed

+222
-2
lines changed

Package.swift

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ if warningsAsErrorsCondition {
8484
let openSwiftUICoreTarget = Target.target(
8585
name: "OpenSwiftUICore",
8686
dependencies: [
87-
"COpenSwiftUI",
87+
"COpenSwiftUICore",
8888
.product(name: "OpenGraphShims", package: "OpenGraph"),
8989
],
9090
swiftSettings: sharedSwiftSettings
@@ -106,6 +106,14 @@ let openSwiftUIExtensionTarget = Target.target(
106106
],
107107
swiftSettings: sharedSwiftSettings
108108
)
109+
let openSwiftUICoreTestTarget = Target.testTarget(
110+
name: "OpenSwiftUICoreTests",
111+
dependencies: [
112+
"OpenSwiftUICore",
113+
],
114+
exclude: ["README.md"],
115+
swiftSettings: sharedSwiftSettings
116+
)
109117
let openSwiftUITestTarget = Target.testTarget(
110118
name: "OpenSwiftUITests",
111119
dependencies: [
@@ -153,6 +161,16 @@ let package = Package(
153161
),
154162
.target(
155163
name: "COpenSwiftUI",
164+
dependencies: [
165+
"COpenSwiftUICore",
166+
],
167+
cSettings: [
168+
.unsafeFlags(["-I", includePath], .when(platforms: .nonDarwinPlatforms)),
169+
.define("__COREFOUNDATION_FORSWIFTFOUNDATIONONLY__", to: "1", .when(platforms: .nonDarwinPlatforms)),
170+
]
171+
),
172+
.target(
173+
name: "COpenSwiftUICore",
156174
cSettings: [
157175
.unsafeFlags(["-I", includePath], .when(platforms: .nonDarwinPlatforms)),
158176
.define("__COREFOUNDATION_FORSWIFTFOUNDATIONONLY__", to: "1", .when(platforms: .nonDarwinPlatforms)),
@@ -206,6 +224,7 @@ extension Target {
206224
if attributeGraphCondition {
207225
openSwiftUICoreTarget.addAGSettings()
208226
openSwiftUITarget.addAGSettings()
227+
openSwiftUICoreTestTarget.addAGSettings()
209228
openSwiftUITestTarget.addAGSettings()
210229
openSwiftUITempTestTarget.addAGSettings()
211230
openSwiftUICompatibilityTestTarget.addAGSettings()
@@ -244,10 +263,12 @@ if swiftTestingCondition {
244263
// Fix it to be 0.3.0 before we bump to Swift 5.10
245264
.package(url: "https://github.com/apple/swift-testing", exact: "0.6.0")
246265
)
266+
openSwiftUICoreTestTarget.addSwiftTestingSettings()
247267
openSwiftUITestTarget.addSwiftTestingSettings()
248268
openSwiftUITempTestTarget.addSwiftTestingSettings()
249269
openSwiftUICompatibilityTestTarget.addSwiftTestingSettings()
250270

271+
package.targets.append(openSwiftUICoreTestTarget)
251272
package.targets.append(openSwiftUITestTarget)
252273
package.targets.append(openSwiftUITempTestTarget)
253274
package.targets.append(openSwiftUICompatibilityTestTarget)
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
//
2+
// AtomicBox.swift
3+
// OpenSwiftUICore
4+
//
5+
// Audited for RELEASE_2024
6+
// Status: Complete
7+
// ID: 82B2D47816BC992595021D60C278AFF0
8+
9+
import Foundation
10+
11+
private final class AtomicBuffer<Value>: ManagedBuffer<os_unfair_lock_s, Value> {
12+
static func allocate(value: Value) -> AtomicBuffer<Value> {
13+
let buffer = AtomicBuffer.create(minimumCapacity: 1) { buffer in
14+
os_unfair_lock_s()
15+
}
16+
buffer.withUnsafeMutablePointerToElements { pointer in
17+
pointer.initialize(to: value)
18+
}
19+
return unsafeDowncast(buffer, to: AtomicBuffer<Value>.self)
20+
}
21+
}
22+
23+
@propertyWrapper
24+
package struct AtomicBox<Value> {
25+
private let buffer: AtomicBuffer<Value>
26+
27+
package init(wrappedValue: Value) {
28+
buffer = AtomicBuffer.allocate(value: wrappedValue)
29+
}
30+
31+
@inline(__always)
32+
package var wrappedValue: Value {
33+
get {
34+
os_unfair_lock_lock(&buffer.header)
35+
defer { os_unfair_lock_unlock(&buffer.header) }
36+
return buffer.withUnsafeMutablePointerToElements { $0.pointee }
37+
}
38+
nonmutating _modify {
39+
os_unfair_lock_lock(&buffer.header)
40+
defer { os_unfair_lock_unlock(&buffer.header) }
41+
yield &buffer.withUnsafeMutablePointerToElements { $0 }.pointee
42+
}
43+
}
44+
45+
@inline(__always)
46+
package func access<T>(_ body: (inout Value) throws -> T) rethrows -> T {
47+
try body(&wrappedValue)
48+
}
49+
50+
package var projectedValue: AtomicBox<Value> { self }
51+
}
52+
53+
extension AtomicBox: @unchecked Sendable where Value: Sendable {}
54+
55+
extension AtomicBox where Value: ExpressibleByNilLiteral {
56+
package init() {
57+
self.init(wrappedValue: nil)
58+
}
59+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
//
2+
// ObjectCache.swift
3+
// OpenSwiftUICore
4+
//
5+
// Audited for RELEASE_2024
6+
// Status: Complete
7+
// ID: FCB2944DC319042A861E82C8B244E212
8+
9+
final package class ObjectCache<Key, Value> where Key: Hashable {
10+
let constructor: (Key) -> Value
11+
12+
@AtomicBox
13+
private var data: Data
14+
15+
@inlinable
16+
package init(constructor: @escaping (Key) -> Value) {
17+
self.constructor = constructor
18+
self.data = Data()
19+
}
20+
21+
final package subscript(key: Key) -> Value {
22+
let hash = key.hashValue
23+
let bucket = (hash & ((1 << 3) - 1)) << 2
24+
var targetOffset: Int = 0
25+
var diff: Int32 = Int32.min
26+
let value = $data.access { data -> Value? in
27+
for offset in 0 ..< 3 {
28+
let index = bucket + offset
29+
if let itemData = data.table[index].data {
30+
if itemData.hash == hash, itemData.key == key {
31+
data.clock += 1
32+
data.table[index].used = data.clock
33+
return itemData.value
34+
} else {
35+
if diff < Int32(bitPattern: data.clock &- data.table[index].used) {
36+
targetOffset = offset
37+
diff = Int32.max
38+
}
39+
}
40+
} else {
41+
if diff != Int32.max {
42+
targetOffset = offset
43+
diff = Int32.max
44+
}
45+
}
46+
}
47+
return nil
48+
}
49+
if let value {
50+
return value
51+
} else {
52+
let value = constructor(key)
53+
$data.access { data in
54+
data.clock += 1
55+
data.table[bucket + targetOffset] = Item(data: (key, hash, value), used: data.clock)
56+
}
57+
return value
58+
}
59+
}
60+
61+
private struct Item {
62+
var data: (key: Key, hash: Int, value: Value)?
63+
var used: UInt32
64+
65+
init(data: (key: Key, hash: Int, value: Value)?, used: UInt32) {
66+
self.data = data
67+
self.used = used
68+
}
69+
}
70+
71+
private struct Data {
72+
var table: [Item]
73+
var clock: UInt32
74+
75+
init() {
76+
self.table = Array(repeating: Item(data: nil, used: 0), count: 32)
77+
self.clock = 0
78+
}
79+
}
80+
}

Sources/OpenSwiftUICore/Util/Utils.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// Audited for RELEASE_2024
66
// Status: WIP
77

8-
internal import COpenSwiftUI
8+
internal import COpenSwiftUICore
99

1010
@inlinable
1111
@inline(__always)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//
2+
// AtomicBoxTests.swift
3+
// OpenSwiftUICoreTests
4+
5+
@testable import OpenSwiftUICore
6+
import Testing
7+
8+
struct AtomicBoxTests {
9+
@Test
10+
func expressibleByNilLiteral() {
11+
let box: AtomicBox<Int?> = AtomicBox()
12+
#expect(box.wrappedValue == nil)
13+
box.wrappedValue = 3
14+
#expect(box.wrappedValue == 3)
15+
}
16+
17+
@Test
18+
func access() {
19+
@AtomicBox var box: Int = 3
20+
#expect($box.access { $0.description } == "3")
21+
}
22+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//
2+
// ObjectCacheTests.swift
3+
// OpenSwiftUICoreTests
4+
5+
@testable import OpenSwiftUICore
6+
import Testing
7+
8+
struct ObjectCacheTests {
9+
@Test
10+
func example() {
11+
let cache: ObjectCache<Int, String> = ObjectCache { key in "\(key)" }
12+
#expect(cache[0] == "0")
13+
#expect(cache[1] == "1")
14+
#expect(cache[0] == "0")
15+
}
16+
}

Tests/OpenSwiftUICoreTests/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
## OpenSwiftUICoreTests
2+
3+
Test API of OpenSwiftUICore
4+
5+
```swift
6+
@testable import OpenSwiftUICore
7+
```
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//
2+
// Scaffolding.swift
3+
//
4+
//
5+
// Created by Kyle on 2023/11/8.
6+
//
7+
8+
import Testing
9+
import XCTest
10+
11+
final class AllTests: XCTestCase {
12+
func testAll() async {
13+
await XCTestScaffold.runAllTests(hostedBy: self)
14+
}
15+
}

0 commit comments

Comments
 (0)