mirror of
https://github.com/phranck/TUIkit.git
synced 2026-06-20 09:54:37 +00:00
Refactor: Add cross-platform Lock wrapper for optimal performance
Create Lock<State> that uses OSAllocatedUnfairLock on Apple platforms (faster unfair lock) and NSLock on Linux. Also fix TupleView Equatable conformance for Swift 6 concurrency.
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
// TUIKit - Terminal UI Kit for Swift
|
||||
// Lock.swift
|
||||
//
|
||||
// Created by LAYERED.work
|
||||
// License: MIT
|
||||
|
||||
import Foundation
|
||||
|
||||
#if canImport(os)
|
||||
import os
|
||||
#endif
|
||||
|
||||
/// A cross-platform lock wrapper that uses the best available implementation.
|
||||
///
|
||||
/// On Apple platforms, uses `OSAllocatedUnfairLock` for optimal performance
|
||||
/// (unfair lock with no syscall in the uncontended case). On Linux, falls back
|
||||
/// to `NSLock`.
|
||||
///
|
||||
/// This type is `@unchecked Sendable` because the underlying lock implementations
|
||||
/// are thread-safe by design.
|
||||
final class Lock<State: Sendable>: @unchecked Sendable {
|
||||
#if canImport(os)
|
||||
private let _lock: OSAllocatedUnfairLock<State>
|
||||
|
||||
/// Creates a lock with the given initial state.
|
||||
///
|
||||
/// - Parameter initialState: The initial protected state.
|
||||
init(initialState: State) {
|
||||
_lock = OSAllocatedUnfairLock(initialState: initialState)
|
||||
}
|
||||
|
||||
/// Executes the closure while holding the lock and returns the result.
|
||||
///
|
||||
/// - Parameter body: The closure to execute with exclusive access to the state.
|
||||
/// - Returns: The value returned by the closure.
|
||||
func withLock<R: Sendable>(_ body: @Sendable (inout State) throws -> R) rethrows -> R {
|
||||
try _lock.withLock(body)
|
||||
}
|
||||
#else
|
||||
private let _lock = NSLock()
|
||||
private var _state: State
|
||||
|
||||
/// Creates a lock with the given initial state.
|
||||
///
|
||||
/// - Parameter initialState: The initial protected state.
|
||||
init(initialState: State) {
|
||||
_state = initialState
|
||||
}
|
||||
|
||||
/// Executes the closure while holding the lock and returns the result.
|
||||
///
|
||||
/// - Parameter body: The closure to execute with exclusive access to the state.
|
||||
/// - Returns: The value returned by the closure.
|
||||
func withLock<R>(_ body: (inout State) throws -> R) rethrows -> R {
|
||||
_lock.lock()
|
||||
defer { _lock.unlock() }
|
||||
return try body(&_state)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
/// `@ViewBuilder`. Do not instantiate directly.
|
||||
public struct TupleView<each V: View>: View {
|
||||
/// The packed child views.
|
||||
nonisolated(unsafe) let children: (repeat each V)
|
||||
let children: (repeat each V)
|
||||
|
||||
/// Creates a tuple view from a parameter pack of child views.
|
||||
///
|
||||
@@ -34,7 +34,7 @@ public struct TupleView<each V: View>: View {
|
||||
// MARK: - Equatable Conformance
|
||||
|
||||
extension TupleView: @preconcurrency Equatable where repeat each V: Equatable {
|
||||
public nonisolated static func == (lhs: TupleView, rhs: TupleView) -> Bool {
|
||||
public static func == (lhs: TupleView, rhs: TupleView) -> Bool {
|
||||
func isEqual<T: Equatable>(_ left: T, _ right: T) -> Bool { left == right }
|
||||
var result = true
|
||||
repeat result = result && isEqual(each lhs.children, each rhs.children)
|
||||
|
||||
@@ -22,15 +22,15 @@ import Foundation
|
||||
/// - Important: This is framework infrastructure. Prefer using ``State`` for reactive state
|
||||
/// management in your views. Direct use of `AppState` is only necessary in advanced scenarios
|
||||
/// where you manage state outside the view hierarchy.
|
||||
public final class AppState: @unchecked Sendable {
|
||||
public final class AppState: Sendable {
|
||||
/// Internal state protected by a lock.
|
||||
private struct StateData: Sendable {
|
||||
var needsRender = false
|
||||
var observers: [@Sendable () -> Void] = []
|
||||
}
|
||||
|
||||
/// Lock protecting all mutable state.
|
||||
private let lock = NSLock()
|
||||
|
||||
/// Whether state has changed since last render.
|
||||
private var _needsRender = false
|
||||
|
||||
/// Observers to notify on state change.
|
||||
private var _observers: [@Sendable () -> Void] = []
|
||||
private let lock = Lock(initialState: StateData())
|
||||
|
||||
/// Creates a new app state instance.
|
||||
public init() {}
|
||||
@@ -48,11 +48,10 @@ public extension AppState {
|
||||
/// automatically detects environment changes via ``EnvironmentSnapshot``
|
||||
/// comparison and clears the cache when needed.
|
||||
func setNeedsRender() {
|
||||
let observers: [@Sendable () -> Void]
|
||||
lock.lock()
|
||||
_needsRender = true
|
||||
observers = _observers
|
||||
lock.unlock()
|
||||
let observers = lock.withLock { state -> [@Sendable () -> Void] in
|
||||
state.needsRender = true
|
||||
return state.observers
|
||||
}
|
||||
// Call observers outside the lock to avoid potential deadlocks
|
||||
for observer in observers {
|
||||
observer()
|
||||
@@ -65,32 +64,30 @@ public extension AppState {
|
||||
extension AppState {
|
||||
/// Whether state has changed since last render.
|
||||
var needsRender: Bool {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
return _needsRender
|
||||
lock.withLock { $0.needsRender }
|
||||
}
|
||||
|
||||
/// Registers an observer to be notified of state changes.
|
||||
///
|
||||
/// - Parameter callback: The callback to invoke on state change.
|
||||
func observe(_ callback: @escaping @Sendable () -> Void) {
|
||||
lock.lock()
|
||||
_observers.append(callback)
|
||||
lock.unlock()
|
||||
lock.withLock { state in
|
||||
state.observers.append(callback)
|
||||
}
|
||||
}
|
||||
|
||||
/// Clears all observers.
|
||||
func clearObservers() {
|
||||
lock.lock()
|
||||
_observers.removeAll()
|
||||
lock.unlock()
|
||||
lock.withLock { state in
|
||||
state.observers.removeAll()
|
||||
}
|
||||
}
|
||||
|
||||
/// Resets the needs render flag.
|
||||
func didRender() {
|
||||
lock.lock()
|
||||
_needsRender = false
|
||||
lock.unlock()
|
||||
lock.withLock { state in
|
||||
state.needsRender = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user