Skip to content

Commit

Permalink
Revert "Drop VergeTiny" (#508)
Browse files Browse the repository at this point in the history
Reverts #504
  • Loading branch information
muukii authored Jan 23, 2025
1 parent 62e1d7f commit fae0c5c
Show file tree
Hide file tree
Showing 3 changed files with 378 additions and 0 deletions.
6 changes: 6 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ let package = Package(
],
products: [
.library(name: "Verge", targets: ["Verge"]),
.library(name: "VergeTiny", targets: ["VergeTiny"]),
.library(name: "VergeNormalizationDerived", targets: ["VergeNormalizationDerived"]),
.library(name: "VergeRx", targets: ["VergeRx"]),
.library(name: "VergeClassic", targets: ["VergeClassic"]),
Expand Down Expand Up @@ -45,6 +46,7 @@ let package = Package(
// macro exports
.target(name: "VergeMacros", dependencies: ["VergeMacrosPlugin"]),

.target(name: "VergeTiny", dependencies: []),
.target(
name: "Verge",
dependencies: [
Expand Down Expand Up @@ -96,6 +98,10 @@ let package = Package(
.enableExperimentalFeature("StrictConcurrency")
]
),
.testTarget(
name: "VergeTinyTests",
dependencies: ["VergeTiny"]
),
.testTarget(
name: "VergeMacrosTests",
dependencies: [
Expand Down
243 changes: 243 additions & 0 deletions Sources/VergeTiny/Source.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
//
// Copyright (c) 2021 Hiroshi Kimura(Muukii) <[email protected]>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import Foundation

public struct DynamicProperty<T> {

public var value: T? {
get {

storage.lock.lock()

// Do not directly cast using `as?`.
guard let value = storage.synchronized_value(for: key) else {
storage.lock.unlock()
return nil
}

storage.lock.unlock()

return value as? T
}
nonmutating set {
storage.lock.lock()
storage.synchronized_setValue(newValue as Any, for: key)
storage.lock.unlock()
}
}

public let key: String
private let storage: DynamicPropertyStorage.Storage<Any>

init(key: String, storage: DynamicPropertyStorage.Storage<Any>) {
self.key = key
self.storage = storage
}

private func _doIfChanged(
value: T,
compare: (T, T) -> Bool,
perform: (T) -> Void
) {

storage.lock.lock()

// Do not directly cast using `as?`.
// To detect storage does not have value for key when T is optinal type.
guard let cachedRawValue = storage.synchronized_value(for: key) else {
storage.synchronized_setValue(value, for: key)
storage.lock.unlock()
perform(value)
return
}

let cachedValue = cachedRawValue as! T

guard compare(cachedValue, value) == false else {
storage.lock.unlock()
return
}

storage.synchronized_setValue(value, for: key)

storage.lock.unlock()

perform(value)

}

/// [Experimental]
/// should be renamed
public func doIfChanged(
_ value: T,
_ compare: (T, T) -> Bool,
_ perform: (T) -> Void
) {

_doIfChanged(
value: value,
compare: compare,
perform: perform
)

}

/// [Experimental]
/// should be renamed
public func doIfChanged(
_ value: T,
_ perform: (T) -> Void
) where T : Equatable {

_doIfChanged(
value: value,
compare: ==,
perform: perform
)

}

}

public final class DynamicPropertyStorage {

final class Storage<T> {

let lock = NSLock()

var rawStorage: [String : T] = [:]

func synchronized_value(for key: String) -> T? {

guard let value = rawStorage[key] else {
return nil
}

return value
}

func synchronized_setValue(_ value: T, for key: String) {
rawStorage[key] = value
}

func synchronized_removeValue(for key: String) {
rawStorage.removeValue(forKey: key)
}
}

struct CodeLocation: Hashable {
let file: String
let line: UInt
let column: UInt

func makeKey() -> String {
"\(file).\(line).\(column)"
}
}

private let _anyStorage: Storage<Any> = .init()

public init() {

}

public func defineProperty<T>(
file: StaticString = #file,
line: UInt = #line,
column: UInt = #column,
_ type: T.Type
) -> DynamicProperty<T> {

let codeLocation = CodeLocation(file: file.description, line: line, column: column)

return .init(key: codeLocation.makeKey(), storage: _anyStorage)

}

/// [Experimental]
/// should be renamed
public func doIfChanged<T>(
file: StaticString = #file,
line: UInt = #line,
column: UInt = #column,
_ value: T,
_ compare: (T, T) -> Bool,
_ perform: (T) -> Void
) {

let property = defineProperty(file: file, line: line, column: column, T.self)

property.doIfChanged(
value,
compare,
perform
)

}

/// [Experimental]
/// should be renamed
public func doIfChanged<T>(
file: StaticString = #file,
line: UInt = #line,
column: UInt = #column,
_ value: T,
_ perform: (T) -> Void
) where T : Equatable {

let property = defineProperty(file: file, line: line, column: column, T.self)

property.doIfChanged(
value,
perform
)

}

}

nonisolated(unsafe) private var _storageKey: Void?

extension NSObject {

public var associatedProperties: DynamicPropertyStorage {

objc_sync_enter(self)
defer {
objc_sync_exit(self)
}

if let associated = objc_getAssociatedObject(self, &_storageKey)
as? DynamicPropertyStorage
{
return associated
} else {
let associated = DynamicPropertyStorage()
objc_setAssociatedObject(self, &_storageKey, associated, .OBJC_ASSOCIATION_RETAIN)
return associated
}

}

}


Loading

0 comments on commit fae0c5c

Please sign in to comment.