Skip to content

Commit

Permalink
Noncopyable InoutRef (#446)
Browse files Browse the repository at this point in the history
  • Loading branch information
muukii authored Oct 29, 2023
1 parent f048784 commit e1656a1
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 36 deletions.
33 changes: 16 additions & 17 deletions Sources/Verge/Library/InoutRef.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import Foundation
- Warning: Do not retain this object anywhere.
*/
@dynamicMemberLookup
public final class InoutRef<Wrapped> {
public struct InoutRef<Wrapped>: ~Copyable {

// MARK: - Nested types

Expand Down Expand Up @@ -99,7 +99,7 @@ public final class InoutRef<Wrapped> {

private(set) var nonatomic_hasModified = false

private lazy var nonatomic_modifiedKeyPaths: Set<PartialKeyPath<Wrapped>> = .init()
private var nonatomic_modifiedKeyPaths: Set<PartialKeyPath<Wrapped>> = .init()

private var nonatomic_wasModifiedIndeterminate = false

Expand Down Expand Up @@ -161,11 +161,11 @@ public final class InoutRef<Wrapped> {
}
}

func append(trace: MutationTrace) {
mutating func append(trace: MutationTrace) {
traces.append(trace)
}

func append(traces otherTraces: [MutationTrace]) {
mutating func append(traces otherTraces: [MutationTrace]) {
traces.append(contentsOf: otherTraces)
}

Expand Down Expand Up @@ -234,20 +234,20 @@ public final class InoutRef<Wrapped> {
}

@inline(__always)
private func maskAsModified<U>(on keyPath: KeyPath<Wrapped, U>) {
private mutating func maskAsModified<U>(on keyPath: KeyPath<Wrapped, U>) {
nonatomic_modifiedKeyPaths.insert(keyPath)
nonatomic_hasModified = true
}

@inline(__always)
private func maskAsModified<U>(on keyPath: KeyPath<Wrapped, U?>) {
private mutating func maskAsModified<U>(on keyPath: KeyPath<Wrapped, U?>) {
nonatomic_modifiedKeyPaths.insert(keyPath)
nonatomic_hasModified = true
}

/// Marks as modified
/// `modification` property becomes to `.indeterminate`.
public func markAsModified() {
public mutating func markAsModified() {
nonatomic_hasModified = true
nonatomic_wasModifiedIndeterminate = true
}
Expand All @@ -258,7 +258,7 @@ public final class InoutRef<Wrapped> {
We can't overload `=` operator.
https://docs.swift.org/swift-book/LanguageGuide/AdvancedOperators.html
*/
public func replace(with newValue: Wrapped) {
public mutating func replace(with newValue: Wrapped) {
markAsModified()
pointer.pointee = newValue
}
Expand All @@ -268,15 +268,15 @@ public final class InoutRef<Wrapped> {
/// Attention: Using this method makes modifition indeterminate.
@available(*, renamed: "modifyDirectly")
@discardableResult
public func modify<Return>(_ perform: (inout Wrapped) throws -> Return) rethrows -> Return {
public mutating func modify<Return>(_ perform: (inout Wrapped) throws -> Return) rethrows -> Return {
return try modifyDirectly(perform)
}

/// Modify the wrapped value with native accessing (without KeyPath + DynamicMemberLookup)
///
/// Attention: Using this method makes modifition indeterminate.
@discardableResult
public func modifyDirectly<Return>(_ perform: (inout Wrapped) throws -> Return) rethrows -> Return {
public mutating func modifyDirectly<Return>(_ perform: (inout Wrapped) throws -> Return) rethrows -> Return {
markAsModified()
return try perform(&pointer.pointee)
}
Expand Down Expand Up @@ -307,15 +307,15 @@ public final class InoutRef<Wrapped> {

- SeeAlso: InoutRef<Wrapped>.Modification
*/
public func withType<Return>(_ perform: (Wrapped.Type, InoutRef<Wrapped>) throws -> Return) rethrows -> Return {
try perform(Wrapped.self, self)
public mutating func withType<Return>(_ perform: (Wrapped.Type, inout InoutRef<Wrapped>) throws -> Return) rethrows -> Return {
try perform(Wrapped.self, &self)
}

/**
Returns a tantative InoutRef that projects the value specified by KeyPath.
That InoutRef must be used only in the given perform closure.
*/
public func map<U, Result>(keyPath: WritableKeyPath<Wrapped, U>, perform: (inout InoutRef<U>) throws -> Result) rethrows -> Result {
public mutating func map<U, Result>(keyPath: WritableKeyPath<Wrapped, U>, perform: (inout InoutRef<U>) throws -> Result) rethrows -> Result {
try withUnsafeMutablePointer(to: &pointer.pointee[keyPath: keyPath]) { (pointer) in
var ref = InoutRef<U>.init(pointer)
defer {
Expand All @@ -336,15 +336,14 @@ public final class InoutRef<Wrapped> {
Returns a tantative InoutRef that projects the value specified by KeyPath.
That InoutRef must be used only in the given perform closure.
*/
public func map<U, Result>(keyPath: WritableKeyPath<Wrapped, U?>, perform: (inout InoutRef<U>?) throws -> Result) rethrows -> Result {
public mutating func map<U, Result>(keyPath: WritableKeyPath<Wrapped, U?>, perform: (inout InoutRef<U>) throws -> Result) rethrows -> Result? {

guard pointer.pointee[keyPath: keyPath] != nil else {
var _nil: InoutRef<U>! = .none
return try perform(&_nil)
return nil
}

return try withUnsafeMutablePointer(to: &pointer.pointee[keyPath: keyPath]!) { (pointer) in
var ref: InoutRef<U>! = InoutRef<U>.init(pointer)
var ref: InoutRef<U> = InoutRef<U>.init(pointer)
defer {
self.nonatomic_hasModified = ref.nonatomic_hasModified
self.nonatomic_wasModifiedIndeterminate = ref.nonatomic_wasModifiedIndeterminate
Expand Down
9 changes: 6 additions & 3 deletions Sources/Verge/Store/Store.swift
Original file line number Diff line number Diff line change
Expand Up @@ -175,9 +175,12 @@ open class Store<State: Equatable, Activity>: EventEmitter<_StoreEvent<State, Ac

// making reduced state
var _initialState = initialState
var inoutRef = InoutRef<State>.init(&_initialState)
State.reduce(modifying: &inoutRef, current: .init(old: nil, new: initialState))
let reduced = inoutRef.wrapped

let reduced = withUnsafeMutablePointer(to: &_initialState) { pointer in
var inoutRef = InoutRef<State>.init(pointer)
State.reduce(modifying: &inoutRef, current: .init(old: nil, new: initialState))
return inoutRef.wrapped
}

self.nonatomicValue = .init(old: nil, new: reduced)
self._lock = storeOperation
Expand Down
14 changes: 7 additions & 7 deletions Tests/VergeTests/InoutRefTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ final class InoutTests: XCTestCase {

withUnsafeMutablePointer(to: &state) { pointer in

let ref1 = InoutRef(pointer)
var ref1 = InoutRef(pointer)
let ref2 = InoutRef(pointer)

XCTAssertEqual(ref1.count, ref2.count)
Expand Down Expand Up @@ -142,7 +142,7 @@ final class InoutTests: XCTestCase {
})

withUnsafeMutablePointer(to: &value) { (pointer) -> Void in
let proxy = InoutRef.init(pointer)
var proxy = InoutRef.init(pointer)

proxy.wrappedValue = 1

Expand Down Expand Up @@ -178,7 +178,7 @@ final class InoutTests: XCTestCase {
var value = DemoState()

let modification = withUnsafeMutablePointer(to: &value) { (pointer) -> InoutRef<DemoState>.Modification? in
let proxy = InoutRef.init(pointer)
var proxy = InoutRef.init(pointer)
proxy.count += 1

XCTAssert(proxy.hasModified(\.self))
Expand All @@ -203,7 +203,7 @@ final class InoutTests: XCTestCase {
var value = DemoState()

let modification = withUnsafeMutablePointer(to: &value) { (pointer) -> InoutRef<DemoState>.Modification? in
let proxy = InoutRef.init(pointer)
var proxy = InoutRef.init(pointer)
proxy.count += 1
proxy.map(keyPath: \.inner) { (inner) in
inner.name = UUID().uuidString
Expand All @@ -228,7 +228,7 @@ final class InoutTests: XCTestCase {
var value = DemoState()

let modification = withUnsafeMutablePointer(to: &value) { (pointer) -> InoutRef<DemoState>.Modification? in
let proxy = InoutRef.init(pointer)
var proxy = InoutRef.init(pointer)
proxy.modify {
$0.count += 1
}
Expand All @@ -251,7 +251,7 @@ final class InoutTests: XCTestCase {
var value = DemoState()

let modification = withUnsafeMutablePointer(to: &value) { (pointer) -> InoutRef<DemoState>.Modification? in
let proxy = InoutRef.init(pointer)
var proxy = InoutRef.init(pointer)
proxy.modify {
$0.count += 1
}
Expand All @@ -266,7 +266,7 @@ final class InoutTests: XCTestCase {
var value = DemoState()

let modification = withUnsafeMutablePointer(to: &value) { (pointer) -> InoutRef<DemoState>.Modification? in
let proxy = InoutRef.init(pointer)
var proxy = InoutRef.init(pointer)
proxy.wrapped.updateFromItself()
return proxy.modification
}
Expand Down
17 changes: 8 additions & 9 deletions Tests/VergeTests/VergeStoreTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -121,17 +121,16 @@ final class VergeStoreTests: XCTestCase {

let _: Changes<State.NestedState> = _detached.state

_detached.commit { state in
let _: InoutRef<State.NestedState> = state

_detached.commit { (state: inout InoutRef<State.NestedState>) in

}

let optionalNestedTarget = detached(from: \.optionalNested)

let _: Changes<State.OptionalNestedState?> = optionalNestedTarget.state

optionalNestedTarget.commit { state in
let _: InoutRef<State.OptionalNestedState?> = state
optionalNestedTarget.commit { (state: inout InoutRef<State.OptionalNestedState?>) in

}

}
Expand All @@ -154,16 +153,16 @@ final class VergeStoreTests: XCTestCase {

let _: Changes<Edge<_State.TreeA>> = state

commit { state in
let _: InoutRef<Edge<_State.TreeA>> = state
commit { (state: inout InoutRef<Edge<_State.TreeA>>) in

}

let treeB = detached(from: \.$treeB)

let _: Changes<Edge<_State.TreeB>> = treeB.state

treeB.commit { state in
let _: InoutRef<Edge<_State.TreeB>> = state
treeB.commit { (state: inout InoutRef<Edge<_State.TreeB>>) in

}

}
Expand Down

0 comments on commit e1656a1

Please sign in to comment.