diff --git a/Sources/OpenSwiftUICore/Data/Environment/EnvironmentalView.swift b/Sources/OpenSwiftUICore/Data/Environment/EnvironmentalView.swift index ea9f67411..69e028d02 100644 --- a/Sources/OpenSwiftUICore/Data/Environment/EnvironmentalView.swift +++ b/Sources/OpenSwiftUICore/Data/Environment/EnvironmentalView.swift @@ -48,9 +48,7 @@ struct EnvironmentalViewChild: StatefulRule, AsyncAttribute, CustomStringConv } else if envChanged, tracker.hasDifferentUsedValues(env.plist) { shouldReset = true } else { - // TODO: Optimize this API call - let outputValue: UnsafePointer? = Graph.outputValue() - shouldReset = outputValue == nil + shouldReset = !hasValue } guard shouldReset else { return } tracker.reset() diff --git a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayList_StableIdentity.swift b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayList_StableIdentity.swift index e9fef0fa1..1ebc88278 100644 --- a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayList_StableIdentity.swift +++ b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayList_StableIdentity.swift @@ -128,10 +128,12 @@ extension _GraphInputs { } package mutating func pushStableType(_ type: any Any.Type) { + #if OPENSWIFTUI_SUPPORT_2024_API guard options.contains(.needsStableDisplayListIDs) else { return } pushScope(id: makeStableTypeData(type)) + #endif } package var stableIDScope: WeakAttribute? { @@ -143,10 +145,11 @@ extension _GraphInputs { } } +#if OPENSWIFTUI_SUPPORT_2024_API package func makeStableTypeData(_ type: any Any.Type) -> StrongHash { - // OGTypeGetSignature - preconditionFailure("TODO") + unsafeBitCast(Metadata(type).signature, to: StrongHash.self) } +#endif package func makeStableIDData(from id: ID) -> StrongHash? { guard let encodable = id as? Encodable else { diff --git a/Sources/OpenSwiftUICore/View/AnyView.swift b/Sources/OpenSwiftUICore/View/AnyView.swift index 8efe36a82..ea2425404 100644 --- a/Sources/OpenSwiftUICore/View/AnyView.swift +++ b/Sources/OpenSwiftUICore/View/AnyView.swift @@ -3,17 +3,25 @@ // OpenSwiftUICore // // Audited for iOS 18.0 -// Status: WIP -// ID: A96961F3546506F21D8995C6092F15B5 (OpenSwiftUI) -// ID: 7578D05D331D7F1A2E0C2F8DEF38AAD4 (OpenSwiftUICore) +// Status: Complete +// ID: A96961F3546506F21D8995C6092F15B5 (SwiftUI) +// ID: 7578D05D331D7F1A2E0C2F8DEF38AAD4 (SwiftUICore) -import OpenGraphShims +package import OpenGraphShims import OpenSwiftUI_SPI +// MARK: - AnyView + +/// A type-erased view. +/// +/// An `AnyView` allows changing the type of view used in a given view +/// hierarchy. Whenever the type of view used with an `AnyView` changes, the old +/// hierarchy is destroyed and a new hierarchy is created for the new type. @frozen public struct AnyView: View, PrimitiveView { var storage: AnyViewStorageBase + /// Create an instance that type-erases `view`. public init(_ view: V) where V: View { if let anyView = view as? AnyView { storage = anyView.storage @@ -31,7 +39,7 @@ public struct AnyView: View, PrimitiveView { struct Visitor: ViewTypeVisitor { var value: Any var view: AnyView? - + mutating func visit(type: V.Type) { view = AnyView(value as! V) } @@ -39,31 +47,17 @@ public struct AnyView: View, PrimitiveView { guard let conformace = TypeConformance(type(of: value)) else { return nil } - // Audited for RELEASE_2023 var visitor = Visitor(value: value) - withUnsafePointer(to: conformace) { pointer in - let type = UnsafeRawPointer(pointer).assumingMemoryBound(to: (any View.Type).self) - visitor.visit(type: type.pointee) - } + visitor.visit(type: unsafeBitCast(conformace, to: (any View.Type).self)) self = visitor.view! } - public static func _makeView(view: _GraphValue, inputs: _ViewInputs) -> _ViewOutputs { - let outputs = inputs.makeIndirectOutputs() - let parent = OGSubgraph.current! - let container = AnyViewContainer(view: view.value, inputs: inputs, outputs: outputs, parentSubgraph: parent) - let containerAttribute = Attribute(container) - outputs.forEachPreference { key, value in - value.indirectDependency = containerAttribute.identifier - } - if let layoutComputer = outputs.layoutComputer { - layoutComputer.identifier.indirectDependency = containerAttribute.identifier - } - return outputs + nonisolated public static func _makeView(view: _GraphValue, inputs: _ViewInputs) -> _ViewOutputs { + makeDynamicView(metadata: (), view: view, inputs: inputs) } - - public static func _makeViewList(view: _GraphValue, inputs: _ViewListInputs) -> _ViewListOutputs { - preconditionFailure("TODO") + + nonisolated public static func _makeViewList(view: _GraphValue, inputs: _ViewListInputs) -> _ViewListOutputs { + makeDynamicViewList(metadata: (), view: view, inputs: inputs) } package func visitContent(_ visitor: inout Visitor) { @@ -71,243 +65,107 @@ public struct AnyView: View, PrimitiveView { } } +@available(*, unavailable) +extension AnyView: Sendable {} + +// MARK: - AnyView: DynamicView + +extension AnyView: DynamicView { + package static var canTransition: Bool { false } + + package func childInfo(metadata: Void) -> (any Any.Type, UniqueID?) { + (storage.childType, nil) + } + + package func makeChildView(metadata: (), view: Attribute, inputs: _ViewInputs) -> _ViewOutputs { + storage.makeChildView(view: view, inputs: inputs) + } + + package func makeChildViewList(metadata: (), view: Attribute, inputs: _ViewListInputs) -> _ViewListOutputs { + storage.makeChildViewList(view: view, inputs: inputs) + } +} + +// MARK: - AnyViewStorageBase + @usableFromInline class AnyViewStorageBase { - fileprivate var type: Any.Type { preconditionFailure("") } - fileprivate var canTransition: Bool { preconditionFailure("") } - fileprivate func matches(_ other: AnyViewStorageBase) -> Bool { preconditionFailure("") } - fileprivate func makeChild( - uniqueId: UInt32, - container: Attribute, - inputs: _ViewInputs - ) -> _ViewOutputs { + fileprivate var childType: any Any.Type { preconditionFailure("") } - func child() -> Value { preconditionFailure("") } - fileprivate func makeViewList( - view: _GraphValue, - inputs: _ViewListInputs - ) -> _ViewListOutputs { + + fileprivate func makeChildView(view: Attribute, inputs: _ViewInputs) -> _ViewOutputs { + preconditionFailure("") + } + + fileprivate func makeChildViewList(view: Attribute, inputs: _ViewListInputs) -> _ViewListOutputs { preconditionFailure("") } - fileprivate func visitContent(_ visitor: inout Vistor) { + + fileprivate func visitContent(_ visitor: inout Visitor) where Visitor: ViewVisitor { + preconditionFailure("") + } + + fileprivate var content: any View { preconditionFailure("") } } -private final class AnyViewStorage: AnyViewStorageBase { +@available(*, unavailable) +extension AnyViewStorageBase: Sendable {} + +// MARK: - AnyViewStorage + +private final class AnyViewStorage: AnyViewStorageBase where V: View { let view: V - + init(view: V) { self.view = view super.init() } - // FIXME - var id: UniqueID? { nil } - - override var type: Any.Type { V.self } - - override var canTransition: Bool { id != nil } - - override func matches(_ other: AnyViewStorageBase) -> Bool { - other is AnyViewStorage + override var childType: Any.Type { + V.self } - - override func makeChild( - uniqueId: UInt32, - container: Attribute, - inputs: _ViewInputs - ) -> _ViewOutputs { - let child = AnyViewChild(info: container, uniqueId: uniqueId) - let graphValue = _GraphValue(Attribute(child)) - // FIXME - return _ViewDebug.makeView( - view: graphValue, - inputs: inputs - ) { view, inputs in - V._makeView(view: view, inputs: inputs) - } + + override func makeChildView(view: Attribute, inputs: _ViewInputs) -> _ViewOutputs { + var inputs = inputs + inputs.base.pushStableType(V.self) + let view = _GraphValue(AnyViewChild(view: view)) + return V.makeDebuggableView(view: view, inputs: inputs) } - - override func child() -> Value { view as! Value } - - override func makeViewList( - view: _GraphValue, - inputs: _ViewListInputs - ) -> _ViewListOutputs { - let childList = AnyViewChildList(view: view.value, id: id) - let childListAttribute = Attribute(childList) - childListAttribute.value = self.view - return V.makeDebuggableViewList(view: _GraphValue(childListAttribute), inputs: inputs) + + override func makeChildViewList(view: Attribute, inputs: _ViewListInputs) -> _ViewListOutputs { + var inputs = inputs + inputs.base.pushStableType(V.self) + let view = _GraphValue(AnyViewChild(view: view)) + return V.makeDebuggableViewList(view: view, inputs: inputs) } - - override func visitContent(_ visitor: inout Vistor) where Vistor : ViewVisitor { + + override func visitContent(_ visitor: inout Visitor) where Visitor: ViewVisitor { visitor.visit(view) } -} -private struct AnyViewInfo { - var item: AnyViewStorageBase - var subgraph: OGSubgraph - var uniqueID: UInt32 -} - -private struct AnyViewContainer: StatefulRule, AsyncAttribute { - @Attribute var view: AnyView - let inputs: _ViewInputs - let outputs: _ViewOutputs - let parentSubgraph: OGSubgraph - - typealias Value = AnyViewInfo - - func updateValue() { - let view = view - let newInfo: AnyViewInfo - if hasValue { - let oldInfo = value - if oldInfo.item.matches(view.storage) { - newInfo = oldInfo - } else { - eraseItem(info: oldInfo) - newInfo = makeItem(view.storage, uniqueId: oldInfo.uniqueID &+ 1) - } - } else { - newInfo = makeItem(view.storage, uniqueId: 0) - } - value = newInfo - } - - func makeItem(_ storage: AnyViewStorageBase, uniqueId: UInt32) -> AnyViewInfo { - let current = AnyAttribute.current! - let childGraph = OGSubgraph(graph: parentSubgraph.graph) - parentSubgraph.addChild(childGraph) - return childGraph.apply { - let childInputs = inputs.detachedEnvironmentInputs() - let childOutputs = storage.makeChild( - uniqueId: uniqueId, - container: current.unsafeCast(to: AnyViewInfo.self), - inputs: childInputs - ) - outputs.attachIndirectOutputs(to: childOutputs) - return AnyViewInfo(item: storage, subgraph: childGraph, uniqueID: uniqueId) - } - } - - func eraseItem(info: AnyViewInfo) { - outputs.detachIndirectOutputs() - let subgraph = info.subgraph - subgraph.willInvalidate(isInserted: true) - subgraph.invalidate() + override var content: any View { + view } } -private struct AnyViewChild: StatefulRule, AsyncAttribute { - @Attribute var info: AnyViewInfo - let uniqueId: UInt32 - - typealias Value = V - - func updateValue() { - guard uniqueId == info.uniqueID else { - return - } - value = info.item.child() - } -} +// MARK: - AnyViewChild -extension AnyViewChild: CustomStringConvertible { - var description: String { "\(V.self)" } -} +fileprivate struct AnyViewChild: StatefulRule, AsyncAttribute, CustomStringConvertible where V: View { + @Attribute var view: AnyView -private struct AnyViewChildList: StatefulRule, AsyncAttribute { typealias Value = V - - @Attribute var view: AnyView - var id: UniqueID? - + func updateValue() { - guard let storage = view.storage as? AnyViewStorage, - storage.id == id else { + guard let storage = view.storage as? AnyViewStorage else { return } value = storage.view - return } -} -// TODO -private struct AnyViewList: StatefulRule, AsyncAttribute { - @Attribute var view: AnyView - let inputs: _ViewListInputs - let parentSubgraph: OGSubgraph - let allItems: MutableBox<[Unmanaged]> - var lastItem: Item? - - typealias Value = AnyView // FIXME - - func updateValue() { - preconditionFailure("TODO") - } - - final class Item: _ViewList_Subgraph { - let type: Any.Type - let owner: AnyAttribute - @Attribute var list: ViewList - let id: UniqueID - let isUnary: Bool - let allItems: MutableBox<[Unmanaged]> - - init(type: Any.Type, owner: AnyAttribute, list: Attribute, id: UniqueID, isUnary: Bool, subgraph: OGSubgraph, allItems: MutableBox<[Unmanaged]>) { - self.type = type - self.owner = owner - _list = list - self.id = id - self.isUnary = isUnary - self.allItems = allItems - super.init(subgraph: subgraph) - allItems.wrappedValue.append(.passUnretained(self)) - } - - override func invalidate() { - for (index, item) in allItems.wrappedValue.enumerated() { - guard item == .passUnretained(self) else { - continue - } - allItems.wrappedValue.remove(at: index) - break - } - } - - func bindID(_ id: inout _ViewList_ID) { - id.bind(explicitID: AnyHashable(self.id), owner: owner, isUnary: isUnary) - } - } - - // TODO - struct WrappedList { - let base: ViewList - let item: Item - let lastID: UniqueID? - let lastTransaction: TransactionID - } - - // TODO - struct WrappedIDs/*: Sequence*/ { - let base: _ViewList_ID.Views - let item: Item - } - - struct Transform: _ViewList_SublistTransform_Item { - func bindID(_ id: inout _ViewList_ID) { - // TODO - } - - func apply(sublist: inout _ViewList_Sublist) { - item.bindID(&sublist.id) - sublist.elements = item.wrapping(sublist.elements) - } - - var item: Item + var description: String { + "\(V.self)" } } diff --git a/Sources/OpenSwiftUICore/View/DynamicView.swift b/Sources/OpenSwiftUICore/View/DynamicView.swift index ce9cd25e3..cc3010ac8 100644 --- a/Sources/OpenSwiftUICore/View/DynamicView.swift +++ b/Sources/OpenSwiftUICore/View/DynamicView.swift @@ -72,8 +72,8 @@ private struct DynamicViewContainer: StatefulRule, AsyncAttribute where V: Dy func updateValue() { let (type, id) = view.childInfo(metadata: metadata) - let oldValue: Value? = Graph.outputValue()?.pointee - guard oldValue.map({ $0.matches(type: type, id: id)}) == false else { + let oldValue: Value? = hasValue ? value : nil + guard oldValue.map({ $0.matches(type: type, id: id)}) != true else { return } if let oldValue { diff --git a/Tests/OpenSwiftUICoreTests/View/AnyViewTests.swift b/Tests/OpenSwiftUICoreTests/View/AnyViewTests.swift index b1d289443..6fcdd1f21 100644 --- a/Tests/OpenSwiftUICoreTests/View/AnyViewTests.swift +++ b/Tests/OpenSwiftUICoreTests/View/AnyViewTests.swift @@ -5,16 +5,17 @@ @testable import OpenSwiftUICore import Testing +@MainActor struct AnyViewTests { @Test - func testInitFromValue() throws { + func initFromValue() throws { let empty = EmptyView() - let any = try #require(AnyView(_fromValue: empty)) - // #expect(any.storage.id == nil) - let _: EmptyView = any.storage.child() - - let any1 = AnyView(any) - let any2 = try #require(AnyView(_fromValue: any)) - #expect(any1.storage === any2.storage) + let any1 = try #require(AnyView(_fromValue: empty)) + + let any2 = try #require(AnyView(_fromValue: any1)) + #expect(any2.storage === any1.storage) + + let any3 = AnyView(any1) + #expect(any3.storage === any1.storage) } }