Skip to content

Commit

Permalink
Single assignment cancellable (#55)
Browse files Browse the repository at this point in the history
* Add SingleAssignmentCancellable

We need this in the shared code base.

* by-catch: Extend tests to cover negative ranges
  • Loading branch information
melle authored Aug 28, 2024
1 parent ce30082 commit 3b05ba2
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 1 deletion.
121 changes: 121 additions & 0 deletions Sources/FoundationExtensions/Combine/SingleAssignmentCancellable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import Combine
import Foundation

/// Credit by Krunoslav Zaher, RxSwift:
/// https://github.com/ReactiveX/RxSwift/blob/main/RxSwift/Disposables/SingleAssignmentDisposable.swift
public final class SingleAssignmentCancellable: Cancellable {
private enum CancelState: Int32 {
case cancelled = 1
case cancellableSet = 2
}

// state
private let state = AtomicInt(0)
private var innerCancellable = nil as AnyCancellable?

/// - returns: A value that indicates whether the object is disposed.
public var isCancelled: Bool {
isFlagSet(self.state, CancelState.cancelled.rawValue)
}

/// Initializes a new instance of the `SingleAssignmentCancellable`.
public init() {
}

/// Gets or sets the underlying disposable. After disposal, the result of getting this property is undefined.
///
/// **Throws exception if the `SingleAssignmentDisposable` has already been assigned to.**
public func setCancellable(_ cancellable: Cancellable) {
self.innerCancellable = AnyCancellable { cancellable.cancel() }

let previousState = fetchOr(self.state, CancelState.cancellableSet.rawValue)

if (previousState & CancelState.cancellableSet.rawValue) != 0 {
fatalError("oldState.disposable != nil")
}

if (previousState & CancelState.cancelled.rawValue) != 0 {
cancellable.cancel()
self.innerCancellable = nil
}
}

/// Disposes the underlying disposable.
public func cancel() {
let previousState = fetchOr(self.state, CancelState.cancelled.rawValue)

if (previousState & CancelState.cancelled.rawValue) != 0 {
return
}

if (previousState & CancelState.cancellableSet.rawValue) != 0 {
guard let cancellable = self.innerCancellable else {
fatalError("Disposable not set")
}
cancellable.cancel()
self.innerCancellable = nil
}
}
}

final class AtomicInt: NSLock {
fileprivate var value: Int32
init(_ value: Int32 = 0) {
self.value = value
}
}

@discardableResult
@inline(__always)
func add(_ this: AtomicInt, _ value: Int32) -> Int32 {
this.lock()
let oldValue = this.value
this.value += value
this.unlock()
return oldValue
}

@discardableResult
@inline(__always)
func sub(_ this: AtomicInt, _ value: Int32) -> Int32 {
this.lock()
let oldValue = this.value
this.value -= value
this.unlock()
return oldValue
}

@discardableResult
@inline(__always)
func fetchOr(_ this: AtomicInt, _ mask: Int32) -> Int32 {
this.lock()
let oldValue = this.value
this.value |= mask
this.unlock()
return oldValue
}

@inline(__always)
func load(_ this: AtomicInt) -> Int32 {
this.lock()
let oldValue = this.value
this.unlock()
return oldValue
}

@discardableResult
@inline(__always)
func increment(_ this: AtomicInt) -> Int32 {
add(this, 1)
}

@discardableResult
@inline(__always)
func decrement(_ this: AtomicInt) -> Int32 {
sub(this, 1)
}

@inline(__always)
func isFlagSet(_ this: AtomicInt, _ mask: Int32) -> Bool {
(load(this) & mask) != 0
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,16 @@ extension ComparableExtensionsTests {
(minimum: 0.0, maximum: 100.0, given: -1000.0, expected: 0.0),
(minimum: 0.0, maximum: 100.0, given: 100.01, expected: 100.0),
(minimum: 0.0, maximum: 100.0, given: 101.0, expected: 100.0),
(minimum: 0.0, maximum: 100.0, given: 1000.0, expected: 100.0)
(minimum: 0.0, maximum: 100.0, given: 1000.0, expected: 100.0),
(minimum: -100.0, maximum: 100.0, given: 0.0, expected: 0.0),
(minimum: -100.0, maximum: 100.0, given: -1.0, expected: -1.0),
(minimum: -100.0, maximum: 100.0, given: -101.0, expected: -100.0),
(minimum: -100.0, maximum: 100.0, given: 101.0, expected: 100.0),
(minimum: -100.0, maximum: -40.0, given: -45.0, expected: -45.0),
(minimum: -100.0, maximum: -40.0, given: 30.0, expected: -40.0),
(minimum: -100.0, maximum: -40.0, given: -30.0, expected: -40.0),
(minimum: -100.0, maximum: -40.0, given: -101.0, expected: -100.0),
(minimum: -100.0, maximum: -40.0, given: -99.0, expected: -99.0),
]

// when
Expand Down

0 comments on commit 3b05ba2

Please sign in to comment.