diff --git a/Sources/TUIkit/Core/Environment.swift b/Sources/TUIkit/Core/Environment.swift index ecea748..08003ab 100644 --- a/Sources/TUIkit/Core/Environment.swift +++ b/Sources/TUIkit/Core/Environment.swift @@ -89,6 +89,9 @@ public final class EnvironmentStorage: @unchecked Sendable { /// The shared environment storage. public static let shared = EnvironmentStorage() + /// Lock protecting all mutable state. + private let lock = NSLock() + /// The current environment values. private var current = EnvironmentValues() @@ -99,20 +102,32 @@ public final class EnvironmentStorage: @unchecked Sendable { /// The current environment values. public var environment: EnvironmentValues { - get { current } - set { current = newValue } + get { + lock.lock() + defer { lock.unlock() } + return current + } + set { + lock.lock() + defer { lock.unlock() } + current = newValue + } } /// Pushes a new environment onto the stack. /// /// - Parameter environment: The environment to push. public func push(_ environment: EnvironmentValues) { + lock.lock() + defer { lock.unlock() } stack.append(current) current = environment } /// Pops the current environment and restores the previous one. public func pop() { + lock.lock() + defer { lock.unlock() } if let previous = stack.popLast() { current = previous } @@ -132,6 +147,8 @@ public final class EnvironmentStorage: @unchecked Sendable { /// Resets the environment to its initial state. public func reset() { + lock.lock() + defer { lock.unlock() } current = EnvironmentValues() stack.removeAll() } diff --git a/Sources/TUIkit/Core/KeyEvent.swift b/Sources/TUIkit/Core/KeyEvent.swift index 402eb68..9d2203f 100644 --- a/Sources/TUIkit/Core/KeyEvent.swift +++ b/Sources/TUIkit/Core/KeyEvent.swift @@ -250,10 +250,6 @@ extension KeyEvent { /// Views can register handlers that are called when keys are pressed. /// Handlers are processed in reverse order (most recent first). public final class KeyEventDispatcher: @unchecked Sendable { - /// The shared dispatcher instance. - @available(*, deprecated, message: "Use TUIContext.keyEventDispatcher instead") - public static let shared = KeyEventDispatcher() - /// Registered key handlers. private var handlers: [(KeyEvent) -> Bool] = [] diff --git a/Sources/TUIkit/Core/Preferences.swift b/Sources/TUIkit/Core/Preferences.swift index 768d7df..0dbe008 100644 --- a/Sources/TUIkit/Core/Preferences.swift +++ b/Sources/TUIkit/Core/Preferences.swift @@ -95,10 +95,6 @@ public struct PreferenceValues: @unchecked Sendable { /// Thread-local storage for collecting preferences during rendering. public final class PreferenceStorage: @unchecked Sendable { - /// The shared preference storage. - @available(*, deprecated, message: "Use TUIContext.preferences instead") - public static let shared = PreferenceStorage() - /// Stack of preference values for nested rendering. private var stack: [PreferenceValues] = [PreferenceValues()] diff --git a/Sources/TUIkit/Modifiers/LifecycleModifier.swift b/Sources/TUIkit/Modifiers/LifecycleModifier.swift index 9dc6001..50df2ff 100644 --- a/Sources/TUIkit/Modifiers/LifecycleModifier.swift +++ b/Sources/TUIkit/Modifiers/LifecycleModifier.swift @@ -7,92 +7,6 @@ import Foundation -// MARK: - Lifecycle Tracker - -/// Tracks which views have appeared to prevent duplicate onAppear calls. -/// -/// Since views are recreated on each render, we use a token-based system -/// to track which views have already triggered their onAppear action. -public final class LifecycleTracker: @unchecked Sendable { - /// Shared instance for the running application. - @available(*, deprecated, message: "Use TUIContext.lifecycle instead") - public static let shared = LifecycleTracker() - - /// Lock protecting all mutable state. - private let lock = NSLock() - - /// Set of tokens that have appeared. - private var appearedTokens: Set = [] - - /// Set of tokens that are currently visible (for onDisappear tracking). - private var visibleTokens: Set = [] - - /// Tokens seen during the current render pass. - private var currentRenderTokens: Set = [] - - /// Creates a new lifecycle tracker. - public init() {} - - /// Marks the start of a new render pass. - internal func beginRenderPass() { - lock.lock() - defer { lock.unlock() } - currentRenderTokens.removeAll() - } - - /// Marks the end of a render pass and triggers onDisappear for views that are no longer visible. - internal func endRenderPass(onDisappear: [String: () -> Void]) { - lock.lock() - let disappearedTokens = visibleTokens.subtracting(currentRenderTokens) - for token in disappearedTokens { - appearedTokens.remove(token) - } - visibleTokens = currentRenderTokens - lock.unlock() - - // Execute callbacks outside the lock to avoid deadlocks - for token in disappearedTokens { - onDisappear[token]?() - } - } - - /// Records that a view with the given token appeared. - /// - /// - Parameters: - /// - token: Unique identifier for the view. - /// - action: The onAppear action to execute. - /// - Returns: True if this is the first appearance (action should run). - internal func recordAppear(token: String, action: () -> Void) -> Bool { - lock.lock() - currentRenderTokens.insert(token) - - if !appearedTokens.contains(token) { - appearedTokens.insert(token) - lock.unlock() - action() - return true - } - lock.unlock() - return false - } - - /// Checks if a view has appeared before. - internal func hasAppeared(token: String) -> Bool { - lock.lock() - defer { lock.unlock() } - return appearedTokens.contains(token) - } - - /// Resets all tracking state. - internal func reset() { - lock.lock() - defer { lock.unlock() } - appearedTokens.removeAll() - visibleTokens.removeAll() - currentRenderTokens.removeAll() - } -} - // MARK: - OnAppear Modifier /// A modifier that executes an action when a view first appears. @@ -123,43 +37,6 @@ extension OnAppearModifier: Renderable { // MARK: - OnDisappear Modifier -/// Storage for onDisappear callbacks. -public final class DisappearCallbackStorage: @unchecked Sendable { - @available(*, deprecated, message: "Use TUIContext.lifecycle instead") - public static let shared = DisappearCallbackStorage() - - /// Lock protecting the callbacks dictionary. - private let lock = NSLock() - private var callbacks: [String: () -> Void] = [:] - - /// Creates a new disappear callback storage. - public init() {} - - internal func register(token: String, action: @escaping () -> Void) { - lock.lock() - defer { lock.unlock() } - callbacks[token] = action - } - - internal func unregister(token: String) { - lock.lock() - defer { lock.unlock() } - callbacks.removeValue(forKey: token) - } - - internal var allCallbacks: [String: () -> Void] { - lock.lock() - defer { lock.unlock() } - return callbacks - } - - internal func reset() { - lock.lock() - defer { lock.unlock() } - callbacks.removeAll() - } -} - /// A modifier that executes an action when a view disappears. public struct OnDisappearModifier: View { /// The content view. @@ -212,47 +89,6 @@ public struct TaskModifier: View { } } -/// Storage for running tasks. -public final class TaskStorage: @unchecked Sendable { - @available(*, deprecated, message: "Use TUIContext.lifecycle instead") - public static let shared = TaskStorage() - - /// Lock protecting the tasks dictionary. - private let lock = NSLock() - private var tasks: [String: Task] = [:] - - /// Creates a new task storage. - public init() {} - - internal func startTask(token: String, priority: TaskPriority, operation: @escaping @Sendable () async -> Void) { - lock.lock() - // Cancel existing task if any - tasks[token]?.cancel() - - // Start new task - tasks[token] = Task(priority: priority) { - await operation() - } - lock.unlock() - } - - internal func cancelTask(token: String) { - lock.lock() - tasks[token]?.cancel() - tasks.removeValue(forKey: token) - lock.unlock() - } - - internal func reset() { - lock.lock() - for task in tasks.values { - task.cancel() - } - tasks.removeAll() - lock.unlock() - } -} - extension TaskModifier: Renderable { public func renderToBuffer(context: RenderContext) -> FrameBuffer { let lifecycle = context.tuiContext.lifecycle