From 664fbacf8be22dd53a84e062fe678dd1d81aed74 Mon Sep 17 00:00:00 2001 From: Kyle Date: Tue, 2 Apr 2024 00:22:03 +0800 Subject: [PATCH] Update Preference System (#56) * Update NextUpdate * Add PreferenceInputs and PreferenceOutputs * Update PreferenceBridge implementation * Update VersionSeed implementation * Add HostPreferencesCombiner implementation * Add PreferenceCombiner implementation * Remove unnecessary @_transparent * Add wrapInputs and wrapOutputs * Update HostPreferenceKey call * Fix Linux build issue --- .../Core/Attribute/AsyncAttribute.swift | 4 + .../OpenSwiftUI/Core/Data/BloomFilter.swift | 1 - .../Core/Data/Property/PropertyList.swift | 2 - .../OpenSwiftUI/Core/Data/VersionSeed.swift | 56 +++-- .../OpenSwiftUI/Core/Render/DisplayList.swift | 13 ++ .../Core/View/TODO/_ViewInputs.swift | 14 +- .../Core/View/TODO/_ViewOutputs.swift | 12 +- Sources/OpenSwiftUI/Core/View/ViewGraph.swift | 33 ++- .../Data/Preference/HostPreferenceKey.swift | 12 - .../Data/Preference/HostPreferencesKey.swift | 12 +- .../Data/Preference/PreferenceBridge.swift | 210 ++++++++++++++++++ .../Data/Preference/PreferenceKey.swift | 6 + .../Data/Preference/PreferenceKeys.swift | 37 ++- .../Data/Preference/PreferenceList.swift | 76 +++---- .../Data/Preference/PreferencesCombiner.swift | 156 +++++++++++++ .../Data/Preference/PreferencesInputs.swift | 30 +++ .../Data/Preference/PreferencesOutputs.swift | 70 ++++++ .../Data/Preference/View_Preference.swift | 15 ++ .../Core/Data/VersionSeedTests.swift | 10 +- .../Data/Preference/PreferenceListTests.swift | 10 +- 20 files changed, 670 insertions(+), 109 deletions(-) create mode 100644 Sources/OpenSwiftUI/Core/Render/DisplayList.swift delete mode 100644 Sources/OpenSwiftUI/Data/Preference/HostPreferenceKey.swift create mode 100644 Sources/OpenSwiftUI/Data/Preference/PreferenceBridge.swift create mode 100644 Sources/OpenSwiftUI/Data/Preference/PreferencesCombiner.swift create mode 100644 Sources/OpenSwiftUI/Data/Preference/PreferencesInputs.swift create mode 100644 Sources/OpenSwiftUI/Data/Preference/PreferencesOutputs.swift diff --git a/Sources/OpenSwiftUI/Core/Attribute/AsyncAttribute.swift b/Sources/OpenSwiftUI/Core/Attribute/AsyncAttribute.swift index a92582b..20df3bd 100644 --- a/Sources/OpenSwiftUI/Core/Attribute/AsyncAttribute.swift +++ b/Sources/OpenSwiftUI/Core/Attribute/AsyncAttribute.swift @@ -9,6 +9,10 @@ internal import OpenGraphShims protocol AsyncAttribute: _AttributeBody {} +extension AsyncAttribute { + static var flags: OGAttributeTypeFlags { [] } +} + extension Attribute { func syncMainIfReferences(do body: (Value) -> V) -> V { fatalError("TODO") diff --git a/Sources/OpenSwiftUI/Core/Data/BloomFilter.swift b/Sources/OpenSwiftUI/Core/Data/BloomFilter.swift index 18c16d9..e2a733a 100644 --- a/Sources/OpenSwiftUI/Core/Data/BloomFilter.swift +++ b/Sources/OpenSwiftUI/Core/Data/BloomFilter.swift @@ -22,7 +22,6 @@ struct BloomFilter: Equatable { self.init(hashValue: Int(bitPattern: pointer)) } - @_transparent @inline(__always) func match(_ filter: BloomFilter) -> Bool { (value & filter.value) == value diff --git a/Sources/OpenSwiftUI/Core/Data/Property/PropertyList.swift b/Sources/OpenSwiftUI/Core/Data/Property/PropertyList.swift index 8800ab6..8b5df40 100644 --- a/Sources/OpenSwiftUI/Core/Data/Property/PropertyList.swift +++ b/Sources/OpenSwiftUI/Core/Data/Property/PropertyList.swift @@ -383,7 +383,6 @@ extension PropertyList { // MARK: - PropertyList.Tracker Helper functions -@_transparent @inline(__always) private func match(data: TrackerData, plist: PropertyList) -> Bool { if let elements = plist.elements, @@ -396,7 +395,6 @@ private func match(data: TrackerData, plist: PropertyList) -> Bool { } } -@_transparent @inline(__always) private func match(data: TrackerData, from: PropertyList, to: PropertyList) -> UniqueID? { if let fromElement = from.elements, diff --git a/Sources/OpenSwiftUI/Core/Data/VersionSeed.swift b/Sources/OpenSwiftUI/Core/Data/VersionSeed.swift index d20e837..b6b28e3 100644 --- a/Sources/OpenSwiftUI/Core/Data/VersionSeed.swift +++ b/Sources/OpenSwiftUI/Core/Data/VersionSeed.swift @@ -8,30 +8,44 @@ struct VersionSeed: CustomStringConvertible { var value: UInt32 - + var description: String { switch value { - case VersionSeed.zero.value: "empty" + case VersionSeed.empty.value: "empty" case VersionSeed.invalid.value: "invalid" default: value.description } } - - static var zero: VersionSeed { VersionSeed(value: .zero) } + + @inline(__always) + static var empty: VersionSeed { VersionSeed(value: .zero) } + + @inline(__always) static var invalid: VersionSeed { VersionSeed(value: .max) } - - var isValid: Bool { value != VersionSeed.invalid.value } - - @_transparent + + @inline(__always) + var isInvalid: Bool { value == VersionSeed.invalid.value } + + @inline(__always) + var isEmpty: Bool { value == VersionSeed.empty.value } + @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)) + mutating func merge(_ other: VersionSeed) { + guard !isInvalid, !other.isEmpty else { + return } + guard !isEmpty, !other.isInvalid else { + self = other + return + } + self = VersionSeed(value: merge32(value, other.value)) + } + + @inline(__always) + func merging(_ seed: VersionSeed) -> VersionSeed { + var newValue = self + newValue.merge(seed) + return newValue } } @@ -57,11 +71,11 @@ struct VersionSeedTracker { struct VersionSeedSetTracker { private var values: [Value] - + mutating func addPreference(_: Key.Type) { values.append(Value(key: _AnyPreferenceKey.self, seed: .invalid)) } - + mutating func updateSeeds(to preferences: PreferenceList) { for index in values.indices { var visitor = UpdateSeedVisitor(preferences: preferences, seed: nil) @@ -86,17 +100,17 @@ extension VersionSeedSetTracker { let preferences: PreferenceList var seed: VersionSeed var matches: Bool? - + mutating func visit(key: (some PreferenceKey).Type) { let valueSeed = preferences[key].seed - matches = seed.isValid && valueSeed.isValid && seed.value == valueSeed.value + matches = !seed.isInvalid && !valueSeed.isInvalid && seed.value == valueSeed.value } } - + private struct UpdateSeedVisitor: PreferenceKeyVisitor { let preferences: PreferenceList var seed: VersionSeed? - + mutating func visit(key: (some PreferenceKey).Type) { seed = preferences[key].seed } diff --git a/Sources/OpenSwiftUI/Core/Render/DisplayList.swift b/Sources/OpenSwiftUI/Core/Render/DisplayList.swift new file mode 100644 index 0000000..70e320e --- /dev/null +++ b/Sources/OpenSwiftUI/Core/Render/DisplayList.swift @@ -0,0 +1,13 @@ +// FIXME +struct DisplayList {} + +// FIXME +extension DisplayList { + struct Key: PreferenceKey { + static var defaultValue: Void = () + + static func reduce(value _: inout Void, nextValue _: () -> Void) {} + + typealias Value = Void + } +} diff --git a/Sources/OpenSwiftUI/Core/View/TODO/_ViewInputs.swift b/Sources/OpenSwiftUI/Core/View/TODO/_ViewInputs.swift index adf71fa..e0b698d 100644 --- a/Sources/OpenSwiftUI/Core/View/TODO/_ViewInputs.swift +++ b/Sources/OpenSwiftUI/Core/View/TODO/_ViewInputs.swift @@ -1,9 +1,11 @@ +internal import OpenGraphShims + public struct _ViewInputs { var base: _GraphInputs -// var preferences : PreferencesInputs -// var transform : Attribute -// var position : Attribute -// var containerPosition : Attribute -// var size : Attribute -// var safeAreaInsets : OptionalAttribute + var preferences: PreferencesInputs + var transform: Attribute + var position: Attribute + var containerPosition: Attribute + var size: Attribute + // var safeAreaInsets: OptionalAttribute } diff --git a/Sources/OpenSwiftUI/Core/View/TODO/_ViewOutputs.swift b/Sources/OpenSwiftUI/Core/View/TODO/_ViewOutputs.swift index abdb68d..e1ed7f7 100644 --- a/Sources/OpenSwiftUI/Core/View/TODO/_ViewOutputs.swift +++ b/Sources/OpenSwiftUI/Core/View/TODO/_ViewOutputs.swift @@ -1,4 +1,12 @@ +internal import OpenGraphShims + public struct _ViewOutputs { -// var preferences : PreferencesOutputs -// var _layoutComputer : OptionalAttribute + private var preferences = PreferencesOutputs() + @OptionalAttribute + var layoutComputer: LayoutComputer? + + subscript(_ keyType: Key.Type) -> Attribute? { + get { preferences[keyType] } + set { preferences[keyType] = newValue } + } } diff --git a/Sources/OpenSwiftUI/Core/View/ViewGraph.swift b/Sources/OpenSwiftUI/Core/View/ViewGraph.swift index 399b215..e4f204a 100644 --- a/Sources/OpenSwiftUI/Core/View/ViewGraph.swift +++ b/Sources/OpenSwiftUI/Core/View/ViewGraph.swift @@ -41,6 +41,15 @@ final class ViewGraph: GraphHost { var mainUpdates: Int = 0 var needsFocusUpdate: Bool = false var nextUpdate: (views: NextUpdate, gestures: NextUpdate) = (NextUpdate(time: .infinity), NextUpdate(time: .infinity)) + private weak var _preferenceBridge: PreferenceBridge? + var preferenceBridge: PreferenceBridge? { + get { _preferenceBridge } + // FIXME: TO BE CONFIRMED + set { setPreferenceBridge(to: newValue, isInvalidating: newValue == nil) } + } + #if canImport(Darwin) // FIXME: See #39 + var bridgedPreferences: [(AnyPreferenceKey.Type, OGAttribute)] = [] + #endif // TODO init(rootViewType: Body.Type, requestedOutputs: Outputs) { @@ -148,6 +157,14 @@ final class ViewGraph: GraphHost { return [] } + func clearPreferenceBridge() { + setPreferenceBridge(to: nil, isInvalidating: true) + } + + private func setPreferenceBridge(to bridge: PreferenceBridge?, isInvalidating: Bool) { + // TODO + } + // MARK: - Override Methods override var graphDelegate: GraphDelegate? { delegate } @@ -165,7 +182,7 @@ final class ViewGraph: GraphHost { } override func timeDidChange() { - // TODO + nextUpdate.views = NextUpdate(time: .infinity) } override func isHiddenForReuseDidChange() { @@ -185,6 +202,20 @@ extension ViewGraph { _interval = .infinity reasons = [] } + + // TODO: AnimatorState.nextUpdate + mutating func interval(_ value: Double, reason: UInt32?) { + if value == .zero { + if _interval > 1 / 60 { + _interval = .infinity + } + } else { + _interval = min(value, _interval) + } + if let reason { + reasons.insert(reason) + } + } } } diff --git a/Sources/OpenSwiftUI/Data/Preference/HostPreferenceKey.swift b/Sources/OpenSwiftUI/Data/Preference/HostPreferenceKey.swift deleted file mode 100644 index 7f0fcc4..0000000 --- a/Sources/OpenSwiftUI/Data/Preference/HostPreferenceKey.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// HostPreferenceKey.swift -// OpenSwiftUI -// -// Audited for RELEASE_2021 -// Status: Complete - -protocol HostPreferenceKey: PreferenceKey {} - -extension HostPreferenceKey { - static var _isReadableByHost: Bool { true } -} diff --git a/Sources/OpenSwiftUI/Data/Preference/HostPreferencesKey.swift b/Sources/OpenSwiftUI/Data/Preference/HostPreferencesKey.swift index fc73bce..b680f90 100644 --- a/Sources/OpenSwiftUI/Data/Preference/HostPreferencesKey.swift +++ b/Sources/OpenSwiftUI/Data/Preference/HostPreferencesKey.swift @@ -7,8 +7,18 @@ // ID: 7429200566949B8FB892A77E01A988C8 struct HostPreferencesKey: PreferenceKey { - private static var nodeId: UInt32 = .zero + static var defaultValue: PreferenceList { + PreferenceList() + } + static func reduce(value: inout PreferenceList, nextValue: () -> PreferenceList) { + value.merge(nextValue()) + } +} + +extension HostPreferencesKey { + private static var nodeId: UInt32 = .zero + @inline(__always) static func makeNodeID() -> UInt32 { defer { nodeId &+= 1 } diff --git a/Sources/OpenSwiftUI/Data/Preference/PreferenceBridge.swift b/Sources/OpenSwiftUI/Data/Preference/PreferenceBridge.swift new file mode 100644 index 0000000..2ce1242 --- /dev/null +++ b/Sources/OpenSwiftUI/Data/Preference/PreferenceBridge.swift @@ -0,0 +1,210 @@ +// +// PreferenceBridge.swift +// OpenSwiftUI +// +// Audited for RELEASE_2021 +// Status: Complete +// ID: A9FAE381E99529D5274BA37A9BC9B074 + +internal import OpenGraphShims + +final class PreferenceBridge { + unowned let viewGraph: ViewGraph + private var children: [Unmanaged] = [] + var requestedPreferences = PreferenceKeys() + var bridgedViewInputs = PropertyList() + @WeakAttribute var hostPreferenceKeys: PreferenceKeys? + @WeakAttribute var hostPreferencesCombiner: PreferenceList? + private var bridgedPreferences: [BridgedPreference] = [] + + struct BridgedPreference { + var key: AnyPreferenceKey.Type + var combiner: OGWeakAttribute + } + + init() { + viewGraph = GraphHost.currentHost as! ViewGraph + } + + #if canImport(Darwin) // FIXME: See #39 + func addValue(_ value: OGAttribute, for keyType: AnyPreferenceKey.Type) { + struct AddValue: PreferenceKeyVisitor { + var combiner: OGAttribute + var value: OGAttribute + func visit(key _: Key.Type) { + combiner.mutateBody( + as: PreferenceCombiner.self, + invalidating: true + ) { combiner in + combiner.attributes.append(WeakAttribute(base: OGWeakAttribute(value))) + } + } + } + guard let bridgedPreference = bridgedPreferences.first(where: { $0.key == keyType }) else { + return + } + guard let combiner = bridgedPreference.combiner.attribute else { + return + } + var visitor = AddValue(combiner: combiner, value: value) + keyType.visitKey(&visitor) + viewGraph.graphInvalidation(from: value) + } + + func removeValue(_ value: OGAttribute, for keyType: AnyPreferenceKey.Type, isInvalidating: Bool) { + struct RemoveValue: PreferenceKeyVisitor { + var combiner: OGAttribute + var value: OGAttribute + var changed = false + mutating func visit(key _: Key.Type) { + combiner.mutateBody( + as: PreferenceCombiner.self, + invalidating: true + ) { combiner in + guard let index = combiner.attributes.firstIndex(where: { $0.attribute?.identifier == value }) else { + return + } + combiner.attributes.remove(at: index) + changed = true + } + } + } + guard let bridgedPreference = bridgedPreferences.first(where: { $0.key == keyType }) else { + return + } + guard let combiner = bridgedPreference.combiner.attribute else { + return + } + var visitor = RemoveValue(combiner: combiner, value: value) + keyType.visitKey(&visitor) + if visitor.changed { + viewGraph.graphInvalidation(from: isInvalidating ? nil : value) + } + } + + func addHostValue(_ values: Attribute, for keys: Attribute) { + guard let combiner = $hostPreferencesCombiner else { + return + } + combiner.mutateBody( + as: HostPreferencesCombiner.self, + invalidating: true + ) { combiner in + combiner.addChild(keys: keys, values: values) + } + } + + func removeHostValue(for keys: Attribute, isInvalidating: Bool) { + guard let combiner = $hostPreferencesCombiner else { + return + } + var hasRemoved = false + combiner.mutateBody( + as: HostPreferencesCombiner.self, + invalidating: true + ) { combiner in + guard let index = combiner.children.firstIndex(where: { $0.$keys == keys }) else { + hasRemoved = false + return + } + combiner.children.remove(at: index) + hasRemoved = true + } + if hasRemoved { + viewGraph.graphInvalidation(from: isInvalidating ? nil : keys.identifier) + } + } + + /// Append `child` to `children` property if `child` has not been on `children`. + /// - Parameter child: The ``ViewGraph`` instance to be added + func addChild(_ child: ViewGraph) { + guard !children.contains(where: { $0.takeUnretainedValue() === child }) else { + return + } + children.append(.passUnretained(child)) + } + + /// Remove `child` from `children` property if `child` has been on `children` + /// - Parameter child: The ``ViewGraph`` instance to be removed + func removeChild(_ child: ViewGraph) { + guard let index = children.firstIndex(where: { $0.takeUnretainedValue() === child }) else { + return + } + children.remove(at: index) + } + + func removedStateDidChange() { + for child in children { + let viewGraph = child.takeUnretainedValue() + viewGraph.updateRemovedState() + } + } + + func invalidate() { + requestedPreferences = PreferenceKeys() + bridgedViewInputs = PropertyList() + for child in children { + let viewGraph = child.takeRetainedValue() + viewGraph.preferenceBridge = nil + child.release() + } + } + + func wrapInputs(_ inputs: inout _ViewInputs) { + inputs.base.customInputs = bridgedViewInputs + inputs.preferences.merge(requestedPreferences) + inputs.preferences.hostKeys = Attribute(MergePreferenceKeys(lhs: inputs.preferences.hostKeys, rhs: _hostPreferenceKeys)) + } + + func wrapOutputs(_ outputs: inout PreferencesOutputs, inputs: _ViewInputs) { + struct MakeCombiner: PreferenceKeyVisitor { + var result: OGAttribute? + + mutating func visit(key _: Key.Type) where Key: PreferenceKey { + result = Attribute(PreferenceCombiner(attributes: [])).identifier + } + } + bridgedViewInputs = inputs.base.customInputs + for key in inputs.preferences.keys { + if key == _AnyPreferenceKey.self { + let combiner = Attribute(HostPreferencesCombiner( + keys: inputs.preferences.hostKeys, + values: OptionalAttribute(base: AnyOptionalAttribute(outputs[anyKey: key])), + children: [] + )) + outputs[anyKey: key] = combiner.identifier + $hostPreferenceKeys = inputs.preferences.hostKeys + $hostPreferencesCombiner = combiner + } else { + guard !outputs.contains(key) else { + continue + } + var visitor = MakeCombiner() + key.visitKey(&visitor) + guard let combiner = visitor.result else { + continue + } + if !requestedPreferences.contains(key) { + requestedPreferences.add(key) + } + bridgedPreferences.append(BridgedPreference(key: key, combiner: OGWeakAttribute(combiner))) + outputs[anyKey: key] = combiner + } + } + } + #endif +} + +private struct MergePreferenceKeys: Rule, AsyncAttribute { + @Attribute var lhs: PreferenceKeys + @WeakAttribute var rhs: PreferenceKeys? + + var value: PreferenceKeys { + var result = lhs + guard let rhs else { + return result + } + result.merge(rhs) + return result + } +} diff --git a/Sources/OpenSwiftUI/Data/Preference/PreferenceKey.swift b/Sources/OpenSwiftUI/Data/Preference/PreferenceKey.swift index ec46740..9a00bf5 100644 --- a/Sources/OpenSwiftUI/Data/Preference/PreferenceKey.swift +++ b/Sources/OpenSwiftUI/Data/Preference/PreferenceKey.swift @@ -48,3 +48,9 @@ extension PreferenceKey { public static var _isReadableByHost: Bool { false } } + +protocol HostPreferenceKey: PreferenceKey {} + +extension HostPreferenceKey { + static var _isReadableByHost: Bool { true } +} diff --git a/Sources/OpenSwiftUI/Data/Preference/PreferenceKeys.swift b/Sources/OpenSwiftUI/Data/Preference/PreferenceKeys.swift index 69b8b5a..53b9508 100644 --- a/Sources/OpenSwiftUI/Data/Preference/PreferenceKeys.swift +++ b/Sources/OpenSwiftUI/Data/Preference/PreferenceKeys.swift @@ -6,11 +6,9 @@ // Status: Complete struct PreferenceKeys { - private var keys: [AnyPreferenceKey.Type] + private var keys: [AnyPreferenceKey.Type] = [] - init() { - self.keys = [] - } + init() {} } extension PreferenceKeys: RandomAccessCollection, MutableCollection { @@ -25,14 +23,6 @@ extension PreferenceKeys: RandomAccessCollection, MutableCollection { keys.append(key) } - func contains(_: Key.Type) -> Bool { - contains(_AnyPreferenceKey.self) - } - - func contains(_ key: AnyPreferenceKey.Type) -> Bool { - keys.contains { $0 == key } - } - mutating func remove(_: Key.Type) { remove(_AnyPreferenceKey.self) } @@ -46,12 +36,35 @@ extension PreferenceKeys: RandomAccessCollection, MutableCollection { } } + func contains(_: Key.Type) -> Bool { + contains(_AnyPreferenceKey.self) + } + + func contains(_ key: AnyPreferenceKey.Type) -> Bool { + keys.contains { $0 == key } + } + var isEmpty: Bool { keys.isEmpty } subscript(position: Int) -> AnyPreferenceKey.Type { get { keys[position] } set { keys[position] = newValue } } + + mutating func merge(_ preferenceKeys: PreferenceKeys) { + for key in preferenceKeys.keys { + guard !contains(key) else { + continue + } + add(key) + } + } + + func merging(_ preferenceKeys: PreferenceKeys) -> PreferenceKeys { + var result = self + result.merge(preferenceKeys) + return result + } } extension PreferenceKeys: Equatable { diff --git a/Sources/OpenSwiftUI/Data/Preference/PreferenceList.swift b/Sources/OpenSwiftUI/Data/Preference/PreferenceList.swift index c036861..d1565cd 100644 --- a/Sources/OpenSwiftUI/Data/Preference/PreferenceList.swift +++ b/Sources/OpenSwiftUI/Data/Preference/PreferenceList.swift @@ -15,7 +15,7 @@ struct PreferenceList: CustomStringConvertible { get { guard let first, let node = first.find(key: keyType) else { - return Value(value: keyType.defaultValue, seed: .zero) + return Value(value: keyType.defaultValue, seed: .empty) } return Value(value: node.value, seed: node.seed) } @@ -54,14 +54,14 @@ struct PreferenceList: CustomStringConvertible { mutating func modifyValue(for keyType: Key.Type, transform: Value < (inout Key.Value) -> Void>) { var value = self[keyType] - value.seed = value.seed.merge(transform.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 description = "\((first?.mergedSeed ?? .empty).description): [" var currentNode = first var shouldAddSeparator = false while let node = currentNode { @@ -76,12 +76,42 @@ struct PreferenceList: CustomStringConvertible { description.append("]") return description } + + @inline(__always) + mutating func merge(_ other: PreferenceList) { + guard let otherFirst = other.first else { + return + } + guard let selfFirst = first else { + first = otherFirst + return + } + first = nil + selfFirst.forEach { node in + if let mergedNode = node.combine(from: otherFirst, next: first) { + first = mergedNode + } else { + first = node.copy(next: first) + } + } + otherFirst.forEach { node in + guard node.find(from: selfFirst) == nil else { + return + } + first = node.copy(next: first) + } + } } extension PreferenceList { struct Value { var value: V var seed: VersionSeed + + init(value: V, seed: VersionSeed) { + self.value = value + self.seed = seed + } } } @@ -94,12 +124,7 @@ private class PreferenceNode: CustomStringConvertible { init(keyType: Any.Type, seed: VersionSeed, next: PreferenceNode?) { self.keyType = keyType self.seed = seed - let seedResult: VersionSeed = if let next { - next.mergedSeed.merge(seed) - } else { - seed - } - self.mergedSeed = seedResult + self.mergedSeed = next?.mergedSeed.merging(seed) ?? seed self.next = next } @@ -156,7 +181,7 @@ private class _PreferenceNode: PreferenceNode { var value = self.value var seed = self.seed Key.reduce(value: &value) { - seed = seed.merge(node.seed) + seed.merge(node.seed) return (node as! _PreferenceNode).value } return _PreferenceNode(value: value, seed: seed, next: next) @@ -175,34 +200,3 @@ private class _PreferenceNode: PreferenceNode { "\(Key.self) = \(value)" } } - -extension HostPreferencesKey { - static var defaultValue: PreferenceList { - PreferenceList() - } - - static func reduce(value: inout PreferenceList, nextValue: () -> PreferenceList) { - let newValue = nextValue() - guard let newFirst = newValue.first else { - return - } - guard let first = value.first else { - value.first = newFirst - return - } - value.first = nil - first.forEach { node in - if let mergedNode = node.combine(from: newFirst, next: value.first) { - value.first = mergedNode - } else { - value.first = node.copy(next: value.first) - } - } - newFirst.forEach { node in - guard node.find(from: first) == nil else { - return - } - value.first = node.copy(next: value.first) - } - } -} diff --git a/Sources/OpenSwiftUI/Data/Preference/PreferencesCombiner.swift b/Sources/OpenSwiftUI/Data/Preference/PreferencesCombiner.swift new file mode 100644 index 0000000..c3f99fe --- /dev/null +++ b/Sources/OpenSwiftUI/Data/Preference/PreferencesCombiner.swift @@ -0,0 +1,156 @@ +// +// PreferencesCombiner.swift +// OpenSwiftUI +// +// Audited for RELEASE_2021 +// Status: Complete +// ID: 59D15989E597719355BF0EAE6CB41FF9 + +internal import OpenGraphShims + +struct PreferenceCombiner: Rule, AsyncAttribute { + var attributes: [WeakAttribute] + + init(attributes: [Attribute]) { + self.attributes = attributes.map { WeakAttribute($0) } + } + + var value: Key.Value { + var value = Key.defaultValue + var initialValue = true + for attribute in attributes { + if initialValue { + value = attribute.value ?? Key.defaultValue + } else { + Key.reduce(value: &value) { + attribute.value ?? Key.defaultValue + } + } + initialValue = false + } + return value + } +} + +struct HostPreferencesCombiner: Rule, AsyncAttribute { + @Attribute var keys: PreferenceKeys + @OptionalAttribute var values: PreferenceList? + var children: [Child] + + struct Child { + @WeakAttribute var keys: PreferenceKeys? + @WeakAttribute var values: PreferenceList? + } + + #if canImport(Darwin) // FIXME: See #39 + mutating func addChild(keys: Attribute, values: Attribute) { + let weakKeys = OGWeakAttribute(keys.identifier) + let weakValues = OGWeakAttribute(values.identifier) + let child = Child(keys: .init(base: weakKeys), values: .init(base: weakValues)) + if let index = children.firstIndex(where: { $0.$keys == keys }) { + children[index] = child + } else { + children.append(child) + } + } + #endif + + private struct CombineValues: PreferenceKeyVisitor { + var children: [Child] + var values: PreferenceList + + mutating func visit(key: Key.Type) { + guard !values.contains(key) else { + return + } + var value = Key.defaultValue + var seed = VersionSeed.empty + guard !children.isEmpty else { + return + } + var initialValue = true + for child in children { + guard let keys = child.$keys, + !keys.value.contains(key) else { + continue + } + guard let values = child.$values, + let listValue = values.value.valueIfPresent(key) else { + continue + } + if initialValue { + value = listValue.value + seed = listValue.seed + } else { + Key.reduce(value: &value) { + seed.merge(listValue.seed) + return listValue.value + } + } + initialValue = false + } + if !initialValue { + values[key] = PreferenceList.Value(value: value, seed: seed) + } + } + } + + var value: PreferenceList { + let values = values ?? PreferenceList() + guard !children.isEmpty else { + return values + } + var visitor = CombineValues(children: children, values: values) + let keys = keys + for key in keys { + key.visitKey(&visitor) + } + return visitor.values + } +} + +private struct PairPreferenceCombiner: Rule, AsyncAttribute { + private var attributes: (Attribute, Attribute) + + init(attributes: (Attribute, Attribute)) { + self.attributes = attributes + } + + var value: Key.Value { + var value = attributes.0.value + Key.reduce(value: &value) { attributes.1.value } + return value + } +} + +struct PairwisePreferenceCombinerVisitor: PreferenceKeyVisitor { + let outputs: (_ViewOutputs, _ViewOutputs) + var result: _ViewOutputs + + mutating func visit(key _: Key.Type) { + let values = (outputs.0[Key.self], outputs.1[Key.self]) + + if let value1 = values.0, let value2 = values.1 { + result[Key.self] = Attribute(PairPreferenceCombiner(attributes: (value1, value2))) + } else if let value = values.0 { + result[Key.self] = value + } else if let value = values.1 { + result[Key.self] = value + } + } +} + +struct MultiPreferenceCombinerVisitor: PreferenceKeyVisitor { + let outputs: [PreferencesOutputs] + var result: PreferencesOutputs + + mutating func visit(key _: Key.Type) { + let values = outputs.compactMap { $0[Key.self] } + switch values.count { + case 0: break + case 1: result[Key.self] = values[0] + case 2: result[Key.self] = Attribute(PairPreferenceCombiner(attributes: (values[0], values[1]))) + default: result[Key.self] = Attribute(PreferenceCombiner(attributes: values)) + } + } +} diff --git a/Sources/OpenSwiftUI/Data/Preference/PreferencesInputs.swift b/Sources/OpenSwiftUI/Data/Preference/PreferencesInputs.swift new file mode 100644 index 0000000..839b8d2 --- /dev/null +++ b/Sources/OpenSwiftUI/Data/Preference/PreferencesInputs.swift @@ -0,0 +1,30 @@ +// +// PreferencesInputs.swift +// OpenSwiftUI +// +// Audited for RELEASE_2021 +// Status: Complete + +internal import OpenGraphShims + +struct PreferencesInputs { + private(set) var keys: PreferenceKeys + var hostKeys: Attribute + + mutating func add(_ key: Key.Type) { + keys.add(key) + } + + mutating func remove(_ key: Key.Type) { + keys.remove(key) + } + + func contains(_ key: Key.Type) -> Bool { + keys.contains(key) + } + + @inline(__always) + mutating func merge(_ preferenceKeys: PreferenceKeys) { + keys.merge(preferenceKeys) + } +} diff --git a/Sources/OpenSwiftUI/Data/Preference/PreferencesOutputs.swift b/Sources/OpenSwiftUI/Data/Preference/PreferencesOutputs.swift new file mode 100644 index 0000000..c2f9871 --- /dev/null +++ b/Sources/OpenSwiftUI/Data/Preference/PreferencesOutputs.swift @@ -0,0 +1,70 @@ +// +// PreferencesOutputs.swift +// OpenSwiftUI +// +// Audited for RELEASE_2021 +// Status: Complete +// ID: A948213B3F0A65E8491149A582CA5C71 + +internal import OpenGraphShims + +struct PreferencesOutputs { + private var preferences: [KeyValue] = [] + private var debugProperties: _ViewDebug.Properties = [] + + func contains(_: Key.Type) -> Bool { + contains(_AnyPreferenceKey.self) + } + + func contains(_ key: AnyPreferenceKey.Type) -> Bool { + preferences.contains { $0.key == key } + } + + #if canImport(Darwin) // FIXME: See #39 + subscript(_: Key.Type) -> Attribute? { + get { + let value = self[anyKey: _AnyPreferenceKey.self] + return value.map { Attribute(identifier: $0) } + } + set { + self[anyKey: _AnyPreferenceKey.self] = newValue?.identifier + } + } + + subscript(anyKey keyType: AnyPreferenceKey.Type) -> OGAttribute? { + get { preferences.first { $0.key == keyType }?.value } + set { + if keyType == _AnyPreferenceKey.self { + if !debugProperties.contains(.displayList) { + debugProperties.formUnion(.displayList) + } + } + if let index = preferences.firstIndex(where: { $0.key == keyType }) { + if let newValue { + preferences[index].value = newValue + } else { + preferences.remove(at: index) + } + } else { + if let newValue { + preferences.append(KeyValue(key: keyType, value: newValue)) + } + } + } + } + #else + subscript(_: Key.Type) -> Attribute? { + get { fatalError("See #39") } + set { fatalError("See #39") } + } + #endif +} + +extension PreferencesOutputs { + private struct KeyValue { + var key: AnyPreferenceKey.Type + #if canImport(Darwin) // FIXME: See #39 + var value: OGAttribute + #endif + } +} diff --git a/Sources/OpenSwiftUI/Data/Preference/View_Preference.swift b/Sources/OpenSwiftUI/Data/Preference/View_Preference.swift index 48479db..edb88b7 100644 --- a/Sources/OpenSwiftUI/Data/Preference/View_Preference.swift +++ b/Sources/OpenSwiftUI/Data/Preference/View_Preference.swift @@ -4,6 +4,7 @@ // // Audited for RELEASE_2021 // Status: Complete +// ID: 6C396F98EFDD04A6B58F2F9112448013 extension View { /// Sets a value for the given preference. @@ -21,3 +22,17 @@ extension View { modifier(_PreferenceTransformModifier(transform: callback)) } } + +extension EnvironmentValues { + private struct PreferenceBridgeKey: EnvironmentKey { + struct Value { + weak var value: PreferenceBridge? + } + static let defaultValue: Value = Value() + } + + var preferenceBridge: PreferenceBridge? { + get { self[PreferenceBridgeKey.self].value } + set { self[PreferenceBridgeKey.self] = PreferenceBridgeKey.Value(value: newValue) } + } +} diff --git a/Tests/OpenSwiftUITests/Core/Data/VersionSeedTests.swift b/Tests/OpenSwiftUITests/Core/Data/VersionSeedTests.swift index 6fe238a..f4eca0c 100644 --- a/Tests/OpenSwiftUITests/Core/Data/VersionSeedTests.swift +++ b/Tests/OpenSwiftUITests/Core/Data/VersionSeedTests.swift @@ -21,13 +21,13 @@ struct VersionSeedTests { @Test(arguments: [ (0x0000_0000, 0x0000_0000, 0x0000_0000), - (0xFFFF_FFFF, 0x0000_0000, 0x17F8_3A02), - (0xFFFF_FFFF, 0xFFFF_FFFF, 0x3258_2C16), + (0xFFFF_FFFF, 0x0000_0000, 0xFFFF_FFFF), + (0xFFFF_FFFF, 0xFFFF_FFFF, 0xFFFF_FFFF), (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), + (0xFFFF_FFFF, 0xAABB_CCDD, 0xFFFF_FFFF), + (0xAABB_CCDD, 0xFFFF_FFFF, 0xFFFF_FFFF), (0x0000_0001, 0x0001_0000, 0x8621_ACD2), (0x0001_0000, 0x0000_0001, 0xD5E2_C632), (0x1000_0000, 0x0000_0001, 0xE6E8_7354), @@ -36,7 +36,7 @@ struct VersionSeedTests { func merge(_ a: UInt32, _ b: UInt32, _ c: UInt32) { let seedA = VersionSeed(value: a) let seedB = VersionSeed(value: b) - #expect(seedA.merge(seedB).value == c) + #expect(seedA.merging(seedB).value == c) } } diff --git a/Tests/OpenSwiftUITests/Data/Preference/PreferenceListTests.swift b/Tests/OpenSwiftUITests/Data/Preference/PreferenceListTests.swift index 662dd0a..1727048 100644 --- a/Tests/OpenSwiftUITests/Data/Preference/PreferenceListTests.swift +++ b/Tests/OpenSwiftUITests/Data/Preference/PreferenceListTests.swift @@ -39,15 +39,15 @@ struct PreferenceListTests { func subscriptAndDescriptionWithZeroSeed() { var list = PreferenceList() #expect(list.description == "empty: []") - list[IntKey.self] = PreferenceList.Value(value: 1, seed: .zero) + list[IntKey.self] = PreferenceList.Value(value: 1, seed: .empty) #expect(list.description == "empty: [IntKey = 1]") - list[DoubleKey.self] = PreferenceList.Value(value: 1.0, seed: .zero) + list[DoubleKey.self] = PreferenceList.Value(value: 1.0, seed: .empty) #expect(list.description == "empty: [DoubleKey = 1.0, IntKey = 1]") - list[IntKey.self] = PreferenceList.Value(value: 2, seed: .zero) + list[IntKey.self] = PreferenceList.Value(value: 2, seed: .empty) #expect(list.description == "empty: [IntKey = 2, DoubleKey = 1.0]") - list[DoubleKey.self] = PreferenceList.Value(value: 1.0, seed: .zero) + list[DoubleKey.self] = PreferenceList.Value(value: 1.0, seed: .empty) #expect(list.description == "empty: [DoubleKey = 1.0, IntKey = 2]") - list[EnumKey.self] = PreferenceList.Value(value: .a, seed: .zero) + list[EnumKey.self] = PreferenceList.Value(value: .a, seed: .empty) #expect(list.description == "empty: [EnumKey = a, DoubleKey = 1.0, IntKey = 2]") }