From df810024175e6c0b9295fb86bbb992f109a5e3a0 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 19 Jul 2025 21:26:57 +0800 Subject: [PATCH 1/2] Update ViewRendererHost --- .../View/Graph/EmptyViewRendererHost.swift | 19 +- .../View/Graph/ViewGraph.swift | 38 +++- .../View/Graph/ViewRendererHost.swift | 189 ++++++++++-------- .../Graph/ViewRendererHostProperties.swift | 38 ++++ .../View/Graph/ViewRenderingPhase.swift | 15 ++ 5 files changed, 193 insertions(+), 106 deletions(-) create mode 100644 Sources/OpenSwiftUICore/View/Graph/ViewRendererHostProperties.swift create mode 100644 Sources/OpenSwiftUICore/View/Graph/ViewRenderingPhase.swift diff --git a/Sources/OpenSwiftUICore/View/Graph/EmptyViewRendererHost.swift b/Sources/OpenSwiftUICore/View/Graph/EmptyViewRendererHost.swift index 4d65cfc58..b21f12920 100644 --- a/Sources/OpenSwiftUICore/View/Graph/EmptyViewRendererHost.swift +++ b/Sources/OpenSwiftUICore/View/Graph/EmptyViewRendererHost.swift @@ -2,7 +2,7 @@ // EmptyViewRendererHost.swift // OpenSwiftUICore // -// Audited for iOS 18.0 +// Audited for 6.5.4 // Status: Complete final package class EmptyViewRendererHost: ViewRendererHost { @@ -20,6 +20,7 @@ final package class EmptyViewRendererHost: ViewRendererHost { Update.begin() viewGraph = ViewGraph(rootViewType: EmptyView.self, requestedOutputs: []) viewGraph.setEnvironment(environment) + viewGraph.setRootView(EmptyView()) initializeViewGraph() Update.end() } @@ -34,19 +35,7 @@ final package class EmptyViewRendererHost: ViewRendererHost { package func updateSafeArea() {} - package func updateScrollableContainerSize() {} - - package func renderDisplayList( - _ list: DisplayList, - asynchronously: Bool, - time: Time, - nextTime: Time, - targetTimestamp: Time?, - version: DisplayList.Version, - maxVersion: DisplayList.Version - ) -> Time { - .infinity - } - + package func updateContainerSize() {} + package func forEachIdentifiedView(body: (_IdentifiedViewProxy) -> Void) {} } diff --git a/Sources/OpenSwiftUICore/View/Graph/ViewGraph.swift b/Sources/OpenSwiftUICore/View/Graph/ViewGraph.swift index e5a025a52..0e2cb7bab 100644 --- a/Sources/OpenSwiftUICore/View/Graph/ViewGraph.swift +++ b/Sources/OpenSwiftUICore/View/Graph/ViewGraph.swift @@ -67,12 +67,12 @@ package final class ViewGraph: GraphHost { @OptionalAttribute var scrollableContainerSize: ViewSize? @Attribute var gestureTime: Time - // @Attribute var gestureEvents: [EventID : EventType] - // @Attribute var inheritedPhase: _GestureInputs.InheritedPhase + @Attribute var gestureEvents: [EventID : EventType] + @Attribute var inheritedPhase: _GestureInputs.InheritedPhase @Attribute var gestureResetSeed: UInt32 - // @OptionalAttribute var rootPhase: GesturePhase? - // @OptionalAttribute package var gestureDebug: GestureDebug.Data? - // @OptionalAttribute package var gestureCategory: GestureCategory? + @OptionalAttribute var rootPhase: GesturePhase? + @OptionalAttribute package var gestureDebug: GestureDebug.Data? + @OptionalAttribute package var gestureCategory: GestureCategory? @Attribute package var gesturePreferenceKeys: PreferenceKeys var eventSubgraph: Subgraph? @@ -155,8 +155,8 @@ package final class ViewGraph: GraphHost { _safeAreaInsets = Attribute(value: _SafeAreaInsetsModifier(elements: [.init(regions: .container, insets: .zero)])) _defaultLayoutComputer = Attribute(value: .defaultValue) _gestureTime = Attribute(value: .zero) - // _gestureEvents - // _inheritedPhase + _gestureEvents = Attribute(value: [:]) + _inheritedPhase = Attribute(value: .defaultValue) _gestureResetSeed = Attribute(value: .zero) _gesturePreferenceKeys = Attribute(value: .init()) _rootGeometry = Attribute(RootGeometry( @@ -314,9 +314,7 @@ package final class ViewGraph: GraphHost { extension ViewGraph { package func setRootView(_ view: Root) where Root: View { - @Attribute(identifier: rootView) - var rootView: Root - rootView = view + rootView.unsafeCast(to: Root.self).value = view } package func setSize(_ size: ViewSize) { @@ -518,7 +516,27 @@ extension ViewGraph { } } +extension ViewGraph { + package var responderNode: ResponderNode? { + _openSwiftUIUnimplementedFailure() + } + + package func setInheritedPhase(_ phase: _GestureInputs.InheritedPhase) { + _openSwiftUIUnimplementedFailure() + } + package func sendEvents( + _ events: [EventID: any EventType], + rootNode: ResponderNode, + at time: Time + ) -> GesturePhase { + _openSwiftUIUnimplementedFailure() + } + + package func resetEvents() { + _openSwiftUIUnimplementedFailure() + } +} // MARK: - RootGeometry diff --git a/Sources/OpenSwiftUICore/View/Graph/ViewRendererHost.swift b/Sources/OpenSwiftUICore/View/Graph/ViewRendererHost.swift index 57e7b3e92..59d29c975 100644 --- a/Sources/OpenSwiftUICore/View/Graph/ViewRendererHost.swift +++ b/Sources/OpenSwiftUICore/View/Graph/ViewRendererHost.swift @@ -2,67 +2,47 @@ // ViewRendererHost.swift // OpenSwiftUICore // -// Audited for iOS 18.0 +// Audited for 6.5.4 // Status: WIP -// ID: 76C8A4B3FC8EE0F99045B3425CD62255 +// ID: 76C8A4B3FC8EE0F99045B3425CD62255 (SwiftUICore) package import Foundation import OpenGraphShims -// MARK: - ViewRendererHostProperties - -package struct ViewRendererHostProperties: OptionSet { - package let rawValue: UInt16 - package init(rawValue: UInt16) { - self.rawValue = rawValue - } - package static let rootView: ViewRendererHostProperties = .init(rawValue: 1 << 0) - package static let environment: ViewRendererHostProperties = .init(rawValue: 1 << 1) - package static let focusedValues: ViewRendererHostProperties = .init(rawValue: 1 << 2) - package static let transform: ViewRendererHostProperties = .init(rawValue: 1 << 3) - package static let size: ViewRendererHostProperties = .init(rawValue: 1 << 4) - package static let safeArea: ViewRendererHostProperties = .init(rawValue: 1 << 5) - package static let scrollableContainerSize: ViewRendererHostProperties = .init(rawValue: 1 << 6) - package static let focusStore: ViewRendererHostProperties = .init(rawValue: 1 << 7) - package static let accessibilityFocusStore: ViewRendererHostProperties = .init(rawValue: 1 << 8) - package static let focusedItem: ViewRendererHostProperties = .init(rawValue: 1 << 9) - package static let accessibilityFocus: ViewRendererHostProperties = .init(rawValue: 1 << 10) - package static let all: ViewRendererHostProperties = [.rootView, .environment, .focusedValues, .transform, .size, .safeArea, .scrollableContainerSize, .focusStore, .accessibilityFocusStore, .focusedItem, .accessibilityFocus] -} - -// MARK: - ViewRenderingPhase - -package enum ViewRenderingPhase { - case none - case rendering - case renderingAsync -} - -@available(*, unavailable) -extension ViewRenderingPhase: Sendable {} - -// MARK: - ViewRendererHost +// MARK: - ViewRendererHost [6.5.4] package protocol ViewRendererHost: ViewGraphDelegate { var viewGraph: ViewGraph { get } + var currentTimestamp: Time { get set } + + var responderNode: ResponderNode? { get } + var propertiesNeedingUpdate: ViewRendererHostProperties { get set } + var renderingPhase: ViewRenderingPhase { get set } + var externalUpdateCount: Int { get set } + func updateRootView() + func updateEnvironment() - func updateFocusedItem() - func updateFocusedValues() + func updateTransform() + func updateSize() + func updateSafeArea() - func updateScrollableContainerSize() + + func updateContainerSize() + func updateFocusStore() - func updateAccessibilityFocus() - func updateAccessibilityFocusStore() + + func updateFocusedItem() + + func updateFocusedValues() + func updateAccessibilityEnvironment() - func renderDisplayList(_ list: DisplayList, asynchronously: Bool, time: Time, nextTime: Time, targetTimestamp: Time?, version: DisplayList.Version, maxVersion: DisplayList.Version) -> Time - func didRender() } extension ViewRendererHost { @@ -106,11 +86,11 @@ extension ViewRendererHost { @_spi(ForOpenSwiftUIOnly) public func updateViewGraph(body: (ViewGraph) -> T) -> T { - Update.begin() - defer { Update.end() } - return Graph.withoutUpdate { - updateGraph() - return body(viewGraph) + Update.perform { + Graph.withoutUpdate { + updateGraph() + return body(viewGraph) + } } } @@ -122,9 +102,7 @@ extension ViewRendererHost { } } } - - package func didRender() {} - + @_spi(ForOpenSwiftUIOnly) public func preferencesDidChange() {} @@ -190,11 +168,7 @@ extension ViewRendererHost { } } - package func updateTransform() { - // Blocked by ValueState - // viewGraph.$rootTransform.valueState - // _openSwiftUIUnimplementedFailure() - } + package func render(interval: Double = 0, updateDisplayList: Bool = true, targetTimestamp: Time? = nil) { Update.begin() @@ -311,10 +285,18 @@ extension ViewRendererHost { get { viewGraph.centersRootView } set { viewGraph.centersRootView = newValue } } - -// package var responderNode: ResponderNode? { -// _openSwiftUIUnimplementedFailure() -// } + + package var responderNode: ResponderNode? { + updateViewGraph { viewGraph in + viewGraph.rootResponders?.first + } + } + + package func updateTransform() { + // Blocked by ValueState + // viewGraph.$rootTransform.valueState + _openSwiftUIUnimplementedWarning() + } package var isRootHost: Bool { guard let bridge = viewGraph.preferenceBridge else { @@ -323,29 +305,69 @@ extension ViewRendererHost { return bridge.viewGraph == nil } - private var enclosingHosts: [ViewRendererHost] { _openSwiftUIUnimplementedFailure() } - package func performExternalUpdate(_ update: () -> Void) { _openSwiftUIUnimplementedFailure() } - package func updateFocusedItem() {} - package func updateFocusedValues() {} - package func updateFocusStore() {} - package func updateAccessibilityFocus() {} - package func updateAccessibilityFocusStore() {} - package func updateAccessibilityEnvironment() {} + private var enclosingHosts: [ViewRendererHost] { + _openSwiftUIUnimplementedFailure() + } + + package func performExternalUpdate(_ update: () -> Void) { + Update.assertIsLocked() + for host in enclosingHosts { + host.externalUpdateCount += 1 + } + update() + for host in enclosingHosts { + guard host.externalUpdateCount >= 1 else { + preconditionFailure("Unbalanced will/did update functions.") + } + host.externalUpdateCount -= 1 + } + } + + package func updateFocusStore() { + _openSwiftUIEmptyStub() + } + + package func updateFocusedItem() { + _openSwiftUIEmptyStub() + } + + package func updateFocusedValues() { + _openSwiftUIEmptyStub() + } + + package func updateAccessibilityEnvironment() { + _openSwiftUIEmptyStub() + } } -// MARK: - ViewRendererHost + Gesture [TODO] +// MARK: - ViewRendererHost + Gesture [6.5.4] package let hostingViewCoordinateSpace: CoordinateSpace.ID = .init() -//extension ViewRendererHost { -// package var nextGestureUpdateTime: Time { -// get -// } -// package func sendEvents(_ events: [EventID : any EventType], rootNode: ResponderNode, at time: Time) -> GesturePhase -// package func resetEvents() -// package func gestureCategory() -> GestureCategory? -// package func setInheritedPhase(_ phase: _GestureInputs.InheritedPhase) -//} +extension ViewRendererHost { + package var nextGestureUpdateTime: Time { + viewGraph.nextUpdate.gestures.time + } + + package func sendEvents( + _ events: [EventID : any EventType], + rootNode: ResponderNode, + at time: Time + ) -> GesturePhase { + + } + package func resetEvents() { + viewGraph.resetEvents() + } + + package func gestureCategory() -> GestureCategory? { + viewGraph.gestureCategory + } + + package func setInheritedPhase(_ phase: _GestureInputs.InheritedPhase) { + viewGraph.inheritedPhase = phase + } +} extension ViewRendererHost { package func sendTestEvents(_ events: [EventID : any EventType]) { @@ -363,7 +385,7 @@ extension ViewRendererHost { } } -// MARK: - ViewGraph + viewRendererHost +// MARK: - ViewGraph + viewRendererHost [6.5.4] extension ViewGraph { package static var viewRendererHost: (any ViewRendererHost)? { @@ -371,7 +393,7 @@ extension ViewGraph { } } -// MARK: - EnvironmentValues + PreferenceBridge +// MARK: - EnvironmentValues + PreferenceBridge [6.5.4] extension EnvironmentValues { private struct PreferenceBridgeKey: EnvironmentKey { @@ -387,11 +409,16 @@ extension EnvironmentValues { } } -// MARK: - ViewRendererHost + rootContentPath [TODO] +// MARK: - ViewRendererHost + rootContentPath [6.5.4] extension ViewRendererHost { package func rootContentPath(kind: ContentShapeKinds) -> Path { - _openSwiftUIUnimplementedFailure() + guard let responderNode, + let viewResponder = responderNode as? ViewResponder + else { return Path() } + var path = Path() + viewResponder.addContentPath(to: &path, kind: kind, in: .root, observer: nil) + return path } } diff --git a/Sources/OpenSwiftUICore/View/Graph/ViewRendererHostProperties.swift b/Sources/OpenSwiftUICore/View/Graph/ViewRendererHostProperties.swift new file mode 100644 index 000000000..8ac596a0b --- /dev/null +++ b/Sources/OpenSwiftUICore/View/Graph/ViewRendererHostProperties.swift @@ -0,0 +1,38 @@ +// +// ViewRendererHostProperties.swift +// OpenSwiftUICore +// +// Audited for 6.5.4 +// Status: Complete + +package struct ViewRendererHostProperties: OptionSet { + package let rawValue: UInt16 + + package init(rawValue: UInt16) { + self.rawValue = rawValue + } + + package static let rootView: ViewRendererHostProperties = .init(rawValue: 1 << 0) + + package static let environment: ViewRendererHostProperties = .init(rawValue: 1 << 1) + + package static let transform: ViewRendererHostProperties = .init(rawValue: 1 << 2) + + package static let size: ViewRendererHostProperties = .init(rawValue: 1 << 3) + + package static let safeArea: ViewRendererHostProperties = .init(rawValue: 1 << 4) + + package static let containerSize: ViewRendererHostProperties = .init(rawValue: 1 << 5) + + package static let focusStore: ViewRendererHostProperties = .init(rawValue: 1 << 6) + + package static let focusedItem: ViewRendererHostProperties = .init(rawValue: 1 << 7) + + package static let focusedValues: ViewRendererHostProperties = .init(rawValue: 1 << 8) + + package static let all: ViewRendererHostProperties = [ + .rootView, .environment, .transform, + .size, .safeArea, .containerSize, + .focusStore, .focusedItem, focusedValues, + ] +} diff --git a/Sources/OpenSwiftUICore/View/Graph/ViewRenderingPhase.swift b/Sources/OpenSwiftUICore/View/Graph/ViewRenderingPhase.swift new file mode 100644 index 000000000..10dd76b5c --- /dev/null +++ b/Sources/OpenSwiftUICore/View/Graph/ViewRenderingPhase.swift @@ -0,0 +1,15 @@ +// +// ViewRenderingPhase.swift +// OpenSwiftUICore +// +// Audited for 6.5.4 +// Status: Complete + +package enum ViewRenderingPhase { + case none + case rendering + case renderingAsync +} + +@available(*, unavailable) +extension ViewRenderingPhase: Sendable {} From d3c67c8914d058d856c8eefcb43440b8db18a09d Mon Sep 17 00:00:00 2001 From: Kyle Date: Sun, 20 Jul 2025 01:02:20 +0800 Subject: [PATCH 2/2] Complete ViewRenderHost --- .../Hosting/AppKit/View/NSHostingView.swift | 92 +++----- .../Hosting/UIKit/View/UIHostingView.swift | 92 ++------ .../UIKit/View/UIHostingViewBase.swift | 6 +- Sources/OpenSwiftUICore/Graph/GraphHost.swift | 7 +- .../View/Graph/ViewGraphRender.swift | 2 +- .../View/Graph/ViewRendererHost.swift | 205 +++++++++++++----- 6 files changed, 209 insertions(+), 195 deletions(-) diff --git a/Sources/OpenSwiftUI/Integration/Hosting/AppKit/View/NSHostingView.swift b/Sources/OpenSwiftUI/Integration/Hosting/AppKit/View/NSHostingView.swift index 0de0ca70b..0eee30ec9 100644 --- a/Sources/OpenSwiftUI/Integration/Hosting/AppKit/View/NSHostingView.swift +++ b/Sources/OpenSwiftUI/Integration/Hosting/AppKit/View/NSHostingView.swift @@ -321,78 +321,25 @@ extension NSHostingView: ViewRendererHost { // TODO } - package func updateScrollableContainerSize() { + package func updateContainerSize() { // TODO } - package func renderDisplayList( - _ list: DisplayList, - asynchronously: Bool, - time: Time, - nextTime: Time, - targetTimestamp: Time?, - version: DisplayList.Version, - maxVersion: DisplayList.Version - ) -> Time { - func render() -> Time { - let scale = window?.screen?.backingScaleFactor ?? 1 - let environment = DisplayList.ViewRenderer.Environment(contentsScale: scale, opaqueBackground: isOpaque) - #if canImport(SwiftUI, _underlyingVersion: 6.0.87) && _OPENSWIFTUI_SWIFTUI_RENDER - - return renderer.swiftUI_render( - rootView: self, - from: list, - time: time, - nextTime: nextTime, - version: version, - maxVersion: maxVersion, - environment: environment - ) - - #else - return renderer.render( - rootView: self, - from: list, - time: time, - nextTime: nextTime, - version: version, - maxVersion: maxVersion, - environment: environment - ) - #endif - } - - if asynchronously { - if let renderedTime = renderer.renderAsync( - to: list, - time: time, - nextTime: nextTime, - targetTimestamp: targetTimestamp, - version: version, - maxVersion: maxVersion - ) { - return renderedTime - } else { - var renderedTime = nextTime - Update.syncMain { - renderedTime = render() - } - return renderedTime - } - } else { - var renderedTime = nextTime - Update.syncMain { - renderedTime = render() - } - return renderedTime - } - } - package func updateRootView() { let rootView = makeRootView() viewGraph.setRootView(rootView) } + package func `as`(_ type: T.Type) -> T? { + if ViewGraphRenderDelegate.self == T.self { + return unsafeBitCast(self as any ViewGraphRenderDelegate, to: T.self) + } else if DisplayList.ViewRenderer.self == T.self { + return unsafeBitCast(renderer, to: T.self) + } else { + return nil + } + } + package func requestUpdate(after: Double) { // TODO setNeedsUpdate() @@ -424,4 +371,21 @@ extension NSHostingView/*: TestHost*/ { } } } + +// FIXME +extension NSHostingView: ViewGraphRenderDelegate { + package var renderingRootView: AnyObject { + self + } + + package func updateRenderContext(_ context: inout ViewGraphRenderContext) { + context.contentsScale = window?.backingScaleFactor ?? 1.0 + } + + package func withMainThreadRender(wasAsync: Bool, _ body: () -> Time) -> Time { + // TODO + return body() + } +} + #endif diff --git a/Sources/OpenSwiftUI/Integration/Hosting/UIKit/View/UIHostingView.swift b/Sources/OpenSwiftUI/Integration/Hosting/UIKit/View/UIHostingView.swift index e5e60882c..8fbcc27b9 100644 --- a/Sources/OpenSwiftUI/Integration/Hosting/UIKit/View/UIHostingView.swift +++ b/Sources/OpenSwiftUI/Integration/Hosting/UIKit/View/UIHostingView.swift @@ -414,7 +414,7 @@ extension _UIHostingView { guard safeAreaRegions != oldSafeAreaRegions else { return } - invalidateProperties([.safeArea, .scrollableContainerSize]) + invalidateProperties([.safeArea, .containerSize]) } func updateBackgroundColor() { @@ -465,6 +465,21 @@ extension _UIHostingView { } } +extension _UIHostingView: ViewGraphDelegate { + package func `as`(_ type: T.Type) -> T? { + guard let value = base.as(type) else { + // TODO + return nil + } + return value + } + + package func requestUpdate(after: Double) { + // TODO + requestImmediateUpdate() + } +} + extension _UIHostingView: ViewRendererHost { package var renderingPhase: ViewRenderingPhase { get { base.renderingPhase } @@ -497,87 +512,14 @@ extension _UIHostingView: ViewRendererHost { } } - package func updateScrollableContainerSize() { + package func updateContainerSize() { // _openSwiftUIUnimplementedFailure() } - - package func renderDisplayList( - _ list: DisplayList, - asynchronously: Bool, - time: Time, - nextTime: Time, - targetTimestamp: Time?, - version: DisplayList.Version, - maxVersion: DisplayList.Version - ) -> Time { - func render() -> Time { - let scale = window?.screen.scale ?? 1 - let environment = DisplayList.ViewRenderer.Environment(contentsScale: scale) - #if canImport(SwiftUI, _underlyingVersion: 6.0.87) && _OPENSWIFTUI_SWIFTUI_RENDER - return renderer.swiftUI_render( - rootView: self, - from: list, - time: time, - nextTime: nextTime, - version: version, - maxVersion: maxVersion, - environment: environment - ) - #else - return renderer.render( - rootView: self, - from: list, - time: time, - nextTime: nextTime, - version: version, - maxVersion: maxVersion, - environment: environment - ) - #endif - } - - if asynchronously { - if let renderedTime = renderer.renderAsync( - to: list, - time: time, - nextTime: nextTime, - targetTimestamp: targetTimestamp, - version: version, - maxVersion: maxVersion - ) { - return renderedTime - } else { - var renderedTime = nextTime - Update.syncMain { - renderedTime = render() - } - return renderedTime - } - } else { - if Self.areAnimationsEnabled, shouldDisableUIKitAnimations { - var renderedTime = nextTime // FIXME - Self.performWithoutAnimation { - renderedTime = render() - } - base.allowUIKitAnimationsForNextUpdate = false - return renderedTime - } else { - let renderedTime = render() - base.allowUIKitAnimationsForNextUpdate = false - return renderedTime - } - } - } package func updateRootView() { let rootView = makeRootView() viewGraph.setRootView(rootView) } - - package func requestUpdate(after: Double) { - // TODO - requestImmediateUpdate() - } func requestImmediateUpdate() { // FIXME diff --git a/Sources/OpenSwiftUI/Integration/Hosting/UIKit/View/UIHostingViewBase.swift b/Sources/OpenSwiftUI/Integration/Hosting/UIKit/View/UIHostingViewBase.swift index 4057ae103..dd4e0faf0 100644 --- a/Sources/OpenSwiftUI/Integration/Hosting/UIKit/View/UIHostingViewBase.swift +++ b/Sources/OpenSwiftUI/Integration/Hosting/UIKit/View/UIHostingViewBase.swift @@ -88,7 +88,7 @@ package class UIHostingViewBase { package var updateTimer: Timer? - package var canAdvanceTimeAutomatically: Bool = false + package var canAdvanceTimeAutomatically: Bool = true package var pendingPreferencesUpdate: Bool = false @@ -160,7 +160,7 @@ package class UIHostingViewBase { package func `as`(_ type: T.Type) -> T? { if ViewGraphRenderDelegate.self == T.self { - return unsafeBitCast(self, to: T.self) + return unsafeBitCast(self as any ViewGraphRenderDelegate, to: T.self) } else if DisplayList.ViewRenderer.self == T.self { return unsafeBitCast(renderer, to: T.self) } else { @@ -432,7 +432,7 @@ package class UIHostingViewBase { package func layoutSubviews() { guard let host, let uiView, - let window = uiView.window, + uiView.window != nil, canAdvanceTimeAutomatically else { return diff --git a/Sources/OpenSwiftUICore/Graph/GraphHost.swift b/Sources/OpenSwiftUICore/Graph/GraphHost.swift index 60047ee5d..606e8a8b9 100644 --- a/Sources/OpenSwiftUICore/Graph/GraphHost.swift +++ b/Sources/OpenSwiftUICore/Graph/GraphHost.swift @@ -196,9 +196,12 @@ open class GraphHost: CustomReflectable { return graph.counter(for: ._6) != 0 } - package final func setNeedsUpdate(mayDeferUpdate: Bool) { + package final func setNeedsUpdate(mayDeferUpdate: Bool, values: ViewRendererHostProperties) { self.mayDeferUpdate = self.mayDeferUpdate && mayDeferUpdate - data.graph?.setNeedsUpdate() + if let graph = data.graph { + // TODO: Trace + graph.setNeedsUpdate() + } } // MARK: - GraphHost.ConstantID diff --git a/Sources/OpenSwiftUICore/View/Graph/ViewGraphRender.swift b/Sources/OpenSwiftUICore/View/Graph/ViewGraphRender.swift index 69d9d4200..17eedfd10 100644 --- a/Sources/OpenSwiftUICore/View/Graph/ViewGraphRender.swift +++ b/Sources/OpenSwiftUICore/View/Graph/ViewGraphRender.swift @@ -7,7 +7,7 @@ package import Foundation -package protocol ViewGraphRenderDelegate { +package protocol ViewGraphRenderDelegate: AnyObject { var renderingRootView: AnyObject { get } func updateRenderContext(_ context: inout ViewGraphRenderContext) func withMainThreadRender(wasAsync: Bool, _ body: () -> Time) -> Time diff --git a/Sources/OpenSwiftUICore/View/Graph/ViewRendererHost.swift b/Sources/OpenSwiftUICore/View/Graph/ViewRendererHost.swift index 59d29c975..6faaa6ee3 100644 --- a/Sources/OpenSwiftUICore/View/Graph/ViewRendererHost.swift +++ b/Sources/OpenSwiftUICore/View/Graph/ViewRendererHost.swift @@ -3,7 +3,7 @@ // OpenSwiftUICore // // Audited for 6.5.4 -// Status: WIP +// Status: Blocked by OGValueState and Trace // ID: 76C8A4B3FC8EE0F99045B3425CD62255 (SwiftUICore) package import Foundation @@ -45,6 +45,9 @@ package protocol ViewRendererHost: ViewGraphDelegate { func updateAccessibilityEnvironment() } +// MARK: - ViewRendererHost + default implementation [6.5.4] + +@available(OpenSwiftUI_v1_0, *) extension ViewRendererHost { package var isRendering: Bool { renderingPhase != .none @@ -85,6 +88,7 @@ extension ViewRendererHost { } @_spi(ForOpenSwiftUIOnly) + @available(OpenSwiftUI_v6_0, *) public func updateViewGraph(body: (ViewGraph) -> T) -> T { Update.perform { Graph.withoutUpdate { @@ -95,6 +99,7 @@ extension ViewRendererHost { } @_spi(ForOpenSwiftUIOnly) + @available(OpenSwiftUI_v6_0, *) public func graphDidChange() { Update.locked { if !isRendering { @@ -104,15 +109,18 @@ extension ViewRendererHost { } @_spi(ForOpenSwiftUIOnly) - public func preferencesDidChange() {} + @available(OpenSwiftUI_v6_0, *) + public func preferencesDidChange() { + _openSwiftUIEmptyStub() + } package func invalidateProperties(_ props: ViewRendererHostProperties, mayDeferUpdate: Bool = true) { Update.locked { guard !propertiesNeedingUpdate.contains(props) else { return } - propertiesNeedingUpdate.insert(props) - viewGraph.setNeedsUpdate(mayDeferUpdate: mayDeferUpdate) + propertiesNeedingUpdate = propertiesNeedingUpdate.union(props) + viewGraph.setNeedsUpdate(mayDeferUpdate: mayDeferUpdate, values: propertiesNeedingUpdate) requestUpdate(after: .zero) } } @@ -126,13 +134,9 @@ extension ViewRendererHost { updateRootView() } if properties.contains(.environment) { - propertiesNeedingUpdate.remove(.rootView) + propertiesNeedingUpdate.remove(.environment) updateEnvironment() } - if properties.contains(.focusedValues) { - propertiesNeedingUpdate.remove(.focusedValues) - updateFocusedValues() - } if properties.contains(.transform) { propertiesNeedingUpdate.remove(.transform) updateTransform() @@ -145,32 +149,36 @@ extension ViewRendererHost { propertiesNeedingUpdate.remove(.safeArea) updateSafeArea() } - if properties.contains(.scrollableContainerSize) { - propertiesNeedingUpdate.remove(.scrollableContainerSize) - updateScrollableContainerSize() + if properties.contains(.containerSize) { + propertiesNeedingUpdate.remove(.containerSize) + updateContainerSize() } if properties.contains(.focusStore) { propertiesNeedingUpdate.remove(.focusStore) updateFocusStore() } - if properties.contains(.accessibilityFocusStore) { - propertiesNeedingUpdate.remove(.accessibilityFocusStore) - updateAccessibilityFocusStore() - } if properties.contains(.focusedItem) { propertiesNeedingUpdate.remove(.focusedItem) updateFocusedItem() } - if properties.contains(.accessibilityFocus) { - propertiesNeedingUpdate.remove(.accessibilityFocus) - updateAccessibilityFocus() + if properties.contains(.focusedValues) { + propertiesNeedingUpdate.remove(.focusedValues) + updateFocusedValues() } } } + package func updateTransform() { + // Blocked by OGValueState + // viewGraph.$rootTransform.valueState + _openSwiftUIUnimplementedWarning() + } - - package func render(interval: Double = 0, updateDisplayList: Bool = true, targetTimestamp: Time? = nil) { + package func render( + interval: Double = 0, + updateDisplayList: Bool = true, + targetTimestamp: Time? + ) { Update.begin() defer { Update.end() } guard !isRendering else { @@ -188,9 +196,7 @@ extension ViewRendererHost { updateGraph() } renderingPhase = .rendering - - var displayList: DisplayList = .init() - var version: DisplayList.Version = .init() + var (displayList, version) = (DisplayList(), DisplayList.Version()) Signpost.renderUpdate.traceInterval( object: self, nil @@ -216,7 +222,6 @@ extension ViewRendererHost { } var nextTime = viewGraph.nextUpdate.views.time if updateDisplayList { - let maxVersion = DisplayList.Version(forUpdate: ()) nextTime = renderDisplayList( displayList, asynchronously:false, @@ -224,7 +229,7 @@ extension ViewRendererHost { nextTime: nextTime, targetTimestamp: targetTimestamp, version: version, - maxVersion: maxVersion + maxVersion: DisplayList.Version(forUpdate: ()) ) } renderingPhase = .none @@ -235,10 +240,119 @@ extension ViewRendererHost { } } - package func renderAsync(interval: Double = 0, targetTimestamp: Time?) -> Time? { - _openSwiftUIUnimplementedFailure() + package func renderAsync( + interval: Double = 0, + targetTimestamp: Time? + ) -> Time? { + Update.assertIsLocked() + guard !isRendering, + !propertiesNeedingUpdate.isEmpty else { + return nil + } + let viewGraph = viewGraph + guard !viewGraph.hasPendingTransactions else { + return nil + } + return Update.perform { + currentTimestamp += interval + let time = currentTimestamp + renderingPhase = .renderingAsync + if let (list, version) = viewGraph.updateOutputsAsync(at: time) { + let renderTime = renderDisplayList( + list, + asynchronously: true, + time: time, + nextTime: viewGraph.nextUpdate.views.time, + targetTimestamp: targetTimestamp, + version: version, + maxVersion: .init(forUpdate: ()) + ) + renderingPhase = .none + return renderTime + } else { + renderingPhase = .none + return nil + } + + } } - + + package func renderDisplayList( + _ list: DisplayList, + asynchronously: Bool, + time: Time, + nextTime: Time, + targetTimestamp: Time?, + version: DisplayList.Version, + maxVersion: DisplayList.Version + ) -> Time { + guard let delegate = self.as(ViewGraphRenderDelegate.self), + let renderer = self.as(DisplayList.ViewRenderer.self) + else { return .infinity } + + func renderOnMainThread() -> Time { + var context = ViewGraphRenderContext( + contentsScale: .zero, + opaqueBackground: false + ) + delegate.updateRenderContext(&context) + var environment = DisplayList.ViewRenderer.Environment( + contentsScale: context.contentsScale + ) + #if os(macOS) + if isAppKitBased() { + environment.opaqueBackground = context.opaqueBackground + } + #endif + let rootView = delegate.renderingRootView + // TODO: CustomEventTrace + return delegate.withMainThreadRender(wasAsync: false) { + #if canImport(SwiftUI, _underlyingVersion: 6.0.87) && _OPENSWIFTUI_SWIFTUI_RENDER + renderer.swiftUI_render( + rootView: self, + from: list, + time: time, + nextTime: nextTime, + version: version, + maxVersion: maxVersion, + environment: environment + ) + #else + renderer.render( + rootView: self, + from: list, + time: time, + nextTime: nextTime, + version: version, + maxVersion: maxVersion, + environment: environment + ) + #endif + } + } + if asynchronously { + // TODO: CustomEventTrace + if let renderedTime = renderer.renderAsync( + to: list, + time: time, + nextTime: nextTime, + targetTimestamp: targetTimestamp, + version: version, + maxVersion: maxVersion + ) { + return renderedTime + } else { + var renderedTime = nextTime + Update.syncMain { + renderedTime = renderOnMainThread() + } + return renderedTime + } + } else { + return renderOnMainThread() + } + } + package func advanceTimeForTest(interval: Double) { guard interval >= 0 else { preconditionFailure("Test render timestamps must monotonically increase.") @@ -248,6 +362,7 @@ extension ViewRendererHost { } @_spi(Private) + @available(OpenSwiftUI_v4_0, *) public func preferenceValue(_ key: K.Type) -> K.Value where K: HostPreferenceKey { updateViewGraph { graph in graph.preferenceValue(key) @@ -259,26 +374,23 @@ extension ViewRendererHost { } package func sizeThatFits(_ proposal: _ProposedSize) -> CGSize { - updateViewGraph { graph in - // FIXME: - // graph.sizeThatFits(proposal, layoutComputer: layoutComputer, insets: rootViewInsets) - CGSize.zero + updateViewGraph { $0.sizeThatFits(proposal) } } package func explicitAlignment(of guide: HorizontalAlignment, at size: CGSize) -> CGFloat? { - _openSwiftUIUnimplementedFailure() + updateViewGraph { $0.explicitAlignment(of: guide, at: size) } } package func explicitAlignment(of guide: VerticalAlignment, at size: CGSize) -> CGFloat? { - _openSwiftUIUnimplementedFailure() + updateViewGraph { $0.explicitAlignment(of: guide, at: size) } } package func alignment(of guide: HorizontalAlignment, at size: CGSize) -> CGFloat { - _openSwiftUIUnimplementedFailure() + updateViewGraph { $0.alignment(of: guide, at: size) } } package func alignment(of guide: VerticalAlignment, at size: CGSize) -> CGFloat { - _openSwiftUIUnimplementedFailure() + updateViewGraph { $0.alignment(of: guide, at: size) } } package var centersRootView: Bool { @@ -287,15 +399,7 @@ extension ViewRendererHost { } package var responderNode: ResponderNode? { - updateViewGraph { viewGraph in - viewGraph.rootResponders?.first - } - } - - package func updateTransform() { - // Blocked by ValueState - // viewGraph.$rootTransform.valueState - _openSwiftUIUnimplementedWarning() + updateViewGraph { $0.rootResponders?.first } } package var isRootHost: Bool { @@ -354,8 +458,9 @@ extension ViewRendererHost { rootNode: ResponderNode, at time: Time ) -> GesturePhase { - + viewGraph.sendEvents(events, rootNode: rootNode, at: time) } + package func resetEvents() { viewGraph.resetEvents() } @@ -385,7 +490,7 @@ extension ViewRendererHost { } } -// MARK: - ViewGraph + viewRendererHost [6.5.4] +// MARK: - ViewGraph + viewRendererHost extension ViewGraph { package static var viewRendererHost: (any ViewRendererHost)? { @@ -393,7 +498,7 @@ extension ViewGraph { } } -// MARK: - EnvironmentValues + PreferenceBridge [6.5.4] +// MARK: - EnvironmentValues + PreferenceBridge extension EnvironmentValues { private struct PreferenceBridgeKey: EnvironmentKey { @@ -409,7 +514,7 @@ extension EnvironmentValues { } } -// MARK: - ViewRendererHost + rootContentPath [6.5.4] +// MARK: - ViewRendererHost + rootContentPath extension ViewRendererHost { package func rootContentPath(kind: ContentShapeKinds) -> Path {