From 85b0ec6f443234b09669aba93f679bdf6cda8540 Mon Sep 17 00:00:00 2001 From: Kyle Date: Mon, 10 Feb 2025 02:45:52 +0800 Subject: [PATCH 1/5] Add DynamicView interface --- .../OpenSwiftUICore/View/DynamicView.swift | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 Sources/OpenSwiftUICore/View/DynamicView.swift diff --git a/Sources/OpenSwiftUICore/View/DynamicView.swift b/Sources/OpenSwiftUICore/View/DynamicView.swift new file mode 100644 index 00000000..b3c54466 --- /dev/null +++ b/Sources/OpenSwiftUICore/View/DynamicView.swift @@ -0,0 +1,38 @@ +// +// DynamicView.swift +// OpenSwiftUICore +// +// Audited for iOS 18.0 +// Status: WIP +// ID: 3FB6ABB0477B815AB3C89DD5EDC9F0F0 (SwiftUICore) + +package import OpenGraphShims + +package protocol DynamicView { + static var canTransition: Bool { get } + static var traitKeysDependOnView: Bool { get } + associatedtype Metadata + associatedtype ID : Hashable + static func makeID() -> ID + func childInfo(metadata: Metadata) -> (any Any.Type, ID?) + func makeChildView(metadata: Metadata, view: Attribute, inputs: _ViewInputs) -> _ViewOutputs + func makeChildViewList(metadata: Metadata, view: Attribute, inputs: _ViewListInputs) -> _ViewListOutputs +} + +extension DynamicView { + package static var traitKeysDependOnView: Bool { true } +} + +extension DynamicView where ID == UniqueID { + package static func makeID() -> UniqueID { UniqueID() } +} + +extension DynamicView { + package static func makeDynamicView(metadata: Metadata, view: _GraphValue, inputs: _ViewInputs) -> _ViewOutputs { + preconditionFailure("TODO") + } + + package static func makeDynamicViewList(metadata: Metadata, view: _GraphValue, inputs: _ViewListInputs) -> _ViewListOutputs { + preconditionFailure("TODO") + } +} From cac37152a6b57de7725d06ba42d4d5a107a647f0 Mon Sep 17 00:00:00 2001 From: Kyle Date: Wed, 12 Feb 2025 01:16:03 +0800 Subject: [PATCH 2/5] Add DynamicViewContainer --- .../Data/Preference/PreferencesOutputs.swift | 2 +- .../Data/Preference/View_Indirect.swift | 4 +- .../OpenSwiftUICore/Graph/GraphInputs.swift | 6 +- .../ViewModifier/CustomViewModifier.swift | 4 +- .../Modifier/ViewModifier/ViewModifier.swift | 4 +- Sources/OpenSwiftUICore/View/CustomView.swift | 4 +- .../OpenSwiftUICore/View/DynamicView.swift | 80 ++++++++++++++++++- .../View/Input/ViewInputs.swift | 29 +++---- 8 files changed, 95 insertions(+), 38 deletions(-) diff --git a/Sources/OpenSwiftUICore/Data/Preference/PreferencesOutputs.swift b/Sources/OpenSwiftUICore/Data/Preference/PreferencesOutputs.swift index 1c076035..ecb80523 100644 --- a/Sources/OpenSwiftUICore/Data/Preference/PreferencesOutputs.swift +++ b/Sources/OpenSwiftUICore/Data/Preference/PreferencesOutputs.swift @@ -74,7 +74,7 @@ package struct PreferencesOutputs { #endif #if canImport(Darwin) - package func setIndirectDependencies(_ dependency: AnyAttribute?) { + package func setIndirectDependency(_ dependency: AnyAttribute?) { preferences.forEach { $0.value.indirectDependency = dependency } diff --git a/Sources/OpenSwiftUICore/Data/Preference/View_Indirect.swift b/Sources/OpenSwiftUICore/Data/Preference/View_Indirect.swift index c60245a7..c3e25d1f 100644 --- a/Sources/OpenSwiftUICore/Data/Preference/View_Indirect.swift +++ b/Sources/OpenSwiftUICore/Data/Preference/View_Indirect.swift @@ -31,8 +31,8 @@ extension _ViewInputs { extension _ViewOutputs { #if canImport(Darwin) - package func setIndirectDependencies(_ dependency: AnyAttribute?) { - preferences.setIndirectDependencies(dependency) + package func setIndirectDependency(_ dependency: AnyAttribute?) { + preferences.setIndirectDependency(dependency) if let target = layoutComputer?.identifier { target.indirectDependency = dependency } diff --git a/Sources/OpenSwiftUICore/Graph/GraphInputs.swift b/Sources/OpenSwiftUICore/Graph/GraphInputs.swift index a9926734..1ccb9a80 100644 --- a/Sources/OpenSwiftUICore/Graph/GraphInputs.swift +++ b/Sources/OpenSwiftUICore/Graph/GraphInputs.swift @@ -317,13 +317,9 @@ private struct MergedEnvironment: Rule, AsyncAttribute { } } -// FIXME: TO BE REMOVED extension _GraphInputs { - - // MARK: - cachedEnvironment - @inline(__always) - package func detechedEnvironmentInputs() -> Self { + func detechedEnvironmentInputs() -> Self { var newInputs = self newInputs.cachedEnvironment = MutableBox(cachedEnvironment.wrappedValue) return newInputs diff --git a/Sources/OpenSwiftUICore/Modifier/ViewModifier/CustomViewModifier.swift b/Sources/OpenSwiftUICore/Modifier/ViewModifier/CustomViewModifier.swift index 01d38e85..fe1228c6 100644 --- a/Sources/OpenSwiftUICore/Modifier/ViewModifier/CustomViewModifier.swift +++ b/Sources/OpenSwiftUICore/Modifier/ViewModifier/CustomViewModifier.swift @@ -18,9 +18,7 @@ extension ViewModifier { ) -> _ViewOutputs { let fields = DynamicPropertyCache.fields(of: Self.self) var inputs = inputs - let (view, buffer) = inputs.withMutateGraphInputs { inputs in - makeBody(modifier: modifier, inputs: &inputs, fields: fields) - } + let (view, buffer) = makeBody(modifier: modifier, inputs: &inputs.base, fields: fields) inputs.append(.view(body), to: _ViewModifier_Content.BodyInput.self) let outputs = _ViewDebug.makeView( view: view, diff --git a/Sources/OpenSwiftUICore/Modifier/ViewModifier/ViewModifier.swift b/Sources/OpenSwiftUICore/Modifier/ViewModifier/ViewModifier.swift index 7732a015..efa5c3c8 100644 --- a/Sources/OpenSwiftUICore/Modifier/ViewModifier/ViewModifier.swift +++ b/Sources/OpenSwiftUICore/Modifier/ViewModifier/ViewModifier.swift @@ -107,9 +107,7 @@ extension ViewModifier where Self: _GraphInputsModifier, Body == Never { body: @escaping (_Graph, _ViewInputs) -> _ViewOutputs ) -> _ViewOutputs { var inputs = inputs - inputs.withMutateGraphInputs { inputs in - _makeInputs(modifier: modifier, inputs: &inputs) - } + _makeInputs(modifier: modifier, inputs: &inputs.base) let outputs = body(_Graph(), inputs) return outputs } diff --git a/Sources/OpenSwiftUICore/View/CustomView.swift b/Sources/OpenSwiftUICore/View/CustomView.swift index 1d49ab8b..cd44fdae 100644 --- a/Sources/OpenSwiftUICore/View/CustomView.swift +++ b/Sources/OpenSwiftUICore/View/CustomView.swift @@ -25,9 +25,7 @@ extension View { nonisolated package static func makeView(view: _GraphValue, inputs: _ViewInputs) -> _ViewOutputs { let fields = DynamicPropertyCache.fields(of: Self.self) var inputs = inputs - let (body, buffer) = inputs.withMutateGraphInputs { inputs in - makeBody(view: view, inputs: &inputs, fields: fields) - } + let (body, buffer) = makeBody(view: view, inputs: &inputs.base, fields: fields) // FIXME let outputs = _ViewDebug.makeView( view: body, diff --git a/Sources/OpenSwiftUICore/View/DynamicView.swift b/Sources/OpenSwiftUICore/View/DynamicView.swift index b3c54466..3606570c 100644 --- a/Sources/OpenSwiftUICore/View/DynamicView.swift +++ b/Sources/OpenSwiftUICore/View/DynamicView.swift @@ -3,7 +3,7 @@ // OpenSwiftUICore // // Audited for iOS 18.0 -// Status: WIP +// Status: Blocked by DynamicViewList // ID: 3FB6ABB0477B815AB3C89DD5EDC9F0F0 (SwiftUICore) package import OpenGraphShims @@ -29,10 +29,86 @@ extension DynamicView where ID == UniqueID { extension DynamicView { package static func makeDynamicView(metadata: Metadata, view: _GraphValue, inputs: _ViewInputs) -> _ViewOutputs { - preconditionFailure("TODO") + let outputs = inputs.makeIndirectOutputs() + let container = DynamicViewContainer( + metadata: metadata, + view: view.value, + inputs: inputs, + outputs: inputs.makeIndirectOutputs() + ) + let attribute = Attribute(container) + attribute.flags = .active + #if canImport(Darwin) + outputs.setIndirectDependency(attribute.identifier) + #endif + return outputs } package static func makeDynamicViewList(metadata: Metadata, view: _GraphValue, inputs: _ViewListInputs) -> _ViewListOutputs { preconditionFailure("TODO") } } + +// MARK: - DynamicViewContainer + +private struct DynamicViewContainer: StatefulRule, AsyncAttribute where V: DynamicView { + let metadata: V.Metadata + @Attribute var view: V + let inputs: _ViewInputs + let outputs: _ViewOutputs + let parentSubgraph: Subgraph + + init(metadata: V.Metadata, view: Attribute, inputs: _ViewInputs, outputs: _ViewOutputs) { + self.metadata = metadata + self._view = view + self.inputs = inputs + self.outputs = outputs + self.parentSubgraph = Subgraph.current! + } + + func updateValue() { + let (type, id) = view.childInfo(metadata: metadata) + let value: Value? = Graph.outputValue()?.pointee + guard value.map({ $0.matches(type: type, id: id)}) == false else { + return + } + if let value { + outputs.detachIndirectOutputs() + value.subgraph.willInvalidate(isInserted: true) + value.subgraph.invalidate() + } + let parentSubgraph = parentSubgraph + let graph = parentSubgraph.graph + let newSubgraph = Subgraph(graph: graph) + parentSubgraph.addChild(newSubgraph) + + let newValue = newSubgraph.apply { + let childOutputs = view.makeChildView( + metadata: metadata, + view: $view, + inputs: inputs.detechedEnvironmentInputs() + ) + outputs.attachIndirectOutputs(to: childOutputs) + return Value(type: type, id: id, subgraph: newSubgraph) + } + withUnsafePointer(to: newValue) { pointer in + Graph.setOutputValue(pointer) + } + } + + struct Value { + var type: Any.Type + var id: V.ID? + var subgraph: Subgraph + + init(type: Any.Type, id: V.ID? = nil, subgraph: Subgraph) { + self.type = type + self.id = id + self.subgraph = subgraph + } + + func matches(type: Any.Type, id: V.ID?) -> Bool { + self.type == type && id.map { $0 == id } != false + } + } +} diff --git a/Sources/OpenSwiftUICore/View/Input/ViewInputs.swift b/Sources/OpenSwiftUICore/View/Input/ViewInputs.swift index f9c52800..b24905d6 100644 --- a/Sources/OpenSwiftUICore/View/Input/ViewInputs.swift +++ b/Sources/OpenSwiftUICore/View/Input/ViewInputs.swift @@ -224,19 +224,24 @@ extension _ViewInputs { copy.changedDebugProperties = [] return copy } -} - -// FIXME: TO BE REMOVED + @inline(__always) + func detechedEnvironmentInputs() -> Self { + var newInputs = self + newInputs.base = self.base.detechedEnvironmentInputs() + return newInputs + } +} -// @available(*, deprecated, message: "TO BE REMOVED") +// FIXME +@available(*, deprecated, message: "TO BE REMOVED") extension _ViewInputs { mutating func append(_ value: Value, to type: Input.Type) where Input.Value == [Value] { var values = base[type] values.append(value) base[type] = values } - + mutating func popLast(_ type: Input.Type) -> Value? where Input.Value == [Value] { var values = base[type] guard let value = values.popLast() else { @@ -245,18 +250,4 @@ extension _ViewInputs { base[type] = values return value } - - // MARK: - base - - @inline(__always) - mutating func withMutateGraphInputs(_ body: (inout _GraphInputs) -> R) -> R { - body(&base) - } - - @inline(__always) - func detechedEnvironmentInputs() -> Self { - var newInputs = self - newInputs.base = self.base.detechedEnvironmentInputs() - return newInputs - } } From 8bb6530a34c6e00f691a088a277bac9949fc92eb Mon Sep 17 00:00:00 2001 From: Kyle Date: Fri, 14 Feb 2025 00:34:59 +0800 Subject: [PATCH 3/5] Format ViewList --- .../OpenSwiftUICore/View/Input/ViewList.swift | 75 +++++++++---------- 1 file changed, 37 insertions(+), 38 deletions(-) diff --git a/Sources/OpenSwiftUICore/View/Input/ViewList.swift b/Sources/OpenSwiftUICore/View/Input/ViewList.swift index 67ef122a..37896b87 100644 --- a/Sources/OpenSwiftUICore/View/Input/ViewList.swift +++ b/Sources/OpenSwiftUICore/View/Input/ViewList.swift @@ -765,21 +765,21 @@ public struct _ViewList_ID: Hashable { let id: AnyHashable2 let reuseID: Int #if canImport(Darwin) - let owner: AnyAttribute + let owner: AnyAttribute #endif let isUnary: Bool } #if canImport(Darwin) - package static func explicit(_ id: ID, owner: AnyAttribute) -> ViewList.ID where ID: Hashable { - var viewListID = ViewList.ID() - viewListID.bind(explicitID: id, owner: owner, isUnary: true, reuseID: .zero) - return viewListID - } + package static func explicit(_ id: ID, owner: AnyAttribute) -> ViewList.ID where ID: Hashable { + var viewListID = ViewList.ID() + viewListID.bind(explicitID: id, owner: owner, isUnary: true, reuseID: .zero) + return viewListID + } - package static func explicit(_ id: ID) -> ViewList.ID where ID: Hashable { - explicit(id, owner: .nil) - } + package static func explicit(_ id: ID) -> ViewList.ID where ID: Hashable { + explicit(id, owner: .nil) + } #endif package func elementID(at index: Int) -> ViewList.ID { @@ -846,21 +846,21 @@ public struct _ViewList_ID: Hashable { } #if canImport(Darwin) - package mutating func bind(explicitID: ID, owner: AnyAttribute, isUnary: Bool, reuseID: Int) where ID: Hashable { - explicitIDs.append(Explicit(id: AnyHashable2(explicitID), reuseID: reuseID, owner: owner, isUnary: isUnary)) - } + package mutating func bind(explicitID: ID, owner: AnyAttribute, isUnary: Bool, reuseID: Int) where ID: Hashable { + explicitIDs.append(Explicit(id: AnyHashable2(explicitID), reuseID: reuseID, owner: owner, isUnary: isUnary)) + } - package mutating func bind(explicitID: ID, owner: AnyAttribute, reuseID: Int) where ID: Hashable { - bind(explicitID: explicitID, owner: owner, isUnary: false, reuseID: reuseID) - } + package mutating func bind(explicitID: ID, owner: AnyAttribute, reuseID: Int) where ID: Hashable { + bind(explicitID: explicitID, owner: owner, isUnary: false, reuseID: reuseID) + } - package mutating func bind(explicitID: ID, owner: AnyAttribute, isUnary: Bool) where ID: Hashable { - bind(explicitID: explicitID, owner: owner, isUnary: isUnary, reuseID: .zero) - } + package mutating func bind(explicitID: ID, owner: AnyAttribute, isUnary: Bool) where ID: Hashable { + bind(explicitID: explicitID, owner: owner, isUnary: isUnary, reuseID: .zero) + } - package mutating func bind(explicitID: ID, owner: AnyAttribute) where ID: Hashable { - bind(explicitID: explicitID, owner: owner, isUnary: false, reuseID: .zero) - } + package mutating func bind(explicitID: ID, owner: AnyAttribute) where ID: Hashable { + bind(explicitID: explicitID, owner: owner, isUnary: false, reuseID: .zero) + } #endif package var primaryExplicitID: AnyHashable2? { explicitIDs.first?.id } @@ -868,15 +868,15 @@ public struct _ViewList_ID: Hashable { package var allExplicitIDs: [AnyHashable2] { explicitIDs.map(\.id) } #if canImport(Darwin) - package func explicitID(owner: AnyAttribute) -> ID? where ID: Hashable { - for explicitID in explicitIDs { - guard explicitID.owner == owner, - let id = explicitID.id.as(type: ID.self) - else { continue } - return id - } - return nil + package func explicitID(owner: AnyAttribute) -> ID? where ID: Hashable { + for explicitID in explicitIDs { + guard explicitID.owner == owner, + let id = explicitID.id.as(type: ID.self) + else { continue } + return id } + return nil + } #endif package func explicitID(for idType: ID.Type) -> ID? where ID: Hashable { @@ -903,7 +903,7 @@ public struct _ViewList_ID: Hashable { for explicitID in explicitIDs { hasher.combine(explicitID.id) #if canImport(Darwin) - hasher.combine(explicitID.owner) + hasher.combine(explicitID.owner) #endif } } @@ -1059,7 +1059,7 @@ public struct BodyUnaryViewGenerator: UnaryViewGenerator { } public func tryToReuse(by other: BodyUnaryViewGenerator, indirectMap: IndirectAttributeMap, testOnly: Bool) -> Bool { - // FIXME + // FIXME: // 1. pass body to OGCompareValues directly instaed of withUnsafePointer // 2. Add 0x103 case instead of rawValue guard compareValues(body, other.body, mode: .init(rawValue: 0x103)) else { @@ -1122,7 +1122,7 @@ private struct UnaryElements: ViewList.Elements where Generator: Unar indirectMap: IndirectAttributeMap, testOnly: Bool ) -> Bool { - guard let other = other as? UnaryElements else { + guard let other = other as? UnaryElements else { Log.graphReuse("Reuse failed: other is not Unary") ReuseTrace.traceReuseUnaryElementExpectedFailure(type(of: other)) return false @@ -1300,7 +1300,8 @@ private struct BaseViewList: ViewList { ID._Views( ID.ElementCollection( id: ID(implicitID: implicitID), - count: elements.count), + count: elements.count + ), isDataDependent: false ) } @@ -1333,7 +1334,7 @@ private struct BaseViewList: ViewList { nil } - func firstOffset(forID id: OtherID, style: IteratorStyle) -> Int? where OtherID : Hashable { + func firstOffset(forID id: OtherID, style: IteratorStyle) -> Int? where OtherID: Hashable { nil } @@ -1546,7 +1547,6 @@ package struct _ViewList_Group: ViewList { isDataDependent = isDataDependent || ids.isDataDependent } return ViewList.ID.JoinedViews(views, isDataDependent: isDataDependent) - } package func applyNodes( @@ -1651,7 +1651,6 @@ package struct _ViewList_Section: ViewList { return style } - package func count(style: IteratorStyle) -> Int { if isHierarchical { var style = style @@ -1906,11 +1905,11 @@ private struct SubgraphList: ViewList { struct Transform: ViewList.SublistTransform.Item { var subgraph: ViewList.Subgraph - func apply(sublist: inout _ViewList_Sublist) { + func apply(sublist: inout ViewList.Sublist) { sublist.elements = SubgraphElements(base: sublist.elements, subgraph: subgraph) } - func bindID(_ id: inout _ViewList_ID) {} + func bindID(_ id: inout ViewList.ID) {} } } From 6a65ac5b0058f53837179bbbf26bd27f0010a889 Mon Sep 17 00:00:00 2001 From: Kyle Date: Fri, 14 Feb 2025 03:04:08 +0800 Subject: [PATCH 4/5] Add DynamicViewList --- .../Data/Transaction/Transaction.swift | 30 +- .../OpenSwiftUICore/Graph/GraphInputs.swift | 9 +- Sources/OpenSwiftUICore/View/AnyView.swift | 2 +- .../OpenSwiftUICore/View/DynamicView.swift | 277 +++++++++++++++++- .../View/Input/ViewInputs.swift | 9 +- .../OpenSwiftUICore/View/Input/ViewList.swift | 27 +- 6 files changed, 330 insertions(+), 24 deletions(-) diff --git a/Sources/OpenSwiftUICore/Data/Transaction/Transaction.swift b/Sources/OpenSwiftUICore/Data/Transaction/Transaction.swift index 50e73589..ac1babe8 100644 --- a/Sources/OpenSwiftUICore/Data/Transaction/Transaction.swift +++ b/Sources/OpenSwiftUICore/Data/Transaction/Transaction.swift @@ -6,7 +6,7 @@ // Status: WIP // ID: B2543BCA257433E04979186A1DC2B6BC -import OpenGraphShims +package import OpenGraphShims import OpenSwiftUI_SPI /// The context of the current state-processing update. @@ -247,11 +247,31 @@ extension Transaction { } } -// MARK: - TransactionID [TODO] +// MARK: - TransactionID -package struct TransactionID/*: Comparable, Hashable */{ +package struct TransactionID: Comparable, Hashable { package var id: Int - package init(id: Int) { - self.id = id + + @inlinable + package init() { id = .zero } + + @inlinable + package init(graph: Graph) { + id = Int(graph.counter(for: ._1)) + } + + @inlinable + package init(context: AnyRuleContext) { + self.init(graph: context.attribute.graph) + } + + @inlinable + package init(context: RuleContext) { + self.init(graph: context.attribute.graph) + } + + @inlinable + package static func < (lhs: TransactionID, rhs: TransactionID) -> Bool { + lhs.id < rhs.id } } diff --git a/Sources/OpenSwiftUICore/Graph/GraphInputs.swift b/Sources/OpenSwiftUICore/Graph/GraphInputs.swift index 1ccb9a80..88572e09 100644 --- a/Sources/OpenSwiftUICore/Graph/GraphInputs.swift +++ b/Sources/OpenSwiftUICore/Graph/GraphInputs.swift @@ -319,9 +319,14 @@ private struct MergedEnvironment: Rule, AsyncAttribute { extension _GraphInputs { @inline(__always) - func detechedEnvironmentInputs() -> Self { + mutating func detachEnvironmentInputs() { + cachedEnvironment = MutableBox(cachedEnvironment.wrappedValue) + } + + @inline(__always) + func detachedEnvironmentInputs() -> Self { var newInputs = self - newInputs.cachedEnvironment = MutableBox(cachedEnvironment.wrappedValue) + newInputs.detachEnvironmentInputs() return newInputs } } diff --git a/Sources/OpenSwiftUICore/View/AnyView.swift b/Sources/OpenSwiftUICore/View/AnyView.swift index 3d6666e0..e2084360 100644 --- a/Sources/OpenSwiftUICore/View/AnyView.swift +++ b/Sources/OpenSwiftUICore/View/AnyView.swift @@ -188,7 +188,7 @@ private struct AnyViewContainer: StatefulRule, AsyncAttribute { let childGraph = OGSubgraph(graph: parentSubgraph.graph) parentSubgraph.addChild(childGraph) return childGraph.apply { - let childInputs = inputs.detechedEnvironmentInputs() + let childInputs = inputs.detachedEnvironmentInputs() let childOutputs = storage.makeChild( uniqueId: uniqueId, container: current.unsafeCast(to: AnyViewInfo.self), diff --git a/Sources/OpenSwiftUICore/View/DynamicView.swift b/Sources/OpenSwiftUICore/View/DynamicView.swift index 3606570c..44e783b2 100644 --- a/Sources/OpenSwiftUICore/View/DynamicView.swift +++ b/Sources/OpenSwiftUICore/View/DynamicView.swift @@ -3,7 +3,7 @@ // OpenSwiftUICore // // Audited for iOS 18.0 -// Status: Blocked by DynamicViewList +// Status: Complete // ID: 3FB6ABB0477B815AB3C89DD5EDC9F0F0 (SwiftUICore) package import OpenGraphShims @@ -45,7 +45,13 @@ extension DynamicView { } package static func makeDynamicViewList(metadata: Metadata, view: _GraphValue, inputs: _ViewListInputs) -> _ViewListOutputs { - preconditionFailure("TODO") + let list = DynamicViewList(metadata: metadata, view: view.value, inputs: inputs, lastItem: nil) + let attribute = Attribute(list) + return _ViewListOutputs( + .dynamicList(attribute, nil), + nextImplicitID: inputs.implicitID, + staticCount: nil + ) } } @@ -68,32 +74,29 @@ private struct DynamicViewContainer: StatefulRule, AsyncAttribute where V: Dy func updateValue() { let (type, id) = view.childInfo(metadata: metadata) - let value: Value? = Graph.outputValue()?.pointee - guard value.map({ $0.matches(type: type, id: id)}) == false else { + let oldValue: Value? = Graph.outputValue()?.pointee + guard oldValue.map({ $0.matches(type: type, id: id)}) == false else { return } - if let value { + if let oldValue { outputs.detachIndirectOutputs() - value.subgraph.willInvalidate(isInserted: true) - value.subgraph.invalidate() + oldValue.subgraph.willInvalidate(isInserted: true) + oldValue.subgraph.invalidate() } let parentSubgraph = parentSubgraph let graph = parentSubgraph.graph let newSubgraph = Subgraph(graph: graph) parentSubgraph.addChild(newSubgraph) - let newValue = newSubgraph.apply { + value = newSubgraph.apply { let childOutputs = view.makeChildView( metadata: metadata, view: $view, - inputs: inputs.detechedEnvironmentInputs() + inputs: inputs.detachedEnvironmentInputs() ) outputs.attachIndirectOutputs(to: childOutputs) return Value(type: type, id: id, subgraph: newSubgraph) } - withUnsafePointer(to: newValue) { pointer in - Graph.setOutputValue(pointer) - } } struct Value { @@ -112,3 +115,253 @@ private struct DynamicViewContainer: StatefulRule, AsyncAttribute where V: Dy } } } + +// MARK: - DynamicViewList + +private struct DynamicViewList: StatefulRule, AsyncAttribute where V: DynamicView { + let metadata: V.Metadata + @Attribute var view: V + let inputs: _ViewListInputs + let parentSubgraph: Subgraph + private let allItems: MutableBox<[Unmanaged]> + private var lastItem: Item? + + fileprivate init(metadata: V.Metadata, view: Attribute, inputs: _ViewListInputs, lastItem: Item?) { + self.metadata = metadata + self._view = view + self.inputs = inputs + self.parentSubgraph = Subgraph.current! + self.allItems = MutableBox([]) + self.lastItem = lastItem + } + + typealias Value = (any ViewList) + + mutating func updateValue() { + let (type, id) = view.childInfo(metadata: metadata) + + let lastID: V.ID? + if let lastItem { + lastID = lastItem.id + if lastItem.matches(type: type, id: id), lastItem.isValid { + setValue(for: lastItem, id: lastID) + return + } + lastItem.remove(from: parentSubgraph) + self.lastItem = nil + } else { + lastID = nil + } + + for item in allItems.wrappedValue { + let item = item.takeUnretainedValue() + guard item.matches(type: type, id: id) else { + continue + } + item.retain() + // parentSubgraph.addChild(item.subgraph) + item.subgraph.didReinsert() + lastItem = item + setValue(for: item, id: lastID) + return + } + + let parentSubgraph = parentSubgraph + guard parentSubgraph.isValid else { + value = EmptyViewList() + return + } + let graph = parentSubgraph.graph + let newSubgraph = Subgraph(graph: graph) + parentSubgraph.addChild(newSubgraph) + let (listAttribute, isUnary) = newSubgraph.apply { + var newInputs = inputs + newInputs.detachEnvironmentInputs() + if V.canTransition { + newInputs.options.insert(.canTransition) + } + newInputs.implicitID = 0 + let outputs = view.makeChildViewList(metadata: metadata, view: $view, inputs: newInputs) + let attribute = outputs.makeAttribute(inputs: newInputs) + return (attribute, outputs.staticCount == 1) + } + let item = Item( + type: type, + owner: context.attribute.identifier, + list: listAttribute, + id: id ?? V.makeID(), + isUnary: isUnary, + subgraph: newSubgraph, + allItems: allItems + ) + lastItem = item + setValue(for: item, id: lastID) + } + + private mutating func setValue(for item: Item, id: V.ID?) { + value = WrappedList( + base: item.list, + item: item, + lastID: id, + lastTransaction: TransactionID(context: context) + ) + } + + fileprivate final class Item: ViewList.Subgraph { + let type: any Any.Type + let id: V.ID + let owner: AnyAttribute + @Attribute var list: any ViewList + let isUnary: Bool + let allItems: MutableBox<[Unmanaged]> + + init(type: any Any.Type, owner: AnyAttribute, list: Attribute, id: V.ID, isUnary: Bool, subgraph: Subgraph, allItems: MutableBox<[Unmanaged]>) { + self.type = type + self.id = id + self.owner = owner + self._list = list + self.isUnary = isUnary + self.allItems = allItems + super.init(subgraph: subgraph) + allItems.wrappedValue.append(.passRetained(self)) + } + + override func invalidate() { + if let index = allItems.wrappedValue.firstIndex(where: { $0 == .passUnretained(self) }) { + allItems.wrappedValue.remove(at: index) + } + } + + func matches(type: Any.Type, id: V.ID?) -> Bool { + self.type == type && id.map { $0 == self.id } != false + } + + func bindID(_ id: inout ViewList.ID) { + id.bind(explicitID: id, owner: owner, isUnary: isUnary, reuseID: Int(bitPattern: ObjectIdentifier(Int.self))) + } + } +} + +extension DynamicViewList { + private struct WrappedList: ViewList { + let base: any ViewList + let item: Item + let lastID: V.ID? + let lastTransaction: TransactionID + + init(base: any ViewList, item: Item, lastID: V.ID?, lastTransaction: TransactionID) { + self.base = base + self.item = item + self.lastID = lastID + self.lastTransaction = lastTransaction + } + + func count(style: IteratorStyle) -> Int { + base.count(style: style) + } + + func estimatedCount(style: IteratorStyle) -> Int { + base.estimatedCount(style: style) + } + + var traitKeys: ViewTraitKeys? { + var keys = base.traitKeys + if V.traitKeysDependOnView { + keys?.isDataDependent = true + } + return keys + } + + var viewIDs: ID.Views? { + base.viewIDs.map { viewIDs in + ViewList.ID._Views( + WrappedIDs(base: viewIDs, item: item), + isDataDependent: true + ) + } + } + + var traits: ViewTraitCollection { + base.traits + } + + @discardableResult + func applyNodes( + from start: inout Int, + style: IteratorStyle, + list: Attribute?, + transform: inout SublistTransform, + to body: ApplyBody + ) -> Bool { + transform.push(Transform(item: item)) + defer { transform.pop() } + return base.applyNodes( + from: &start, + style: style, + list: list, + transform: &transform, + to: body + ) + } + + func edit(forID id: ID, since transaction: TransactionID) -> Edit? { + guard transaction >= lastTransaction, + let lastID, + lastID != item.id, + let explicitID: V.ID = id.explicitID(owner: item.owner) + else { + return base.edit(forID: id, since: transaction) + } + if explicitID == lastID { + return .removed + } else if explicitID == item.id { + return .inserted + } else { + return base.edit(forID: id, since: transaction) + } + } + + func firstOffset(forID id: OtherID, style: IteratorStyle) -> Int? where OtherID: Hashable { + guard let otherID = id as? V.ID, + otherID == item.id + else { + return base.firstOffset(forID: id, style: style) + } + return 0 + } + } + + private struct WrappedIDs: RandomAccessCollection, Equatable { + let base: ViewList.ID.Views + let item: Item + + var startIndex: Int { .zero } + + var endIndex: Int { base.endIndex } + + subscript(index: Int) -> ViewList.ID { + _read { + var id = base[index] + item.bindID(&id) + yield id + } + } + + static func ==(_ lhs: WrappedIDs, _ rhs: WrappedIDs) -> Bool { + lhs.item === rhs.item && lhs.base.isEqual(to: rhs.base) + } + } + + private struct Transform: ViewList.SublistTransform.Item { + var item: Item + + package func apply(sublist: inout ViewList.Sublist) { + item.bindID(&sublist.id) + sublist.elements = item.wrapping(sublist.elements) + } + + package func bindID(_ id: inout ViewList.ID) { + item.bindID(&id) + } + } +} diff --git a/Sources/OpenSwiftUICore/View/Input/ViewInputs.swift b/Sources/OpenSwiftUICore/View/Input/ViewInputs.swift index b24905d6..6f8e001a 100644 --- a/Sources/OpenSwiftUICore/View/Input/ViewInputs.swift +++ b/Sources/OpenSwiftUICore/View/Input/ViewInputs.swift @@ -226,9 +226,14 @@ extension _ViewInputs { } @inline(__always) - func detechedEnvironmentInputs() -> Self { + mutating func detachEnvironmentInputs() { + base.detachEnvironmentInputs() + } + + @inline(__always) + func detachedEnvironmentInputs() -> Self { var newInputs = self - newInputs.base = self.base.detechedEnvironmentInputs() + newInputs.detachEnvironmentInputs() return newInputs } } diff --git a/Sources/OpenSwiftUICore/View/Input/ViewList.swift b/Sources/OpenSwiftUICore/View/Input/ViewList.swift index 37896b87..c1daabc3 100644 --- a/Sources/OpenSwiftUICore/View/Input/ViewList.swift +++ b/Sources/OpenSwiftUICore/View/Input/ViewList.swift @@ -104,6 +104,20 @@ public struct _ViewListInputs { } } +extension _ViewListInputs { + @inline(__always) + mutating func detachEnvironmentInputs() { + base.detachEnvironmentInputs() + } + + @inline(__always) + func detachedEnvironmentInputs() -> Self { + var newInputs = self + newInputs.detachEnvironmentInputs() + return newInputs + } +} + // MARK: - _ViewListCountInputs /// Input values to `View._viewListCount()`. @@ -1792,7 +1806,7 @@ open class _ViewList_Subgraph { } @inline(__always) - final func release(isInserted: Bool) { + final func invalidate(isInserted: Bool) { refcount &-= 1 guard refcount == 0 else { return @@ -1804,6 +1818,15 @@ open class _ViewList_Subgraph { subgraph.willInvalidate(isInserted: isInserted) subgraph.invalidate() } + + @inline(__always) + final func remove(from parent: Subgraph) { + if isValid { + subgraph.willRemove() + // parent.removeChild(subgraph) // FIXME: OG + } + invalidate(isInserted: false) + } } @_spi(ForOpenSwiftUIOnly) @@ -1926,7 +1949,7 @@ final package class _ViewList_ReleaseElements: Equatable { deinit { Update.ensure { - subgraph.release(isInserted: true) + subgraph.invalidate(isInserted: true) } } From 8fe232c2dc368390b6b58bdcd67dd074e9969dce Mon Sep 17 00:00:00 2001 From: Kyle Date: Mon, 17 Feb 2025 02:35:58 +0800 Subject: [PATCH 5/5] Fix OG API and Linux build issue --- Package.resolved | 4 ++-- .../Data/Transaction/Transaction.swift | 8 ++++++++ Sources/OpenSwiftUICore/View/DynamicView.swift | 14 ++++++++++++-- Sources/OpenSwiftUICore/View/Input/ViewList.swift | 2 +- 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/Package.resolved b/Package.resolved index 0dd28103..e2cd1226 100644 --- a/Package.resolved +++ b/Package.resolved @@ -7,7 +7,7 @@ "location" : "https://github.com/OpenSwiftUIProject/DarwinPrivateFrameworks.git", "state" : { "branch" : "main", - "revision" : "932885521bc9164670aee302de2329116da11bc6" + "revision" : "2cec508df7d16801a1bb5b659b906cec465b213e" } }, { @@ -25,7 +25,7 @@ "location" : "https://github.com/OpenSwiftUIProject/OpenGraph", "state" : { "branch" : "main", - "revision" : "5992cb5119cf2a7d294a535d76e6e6e1ef0d0ef4" + "revision" : "40b020c8e1cf93f0eab1e5f70476bc31fe429620" } }, { diff --git a/Sources/OpenSwiftUICore/Data/Transaction/Transaction.swift b/Sources/OpenSwiftUICore/Data/Transaction/Transaction.swift index ac1babe8..f88fa59b 100644 --- a/Sources/OpenSwiftUICore/Data/Transaction/Transaction.swift +++ b/Sources/OpenSwiftUICore/Data/Transaction/Transaction.swift @@ -262,12 +262,20 @@ package struct TransactionID: Comparable, Hashable { @inlinable package init(context: AnyRuleContext) { + #if canImport(Darwin) self.init(graph: context.attribute.graph) + #else + preconditionFailure("See #39") + #endif } @inlinable package init(context: RuleContext) { + #if canImport(Darwin) self.init(graph: context.attribute.graph) + #else + preconditionFailure("See #39") + #endif } @inlinable diff --git a/Sources/OpenSwiftUICore/View/DynamicView.swift b/Sources/OpenSwiftUICore/View/DynamicView.swift index 44e783b2..a7f0360a 100644 --- a/Sources/OpenSwiftUICore/View/DynamicView.swift +++ b/Sources/OpenSwiftUICore/View/DynamicView.swift @@ -29,6 +29,7 @@ extension DynamicView where ID == UniqueID { extension DynamicView { package static func makeDynamicView(metadata: Metadata, view: _GraphValue, inputs: _ViewInputs) -> _ViewOutputs { + #if canImport(Darwin) let outputs = inputs.makeIndirectOutputs() let container = DynamicViewContainer( metadata: metadata, @@ -38,13 +39,15 @@ extension DynamicView { ) let attribute = Attribute(container) attribute.flags = .active - #if canImport(Darwin) outputs.setIndirectDependency(attribute.identifier) - #endif return outputs + #else + preconditionFailure("See #39") + #endif } package static func makeDynamicViewList(metadata: Metadata, view: _GraphValue, inputs: _ViewListInputs) -> _ViewListOutputs { + #if canImport(Darwin) let list = DynamicViewList(metadata: metadata, view: view.value, inputs: inputs, lastItem: nil) let attribute = Attribute(list) return _ViewListOutputs( @@ -52,9 +55,14 @@ extension DynamicView { nextImplicitID: inputs.implicitID, staticCount: nil ) + #else + preconditionFailure("See #39") + #endif } } +#if canImport(Darwin) + // MARK: - DynamicViewContainer private struct DynamicViewContainer: StatefulRule, AsyncAttribute where V: DynamicView { @@ -365,3 +373,5 @@ extension DynamicViewList { } } } + +#endif diff --git a/Sources/OpenSwiftUICore/View/Input/ViewList.swift b/Sources/OpenSwiftUICore/View/Input/ViewList.swift index c1daabc3..87a31273 100644 --- a/Sources/OpenSwiftUICore/View/Input/ViewList.swift +++ b/Sources/OpenSwiftUICore/View/Input/ViewList.swift @@ -1823,7 +1823,7 @@ open class _ViewList_Subgraph { final func remove(from parent: Subgraph) { if isValid { subgraph.willRemove() - // parent.removeChild(subgraph) // FIXME: OG + parent.removeChild(subgraph) } invalidate(isInserted: false) }