diff --git a/Example/HostingExample/ViewController.swift b/Example/HostingExample/ViewController.swift index 68686d5f7..e83e46bc3 100644 --- a/Example/HostingExample/ViewController.swift +++ b/Example/HostingExample/ViewController.swift @@ -17,6 +17,8 @@ import UIKit import AppKit #endif +import OpenSwiftUI + #if os(iOS) || os(visionOS) class ViewController: UINavigationController { override func viewDidAppear(_ animated: Bool) { @@ -35,6 +37,10 @@ final class EntryViewController: UIViewController { DispatchQueue.main.asyncAfter(deadline: .now() + 1) { self.pushHostingVC() } + + #if OPENSWIFTUI || DEBUG + // debugUIKitUpdateCycle() + #endif } @objc diff --git a/Sources/COpenSwiftUI/Shims/UIKit/UIKit_Private.h b/Sources/COpenSwiftUI/Shims/UIKit/UIKit_Private.h index ca4a4a0de..54f8d22f9 100644 --- a/Sources/COpenSwiftUI/Shims/UIKit/UIKit_Private.h +++ b/Sources/COpenSwiftUI/Shims/UIKit/UIKit_Private.h @@ -91,12 +91,75 @@ bool UIViewIgnoresTouchEvents(UIView *view); OPENSWIFTUI_EXPORT float UIAnimationDragCoefficient(void); +UIView * _UIKitCreateCustomView(Class class, CALayer *layer); + // MARK: - UIUpdate related private API from UIKitCore OPENSWIFTUI_EXPORT -bool _UIUpdateAdaptiveRateNeeded(); +bool _UIUpdateAdaptiveRateNeeded(void); -UIView * _UIKitCreateCustomView(Class class, CALayer *layer); +OPENSWIFTUI_EXPORT +bool _UIUpdateCycleEnabled(void); + +typedef struct _UIUpdateTiming { + uint64_t unknown1; + uint64_t unknown2; + uint64_t unknown3; +} _UIUpdateTiming; + +typedef void (^_UIUpdateSequenceCallback)(void * _Nullable context, CGFloat time, const _UIUpdateTiming * _Nonnull timing); + +typedef struct _UIUpdateSequenceItem _UIUpdateSequenceItem; + +typedef struct _UIUpdateSequence { + _UIUpdateSequenceItem * _Nullable first; +} _UIUpdateSequence; + +typedef struct _UIUpdateSequenceItem { + const _UIUpdateSequenceItem * _Nullable next; + const _UIUpdateSequence * _Nullable sequence; + const char * name; + uint32_t flags; + void * _Nullable context; + void * _Nullable callback; // Actual type should be _UIUpdateSequenceCallback* +} _UIUpdateSequenceItem; + +// MARK: - UIUpdateSequence Items +// +// UIUpdateActionPhase defines specific phases of the UI update process. +// See: https://developer.apple.com/documentation/uikit/uiupdateactionphase +// +// Each UI update consists of several phases that run in a consistent order. +// There are two phase groups: standard and low-latency. +// +// Standard phase group (runs for each UI update): +// 1. beforeEventDispatch / afterEventDispatch -> HIDEventsItem +// 2. beforeCADisplayLinkDispatch / afterCADisplayLinkDispatch -> CADisplayLinksItem +// 3. beforeCATransactionCommit / afterCATransactionCommit -> CATransactionCommitItem +// +// Low-latency phase group (optional, runs after standard phases): +// 1. beforeLowLatencyEventDispatch / afterLowLatencyEventDispatch -> LowLatencyHIDEventsItem +// 2. beforeLowLatencyCATransactionCommit / afterLowLatencyCATransactionCommit -> LowLatencyCATransactionCommitItem + +OPENSWIFTUI_EXPORT const _UIUpdateSequenceItem * _Nonnull _UIUpdateSequenceScheduledItem; +OPENSWIFTUI_EXPORT const _UIUpdateSequenceItem * _Nonnull _UIUpdateSequenceHIDEventsItem; +OPENSWIFTUI_EXPORT const _UIUpdateSequenceItem * _Nonnull _UIUpdateSequenceCADisplayLinksItem; +OPENSWIFTUI_EXPORT const _UIUpdateSequenceItem * _Nonnull _UIUpdateSequenceAnimationsItem; +OPENSWIFTUI_EXPORT const _UIUpdateSequenceItem * _Nonnull _UIUpdateSequenceCATransactionCommitItem; +OPENSWIFTUI_EXPORT const _UIUpdateSequenceItem * _Nonnull _UIUpdateSequenceLowLatencyHIDEventsItem; +OPENSWIFTUI_EXPORT const _UIUpdateSequenceItem * _Nonnull _UIUpdateSequenceLowLatencyCATransactionCommitItem; +OPENSWIFTUI_EXPORT const _UIUpdateSequenceItem * _Nonnull _UIUpdateSequenceDoneItem; + +OPENSWIFTUI_EXPORT +void * _Nonnull _UIUpdateSequenceInsertItem(const _UIUpdateSequenceItem * _Nullable next, + const _UIUpdateSequence * _Nullable sequence, + const char * name, + uint32_t flags, + void * _Nullable context, + _UIUpdateSequenceCallback _Nullable callback); + +OPENSWIFTUI_EXPORT +void _UIUpdateSequenceRemoveItem(_UIUpdateSequenceItem *item); OPENSWIFTUI_ASSUME_NONNULL_END diff --git a/Sources/OpenSwiftUI/Integration/Hosting/UIKit/UIKitUpdateCycle.swift b/Sources/OpenSwiftUI/Integration/Hosting/UIKit/UIKitUpdateCycle.swift new file mode 100644 index 000000000..214691366 --- /dev/null +++ b/Sources/OpenSwiftUI/Integration/Hosting/UIKit/UIKitUpdateCycle.swift @@ -0,0 +1,96 @@ +// +// UIKitUpdateCycle.swift +// OpenSwiftUI +// +// Audited for 6.5.4 +// Status: WIP +// ID: 61722010453917A59567A84BEBF44765 (SwiftUI) + +#if os(iOS) || os(visionOS) +import COpenSwiftUI +@_spi(ForOpenSwiftUIOnly) +import OpenSwiftUICore + +package enum UIKitUpdateCycle { + private static var observerActions: [() -> Void] = [] + + private static var item: OpaquePointer? + + package static var defaultUseSetNeedsLayout: Bool = { + let key = "UseSetNeedsLayoutForUpdates" + let defaults = UserDefaults.standard + guard defaults.object(forKey: key) != nil else { + return false + } + return defaults.bool(forKey: key) + }() + + package static func addPreCommitObserver(_ action: @escaping () -> Void) { + guard _UIUpdateCycleEnabled() else { + return + } + if item == nil { + item = OpaquePointer( + _UIUpdateSequenceInsertItem( + _UIUpdateSequenceCATransactionCommitItem, + nil, + "OpenSwiftUIFlush", + 0, + nil, + ) { _, _, _ in + let actions = observerActions + guard !actions.isEmpty else { return } + observerActions = [] + for action in actions { + Update.perform(action) + } + }, + ) + + } + observerActions.append(action) + } + + package static func addPreCommitObserverOrAsyncMain(_ action: @escaping () -> Void) { + if _UIUpdateCycleEnabled() { + addPreCommitObserver(action) + } else { + DispatchQueue.main.async(execute: action) + } + } + + #if DEBUG + private static func debugItem(_ item: UnsafePointer<_UIUpdateSequenceItem>) { + let name = String(cString: item.pointee.name) + _ = _UIUpdateSequenceInsertItem( + item, + nil, + ("OpenSwiftUIDebug" + name), + 0, + nil + ) { _, _, _ in + print("[UIKitUpdateCycle] \(name) phase") + } + } + + package static func setupDebug() { + guard _UIUpdateCycleEnabled() else { return } + debugItem(_UIUpdateSequenceScheduledItem) + debugItem(_UIUpdateSequenceHIDEventsItem) + debugItem(_UIUpdateSequenceCADisplayLinksItem) + debugItem(_UIUpdateSequenceAnimationsItem) + debugItem(_UIUpdateSequenceCATransactionCommitItem) + debugItem(_UIUpdateSequenceLowLatencyHIDEventsItem) + debugItem(_UIUpdateSequenceLowLatencyCATransactionCommitItem) + debugItem(_UIUpdateSequenceDoneItem) + } + #endif +} + +#if DEBUG +public func debugUIKitUpdateCycle() { + UIKitUpdateCycle.setupDebug() +} +#endif + +#endif diff --git a/Sources/OpenSwiftUICore/Data/Update.swift b/Sources/OpenSwiftUICore/Data/Update.swift index bcd6319e8..96be18736 100644 --- a/Sources/OpenSwiftUICore/Data/Update.swift +++ b/Sources/OpenSwiftUICore/Data/Update.swift @@ -105,7 +105,7 @@ package enum Update { @inlinable @inline(__always) - static func perform(_ body: () throws -> T) rethrows -> T { + package static func perform(_ body: () throws -> T) rethrows -> T { begin() defer { end() } return try body()