luminate-old/Sources/LuminateDI/DIContainer.swift

98 lines
2.7 KiB
Swift

//
// DIContainer.swift
//
// Copyright 2026 Brendan Szymanski <hello@bscubed.dev>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
import Adwaita
import Foundation
import Synchronization
public final class DIContainer: @unchecked Sendable {
public static let shared = DIContainer()
private struct State {
var values = InjectionValues()
var observers: [AnyKeyPath: [UUID: @Sendable () -> Void]] = [:]
}
private let state = Mutex<State>(.init())
public var values: InjectionValues {
state.withLock { $0.values }
}
private init() {}
public func register<T>(
_ keyPath: WritableKeyPath<InjectionValues, T?>,
value: T
) {
state.withLock {
$0.values[keyPath: keyPath] = value
}
notifyObservers(for: keyPath)
}
public func resolve<T>(
_ keyPath: KeyPath<InjectionValues, T?>
) -> T {
state.withLock {
guard let value = $0.values[keyPath: keyPath] else {
fatalError(
"DIContainer: No value registered for \(keyPath). "
+ "Call DIContainer.shared.register(\\.key, value:) during app startup."
)
}
return value
}
}
@discardableResult
func addObserver<T>(
for keyPath: KeyPath<InjectionValues, T?>,
handler: @escaping @Sendable () -> Void
) -> UUID {
let id = UUID()
state.withLock {
$0.observers[keyPath, default: [:]][id] = handler
}
return id
}
func removeObserver(_ id: UUID) {
state.withLock {
for keyPath in $0.observers.keys {
$0.observers[keyPath]?.removeValue(forKey: id)
}
}
}
private func notifyObservers(for keyPath: AnyKeyPath) {
let handlers: [@Sendable () -> Void] = state.withLock {
Array(($0.observers[keyPath] ?? [:]).values)
}
handlers.forEach { $0() }
}
public func reset() {
state.withLock {
$0 = State()
}
}
}