From 3fa347b87fe935a4e1a3fc728f87d3d25c6f8d52 Mon Sep 17 00:00:00 2001 From: Jordan Baird Date: Sun, 26 May 2024 03:16:35 -0600 Subject: [PATCH] Rework exclusivity lock --- .../ColorWellKit/Utilities/LockedState.swift | 79 +++++++++++++++++++ .../Views/Cocoa/CWColorWell.swift | 27 +++---- 2 files changed, 90 insertions(+), 16 deletions(-) create mode 100644 Sources/ColorWellKit/Utilities/LockedState.swift diff --git a/Sources/ColorWellKit/Utilities/LockedState.swift b/Sources/ColorWellKit/Utilities/LockedState.swift new file mode 100644 index 0000000..a615583 --- /dev/null +++ b/Sources/ColorWellKit/Utilities/LockedState.swift @@ -0,0 +1,79 @@ +// +// LockedState.swift +// ColorWellKit +// + +import os + +/// A locking wrapper around a state. +/// +/// This implementation is heavily inspired by Foundation's `LockedState` type: +/// https://github.com/apple/swift-foundation/blob/main/Sources/FoundationEssentials/LockedState.swift +struct LockedState { + private enum Lock { + typealias UnfairLock = os_unfair_lock + + static func initialize(_ lock: UnsafeMutablePointer) { + lock.initialize(to: UnfairLock()) + } + + static func deinitialize(_ lock: UnsafeMutablePointer) { + lock.deinitialize(count: 1) + } + + static func lock(_ lock: UnsafeMutablePointer) { + os_unfair_lock_lock(lock) + } + + static func unlock(_ lock: UnsafeMutablePointer) { + os_unfair_lock_unlock(lock) + } + } + + private class Buffer: ManagedBuffer { + deinit { + withUnsafeMutablePointerToElements { lock in + Lock.deinitialize(lock) + } + } + } + + private let buffer: ManagedBuffer + + var state: State { + buffer.withUnsafeMutablePointerToHeader { state in + state.pointee + } + } + + init(initialState: State) { + self.buffer = Buffer.create(minimumCapacity: 1) { buffer in + buffer.withUnsafeMutablePointerToElements { lock in + Lock.initialize(lock) + } + return initialState + } + } + + func withLock(_ body: (inout State) throws -> T) rethrows -> T { + try buffer.withUnsafeMutablePointers { state, lock in + Lock.lock(lock) + defer { + Lock.unlock(lock) + } + return try body(&state.pointee) + } + } + + func withLockExtendingLifetimeOfState(_ body: (inout State) throws -> T) rethrows -> T { + try buffer.withUnsafeMutablePointers { state, lock in + Lock.lock(lock) + return try withExtendedLifetime(state.pointee) { + defer { + Lock.unlock(lock) + } + return try body(&state.pointee) + } + } + } +} diff --git a/Sources/ColorWellKit/Views/Cocoa/CWColorWell.swift b/Sources/ColorWellKit/Views/Cocoa/CWColorWell.swift index fe80154..d86e125 100644 --- a/Sources/ColorWellKit/Views/Cocoa/CWColorWell.swift +++ b/Sources/ColorWellKit/Views/Cocoa/CWColorWell.swift @@ -27,9 +27,7 @@ public class CWColorWell: _CWColorWellBaseControl { // MARK: Instance Properties - private let exclusivityLock = NSRecursiveLock() - - private var isExclusive = true + private let exclusivity = LockedState(initialState: true) private var popover: NSPopover? @@ -143,7 +141,7 @@ public class CWColorWell: _CWColorWellBaseControl { } } if shouldActivate { - if isExclusive && allowsMultipleSelection { + if exclusivity.state && allowsMultipleSelection { NSColorPanel.shared.enforceExclusivity(of: self) } NSColorPanel.shared.attach(self) @@ -213,25 +211,22 @@ public class CWColorWell: _CWColorWellBaseControl { /// Until the color well is activated again, changes to the color panel will not /// affect the color well's state. public func deactivate() { - withExclusivityLock(isExclusive) { isActive = false } + withExclusivityLock(exclusivity.state) { isActive = false } } // MARK: Private/Internal Instance Methods /// Performs the given closure while locking the exclusivity of the color well /// to the given state in a thread-safe manner. - private func withExclusivityLock( - _ isExclusive: @autoclosure () -> Bool, - body: () throws -> T - ) rethrows -> T { - exclusivityLock.lock() - let cachedIsExclusive = self.isExclusive - self.isExclusive = isExclusive() - defer { - self.isExclusive = cachedIsExclusive - exclusivityLock.unlock() + private func withExclusivityLock(_ isExclusive: Bool, body: () throws -> T) rethrows -> T { + try exclusivity.withLock { state in + let cachedState = state + state = isExclusive + defer { + state = cachedState + } + return try body() } - return try body() } @objc(deactivate) // exposed to Objective-C as `deactivate`