diff --git a/Sources/OpenSwiftUI/DataAndStorage/Preferences/Internal/HostPreferenceKey.swift b/Sources/OpenSwiftUI/DataAndStorage/Preferences/Internal/HostPreferenceKey.swift index d8d88e1a..c6ffdc2d 100644 --- a/Sources/OpenSwiftUI/DataAndStorage/Preferences/Internal/HostPreferenceKey.swift +++ b/Sources/OpenSwiftUI/DataAndStorage/Preferences/Internal/HostPreferenceKey.swift @@ -7,3 +7,7 @@ // Status: Complete protocol HostPreferenceKey: PreferenceKey {} + +extension HostPreferenceKey { + static var _isReadableByHost: Bool { true } +} diff --git a/Sources/OpenSwiftUI/DataAndStorage/Preferences/Internal/HostPreferencesKey.swift b/Sources/OpenSwiftUI/DataAndStorage/Preferences/Internal/HostPreferencesKey.swift new file mode 100644 index 00000000..015d63f7 --- /dev/null +++ b/Sources/OpenSwiftUI/DataAndStorage/Preferences/Internal/HostPreferencesKey.swift @@ -0,0 +1,20 @@ +// +// HostPreferencesKey.swift +// OpenSwiftUI +// +// Created by Kyle on 2023/1/6. +// Lastest Version: iOS 15.5 +// Status: WIP +// ID: 7429200566949B8FB892A77E01A988C8 + +struct HostPreferencesKey: PreferenceKey { + static var defaultValue: PreferenceList { + PreferenceList() + } + + static func reduce(value: inout PreferenceList, nextValue: () -> PreferenceList) { + // TODO: + } + + private static var nodeId: UInt32 = .zero +} diff --git a/Sources/OpenSwiftUI/DataAndStorage/Preferences/Internal/PreferenceList.swift b/Sources/OpenSwiftUI/DataAndStorage/Preferences/Internal/PreferenceList.swift index 916b4631..4949c9cb 100644 --- a/Sources/OpenSwiftUI/DataAndStorage/Preferences/Internal/PreferenceList.swift +++ b/Sources/OpenSwiftUI/DataAndStorage/Preferences/Internal/PreferenceList.swift @@ -4,14 +4,76 @@ // // Created by Kyle on 2023/1/5. // Lastest Version: iOS 15.5 -// Status: WIP +// Status: Complete +// ID: C1C63C2F6F2B9F3EB30DD747F0605FBD -struct PreferenceList { +struct PreferenceList: CustomStringConvertible { private var first: PreferenceNode? - + subscript(_ keyType: Key.Type) -> Value { - get { fatalError("TODO") } - set { fatalError("TODO") } + get { + guard let first, + let node = first.find(key: keyType) else { + return Value(value: keyType.defaultValue, seed: .zero) + } + return Value(value: node.value, seed: node.seed) + } + set { + if let first, + let _ = first.find(key: keyType) { + removeValue(for: keyType) + } + first = _PreferenceNode(value: newValue.value, seed: newValue.seed, next: first) + } + } + + mutating func removeValue(for keyType: Key.Type) { + let first = first + self.first = nil + first?.forEach { node in + guard node.keyType != keyType else { + return + } + self.first = node.copy(next: self.first) + } + } + + func valueIfPresent(_ keyType: Key.Type) -> Value? { + guard let first else { + return nil + } + return first.find(key: keyType).map { node in + Value(value: node.value, seed: node.seed) + } + } + + func contains(_ keyType: Key.Type) -> Bool { + first?.find(key: keyType) != nil + } + + mutating func modifyValue(for keyType: Key.Type, transform: Value < (inout Key.Value) -> Void>) { + var value = self[keyType] + value.seed = value.seed.merge(transform.seed) + transform.value(&value.value) + removeValue(for: keyType) + first = _PreferenceNode(value: value.value, seed: value.seed, next: first) + } + + var description: String { + var description = "\((first?.mergedSeed ?? .zero).description): [" + var currentNode = first + var shouldAddSeparator = false + while let node = currentNode { + if shouldAddSeparator { + description.append(", ") + } else { + shouldAddSeparator = true + } + description.append(node.description) + currentNode = node.next + } + description.append("]") + return description } } @@ -22,13 +84,93 @@ extension PreferenceList { } } -private class PreferenceNode { +private class PreferenceNode: CustomStringConvertible { let keyType: Any.Type let seed: VersionSeed let mergedSeed: VersionSeed let next: PreferenceNode? init(keyType: Any.Type, seed: VersionSeed, next: PreferenceNode?) { - fatalError("TODO") + self.keyType = keyType + self.seed = seed + let seedResult: VersionSeed = if let next { + next.mergedSeed.merge(seed) + } else { + seed + } + self.mergedSeed = seedResult + self.next = next + } + + final func forEach(_ body: (PreferenceNode) -> Void) { + var node = self + repeat { + body(node) + if let next = node.next { + node = next + } else { + break + } + } while true + } + + final func find(key: Key.Type) -> _PreferenceNode? { + var node = self + repeat { + if node.keyType == key { + return node as? _PreferenceNode + } else { + if let next = node.next { + node = next + } else { + break + } + } + } while true + return nil + } + + func find(from _: PreferenceNode?) -> PreferenceNode? { fatalError() } + func combine(from _: PreferenceNode?, next _: PreferenceNode?) -> PreferenceNode? { fatalError() } + func copy(next _: PreferenceNode?) -> PreferenceNode { fatalError() } + var description: String { fatalError() } +} + +private class _PreferenceNode: PreferenceNode { + let value: Key.Value + + init(value: Key.Value, seed: VersionSeed, next: PreferenceNode?) { + self.value = value + super.init(keyType: Key.self, seed: seed, next: next) + } + + override func find(from: PreferenceNode?) -> PreferenceNode? { + from?.find(key: Key.self) + } + + override func combine(from: PreferenceNode?, next: PreferenceNode?) -> PreferenceNode? { + var currentNode = from + while let node = currentNode { + if keyType == node.keyType { + var value = self.value + var seed = self.seed + Key.reduce(value: &value) { + seed = seed.merge(node.seed) + return (node as! _PreferenceNode).value + } + return _PreferenceNode(value: value, seed: seed, next: next) + } else { + currentNode = node.next + } + } + return nil + } + + override func copy(next: PreferenceNode?) -> PreferenceNode { + _PreferenceNode(value: value, seed: seed, next: next) + } + + override var description: String { + "\(Key.self) = \(value)" } } diff --git a/Sources/OpenSwiftUI/Internal/Graph/VersionSeed.swift b/Sources/OpenSwiftUI/Internal/Graph/VersionSeed.swift index 0859e31f..af112ce2 100644 --- a/Sources/OpenSwiftUI/Internal/Graph/VersionSeed.swift +++ b/Sources/OpenSwiftUI/Internal/Graph/VersionSeed.swift @@ -22,6 +22,34 @@ struct VersionSeed: CustomStringConvertible { static var invalid: VersionSeed { VersionSeed(value: .max) } var isValid: Bool { value != VersionSeed.invalid.value } + + @_transparent + @inline(__always) + func merge(_ seed: VersionSeed) -> VersionSeed { + if isValid, seed.value == .zero { + self + } else if value == .zero, seed.isValid { + seed + } else { + VersionSeed(value: merge32(value, seed.value)) + } + } +} + +private func merge32(_ a: UInt32, _ b: UInt32) -> UInt32 { + let a = UInt64(a) + let b = UInt64(b) + var c = b + c &+= .max ^ (c &<< 32) + c &+= a &<< 32 + c ^= (c &>> 22) + c &+= .max ^ (c &<< 13) + c ^= (c &>> 8) + c &+= (c &<< 3) + c ^= (c >> 15) + c &+= .max ^ (c &<< 27) + c ^= (c &>> 31) + return UInt32(truncatingIfNeeded: c) } struct VersionSeedTracker { diff --git a/Tests/OpenSwiftUITests/Internal/VersionSeed/VersionSeedTests.swift b/Tests/OpenSwiftUITests/Internal/VersionSeed/VersionSeedTests.swift index c6d34ef2..6fe238a3 100644 --- a/Tests/OpenSwiftUITests/Internal/VersionSeed/VersionSeedTests.swift +++ b/Tests/OpenSwiftUITests/Internal/VersionSeed/VersionSeedTests.swift @@ -18,4 +18,33 @@ struct VersionSeedTests { let seed = VersionSeed(value: value) #expect(seed.description == expectedDescription) } + + @Test(arguments: [ + (0x0000_0000, 0x0000_0000, 0x0000_0000), + (0xFFFF_FFFF, 0x0000_0000, 0x17F8_3A02), + (0xFFFF_FFFF, 0xFFFF_FFFF, 0x3258_2C16), + (0xAABB_CCDD, 0x0000_0000, 0xAABB_CCDD), + (0x0000_0000, 0xAABB_CCDD, 0xAABB_CCDD), + (0xAABB_CCDD, 0xAABB_CCDD, 0x1AAD_F11C), + (0xFFFF_FFFF, 0xAABB_CCDD, 0x4C96_0643), + (0xAABB_CCDD, 0xFFFF_FFFF, 0x13DA_25CE), + (0x0000_0001, 0x0001_0000, 0x8621_ACD2), + (0x0001_0000, 0x0000_0001, 0xD5E2_C632), + (0x1000_0000, 0x0000_0001, 0xE6E8_7354), + (0x0000_0001, 0x1000_0000, 0xAE49_4475), + ]) + func merge(_ a: UInt32, _ b: UInt32, _ c: UInt32) { + let seedA = VersionSeed(value: a) + let seedB = VersionSeed(value: b) + #expect(seedA.merge(seedB).value == c) + } +} + +extension UInt32: CustomTestStringConvertible { + public var testDescription: String { hex } + private var hex: String { + let high = UInt16(truncatingIfNeeded: self &>> 16) + let low = UInt16(truncatingIfNeeded: self) + return String(format: "0x%04X_%04X", high, low) + } }